How to Bulk Delete Your Tweets on X Using the Browser Console

How to Bulk Delete Your Tweets on X Using the Browser Console

If you want to clean up your X / Twitter account, deleting tweets one by one can take forever.
There are paid tools that can do this, but you can also automate the process directly from your browser using JavaScript and the browser console.

Before You Start

  • This method works directly in your browser using JavaScript.
  • You must be logged into your X account.
  • Deleted tweets cannot be recovered.
  • X may temporarily rate limit your account if you delete too many tweets too quickly.
  • Always double check scripts before running them in your browser console.

Script Settings

You can customize the behavior of the script by changing the values in the CONFIG section:

const CONFIG = {
  dryRun: false,
  maxActions: 9999,
  delayMs: 5000,
  skipPinned: true,
  removeReposts: true,
};
  • dryRun , If set to true, the script will simulate actions without actually deleting anything.
  • maxActions , Maximum number of tweets or reposts the script will process before stopping automatically.
  • delayMs , Delay in milliseconds between actions. Higher values reduce the chance of rate limiting.
  • skipPinned , If set to true, pinned tweets will not be deleted.
  • removeReposts , If set to true, reposts will also be removed automatically.

Step 1: Open Your X Profile

Go to your profile page on X:

https://x.com

Make sure you are viewing your own posts timeline.

Step 2: Open Developer Tools

  • Press F12 on your keyboard
  • Or right click anywhere on the page and click Inspect
  • Open the Console tab

Step 3: Paste and Run the Script

Copy and paste the following code into the browser console, then press Enter:

(() => {
  // =========================
  // CONFIG
  // =========================

  const CONFIG = {
    dryRun: false,
    maxActions: 9999,
    delayMs: 5000,
    skipPinned: true,
    removeReposts: true,
  };

  let actions = 0;
  let stopped = false;

  // =========================
  // STOP FUNCTION
  // =========================

  window.stopTweetCleanup = () => {
    stopped = true;
    console.log("Tweet cleanup stopped.");
  };

  // =========================
  // HELPERS
  // =========================

  const wait = (ms) =>
    new Promise(resolve => setTimeout(resolve, ms));

  async function clickElement(el, label) {
    if (!el) return false;

    if (CONFIG.dryRun) {
      console.log("[DRY RUN]", label);
      return true;
    }

    el.click();
    console.log("[CLICKED]", label);

    return true;
  }

  function getTopArticle() {
    return document.querySelector("section article");
  }

  function isPinned(article) {
    if (!article) return false;

    return article.innerText
      .toLowerCase()
      .includes("pinned");
  }

  // =========================
  // MAIN POST PROCESSOR
  // =========================

  async function processPost() {
    if (stopped) return false;

    const article = getTopArticle();

    if (!article) {
      console.log("No posts found.");
      return false;
    }

    // Skip pinned
    if (CONFIG.skipPinned && isPinned(article)) {
      console.log("Skipping pinned post.");
      window.scrollBy(0, 800);
      return true;
    }

    // =========================
    // REMOVE REPOSTS
    // =========================

    if (CONFIG.removeReposts) {
      const repostButton = article.querySelector(
        "button[data-testid='unretweet']"
      );

      if (repostButton) {
        console.log("Repost detected.");

        await clickElement(
          repostButton,
          "Open repost dialog"
        );

        await wait(1500);

        const confirmUndo = [
          ...document.querySelectorAll(
            "div[role='menuitem']"
          )
        ].find(el =>
          el.innerText
            .toLowerCase()
            .includes("undo")
        );

        if (confirmUndo) {
          await clickElement(
            confirmUndo,
            "Confirm undo repost"
          );

          actions++;

          await wait(2000);

          window.scrollBy(0, 800);

          return true;
        }

        console.log(
          "Undo repost option not found."
        );
      }
    }

    // =========================
    // DELETE TWEETS
    // =========================

    const menuButton = article.querySelector(
      "button[data-testid='caret']"
    );

    if (!menuButton) {
      console.log("Menu button not found.");

      window.scrollBy(0, 800);

      return true;
    }

    await clickElement(menuButton, "Open menu");

    await wait(1500);

    const deleteItem = [
      ...document.querySelectorAll(
        "div[role='menuitem']"
      )
    ].find(el =>
      el.innerText.trim() === "Delete"
    );

    if (deleteItem) {
      console.log("Delete option found.");

      await clickElement(
        deleteItem,
        "Delete tweet"
      );

      await wait(1500);

      const confirmBtn = document.querySelector(
        "button[data-testid='confirmationSheetConfirm']"
      );

      if (confirmBtn) {
        await clickElement(
          confirmBtn,
          "Confirm delete"
        );

        actions++;

        await wait(2500);

        window.scrollBy(0, 800);

        return true;
      }
    }
    else {
      console.log(
        "No delete option available."
      );
    }

    // =========================
    // SCROLL
    // =========================

    window.scrollBy(0, 800);

    return true;
  }

  // =========================
  // RUN LOOP
  // =========================

  async function run() {
    console.log("Starting cleanup...");
    console.log("Dry run:", CONFIG.dryRun);

    while (
      !stopped &&
      actions < CONFIG.maxActions
    ) {
      try {
        await processPost();
      }
      catch (err) {
        console.error("Error:", err);
      }

      await wait(CONFIG.delayMs);
    }

    console.log("Finished.");
    console.log("Total actions:", actions);
  }

  run();
})();

The script will:

  • Delete tweets automatically
  • Remove reposts automatically
  • Skip pinned tweets
  • Scroll down your profile continuously
  • Pause between actions to reduce rate limiting

How to Stop the Script

If you want to stop the cleanup process at any time, paste this into the console and press Enter:

window.stopTweetCleanup()

Possible Rate Limiting

X may temporarily limit actions if too many tweets are deleted too quickly.
If this happens:

  • Wait a few minutes
  • Refresh the page
  • Run the script again

Important Notes

  • This is a manual browser automation method.
  • X may change its interface at any time which could break the script.
  • Always test on a few tweets first before running large deletions.
  • Consider exporting your X archive before deleting content permanently.

You can request your X archive here:


https://x.com/settings/download_your_data

How to Bulk Delete Your Reddit Comments Using the Browser Console for Free

Bulk Delete Your Reddit Comments Using the Browser Console

If you want to clean up your Reddit history, deleting comments one by one can take forever. There are tools but many are not free. This method lets you automate the process using your browser’s developer tools.

Before You Start

  • This method works directly in your browser using JavaScript.
  • You must be logged into your Reddit account.
  • Use it carefully, deleted comments cannot be recovered.

Step 1: Go to Your Reddit Profile

Open your comments page (OLD reddit layout!):

https://old.reddit.com/user/YOUR_USERNAME/

Make sure you are viewing your comments, not posts.

Step 2: Open Developer Tools

  • Press F12 or right-click anywhere on the page and click Inspect
  • Go to the Console tab

Step 3: Paste and Run the Script

Copy and paste the following code into the console, then press Enter:

var $domNodeToIterateOver = $('.del-button .option .yes'),
    currentTime = 0,
    timeInterval = 500;

$domNodeToIterateOver.each(function() {

  var _this = $(this);
  currentTime = currentTime + timeInterval;

  setTimeout(function() {
    _this.click();
  }, currentTime);
});

This script will find all delete confirmation buttons and click them automatically with a slight delay between each action.

Step 4: Load More Comments and Repeat

Once the visible comments are deleted:

  1. Click Next at the bottom of the page
  2. Press the Up Arrow key in the console to bring back the script
  3. Press Enter again

Repeat this process until all comments are removed.

Possible Rate Limiting

Reddit may temporarily block your IP if you perform too many actions quickly. f this happens wait a few minutes and then continue again

Final Notes

This is a manual automation method, not a permanent tool. It works best on older Reddit layouts where jQuery is available.  Always double-check before running scripts in your browser

If you want a safer or more advanced method, consider using dedicated tools or Reddit API-based scripts.

Fix “X cannot be loaded because running scripts is disabled on this system” error

I tried running npm run dev in Windows PowerShell and got this error:

npm : File C:\Program Files\nodejs\npm.ps1 cannot be loaded because running scripts is disabled on this system.
For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1

Of course when I went to that Microsoft link there was tons of info and who has time to read all this? There is an easy fix for this. This is not just for npm run dev, you can get this error for most of all scripts.

Why This Happens

Windows PowerShell has security restrictions to prevent malicious scripts from executing. Since npm uses scripts, PowerShell may block it unless you change the execution policy.

Permanent Fix (Recommended)

If you want to allow npm and other local scripts to run without issues, you can adjust the execution policy.

  1. Open PowerShell as Administrator.
  2. Run the following command:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
  1. Type Y to confirm.

This allows scripts you create locally to run, while still blocking unsigned scripts from the internet.

Temporary Fix (One-Time Bypass)

If you don’t want to change your system’s settings permanently, you can bypass the restriction just for the current session.

  1. Open PowerShell.
  2. Run:
powershell -ExecutionPolicy Bypass
  1. In that session, run your command:
npm run dev

Once you close that PowerShell window, the execution policy returns to normal.

Which Option Should You Choose?

  • Use the permanent fix if you work with Node.js or npm regularly.
  • Use the temporary fix if you just want a quick, one-time solution without changing system policies.

✅ With either method, you’ll be able to run npm run dev successfully in PowerShell.

How to revert to previous packages if Laravel’s “composer update” breaks site?

When you run composer update and some packages break after the update, you can revert your project to the previous working state. Here’s how to handle it:

1. Check composer.lock file

After running composer update, the composer.lock file gets updated with the new versions of your dependencies. If things break, you can use Git to revert to the previous composer.lock.

2. Revert composer.lock using Git

If you are using Git and you committed the composer.lock file before running the update, you can easily go back to the previous version of the file. Here’s how:

git checkout HEAD^ composer.lock

This command checks out the previous version of the composer.lock file (the version before the last commit). If you want to revert to a specific commit, use the commit hash instead:

git checkout <commit-hash> composer.lock

3. Reinstall the previous dependencies

After reverting the composer.lock file, you need to install the previous versions of the packages defined in the restored composer.lock:

composer install

Note: Do not use composer update, as this will update the packages again. composer install will install the versions defined in composer.lock.

4. Commit the reverted composer.lock (if necessary)

After confirming that everything works as expected, commit the reverted composer.lock file:

git add composer.lock
git commit -m "Revert composer.lock to previous working state"

5. Lock Specific Package Versions (Optional)

If you want to prevent certain packages from being updated in the future, you can specify the exact versions in composer.json by using the caret (^) or tilde (~) version constraints. For example:

{
   "require": {
       "package/name": "^1.0"
   }
}

This ensures that composer update won’t update beyond the specified version.

By following these steps, you can safely revert your Composer dependencies back to a working state after an update causes issues.

Can you copy SSH keys to new PC? (Windows or Linux)

You dont need to generate new SSH keys when switching or upgrading to new PC.

Copying SSH Keys to a New PC: Windows and Linux

On Linux:

  1. Locate Your SSH Keys: Your keys are usually in the ~/.ssh/ directory.
    ls -al ~/.ssh
  2. Copy the Keys: Use a USB drive or a secure transfer method. If using scp:
    scp -r ~/.ssh username@newPC_IP:~/
  3. Set Permissions: On the new PC, set the correct permissions:
    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/id_rsa
    chmod 644 ~/.ssh/id_rsa.pub
  4. Test the Connection:
    ssh -T git@github.com

On Windows:

  1. Locate Your SSH Keys: If using Git Bash, your keys are typically in C:\Users\YourUsername\.ssh\.
  2. Copy the Keys: You can copy the .ssh folder to a USB drive or use file transfer methods.
  3. Paste to the New PC: On the new PC, paste the .ssh folder into C:\Users\YourUsername\.
  4. Permissions: You do not need to set permissions on Windows. Just ensure your private key (id_rsa) remains secure.
  5. Test the Connection:
    ssh -T git@github.com

Notes:

  • Ensure Security: Keep your private key secure and do not share it.
  • SSH Agent: If you use an SSH agent, you may need to add your key again on the new PC with ssh-add.
  • Update Public Key: If necessary, copy the contents of id_rsa.pub and update it in your Git service account settings.

What is the difference between ‘git push – set-usptream origin ‘ and ‘git push’

Difference Between git push --set-upstream origin and git push

The difference between git push --set-upstream origin <branch> and git push lies in how they handle the relationship between your local branch and the remote branch.

1. git push --set-upstream origin <branch>

  • Purpose: This command pushes your local branch to the remote repository and establishes a tracking relationship between the local branch and the remote branch.
  • Upstream Tracking: It sets the upstream branch for the current local branch. After this, future git push and git pull commands can be used without specifying the remote or branch name.
  • Example:
    git push --set-upstream origin feature-branch

    This pushes the feature-branch to the origin remote and sets it as the upstream branch for the current local branch.

  • Use case: If you’re pushing a branch to the remote for the first time or if the local branch does not yet have an upstream branch set, this command ensures that future pushes/pulls can be done with fewer commands.

2. git push

  • Purpose: This command pushes your current branch to the remote repository, but only if an upstream branch is already set.
  • No Upstream Tracking: If the current branch doesn’t have an upstream (tracking) branch set, Git will return an error and ask you to specify where to push.
  • Example:
    git push

    If you’re on feature-branch and an upstream branch is already set (e.g., from a previous --set-upstream command), it pushes to the appropriate remote and branch.

  • Use case: Use this when the upstream branch is already set, and you just want to push your changes without specifying details.

So, in short:

  • git push --set-upstream origin <branch>: Pushes the branch and sets up a connection between your local and remote branch for future operations.
  • git push: Pushes to the remote branch, but only if the upstream branch is already set.

Should I create a new Git branch locally or on GitHub first?

It’s generally better to create a new Git branch locally first and then push it to GitHub. Here’s why:

1. Create the Branch Locally First

Creating a branch locally gives you full control and flexibility before sharing it with others. The workflow typically looks like this:

  1. Create the branch locally:
    git checkout -b new-branch-name
  2. Work on your changes.
  3. Commit your changes:
    git add .
    git commit -m "Description of changes"
  4. Push the branch to GitHub:
    git push origin new-branch-name

Benefits of Creating Locally First:

  • Control: You have the opportunity to make changes and commits before sharing the branch on GitHub.
  • Avoiding Mistakes: It’s easier to catch and fix any mistakes locally before making the branch visible to others.
  • History: Commits will already be in place when you push, giving collaborators more context about your changes.

2. Create the Branch on GitHub First

You can also create a branch directly on GitHub and then fetch it locally:

  1. Create a new branch on GitHub via the web interface.
  2. Fetch and check out the branch locally:
    git fetch origin
    git checkout -b new-branch-name origin/new-branch-name

When to Create on GitHub First:

  • Collaboration: If you need to create a branch quickly for collaboration (e.g., for someone else to start working on it immediately), creating it on GitHub first can make sense.
  • Protection: Sometimes, teams enforce branch protection policies (e.g., on the main branch). Creating branches on GitHub might automatically apply those policies.

Summary:

  • Local First: Provides flexibility, control, and is the most common practice for individual developers or when working on a feature before sharing it.
  • GitHub First: Useful for initiating collaboration quickly or if your team has specific branch management policies.

In most cases, creating the branch locally first is the preferred approach.

How to rename GitHub repository

To rename a GitHub repository, follow these steps:

  1. Go to the repository on GitHub:

    • Log in to your GitHub account.
    • Navigate to the repository you want to rename.
  2. Access the repository settings:

    • Click on the “Settings” tab at the top of the repository page.
  3. Rename the repository:

    • In the “General” section, look for the “Repository name” field.
    • Edit the repository name to your desired new name.
  4. Confirm the change:

    • After entering the new name, scroll down and click the “Rename” button to apply the changes.

Additional Notes

  • GitHub will automatically redirect links to the old repository name to the new one, but it’s still a good idea to update any references (like in your local Git configurations or documentation).
  • In your local copy, you should update the remote URL using the command:
git remote set-url origin https://github.com/USERNAME/NEW_REPO_NAME.git

What is `protected $fillable` in a Laravel model?

In Laravel, the `protected $fillable` property is used to define an array of attributes that are mass assignable. This means these attributes can be assigned using mass-assignment techniques, such as when creating a new model instance or updating an existing one using the `create` or `update` methods.

Mass Assignment

Mass assignment is a way to assign multiple attributes to a model instance in a single step, typically using an array. For example, you might have a form where a user can submit several pieces of information at once. Instead of assigning each piece of information individually, you can pass the entire array to the `create` or `update` method.

Here’s an example of how you might use the `$fillable` property in a Laravel model:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // Define the attributes that are mass assignable
    protected $fillable = [
        'title', 
        'content', 
        'author_id'
    ];
}

In this example, the `Post` model has three attributes (`title`, `content`, and `author_id`) that are mass assignable.

Using Mass Assignment

With the `$fillable` property defined, you can now safely use mass assignment:

// Creating a new post
$post = Post::create([
    'title' => 'My First Post',
    'content' => 'This is the content of my first post.',
    'author_id' => 1
]);

// Updating an existing post
$post->update([
    'title' => 'Updated Title',
    'content' => 'This is the updated content.'
]);

The primary purpose of the `$fillable` property is to prevent mass-assignment vulnerabilities. Without `$fillable` (or its counterpart `$guarded`), any attribute in the model can be mass assigned, which could potentially allow malicious users to update sensitive fields that they shouldn’t have access to.

Mass Assignment Vulnerability

So what is this mass assignment vulnerability? Consider a scenario where a user can submit their profile information. If the user model has an attribute like `is_admin`, and this attribute is not protected, a malicious user could submit a form with an `is_admin` field and set its value to `true`, giving themselves admin privileges.

By defining the `$fillable` property, you explicitly specify which attributes are safe to be mass assigned, thus mitigating this risk.

So, using the `$fillable` property is a best practice in Laravel to ensure that only the intended attributes can be mass assigned, enhancing the security of your application.

C64/SID/Chiptunes covers

This is a non programing related post. I am a big fan of chiptunes, especially C64/SID music. There are so many amazing and well known SID tunes that I though were original but it turned out they are not. Some of them really surprised me. This does not take away from them, some covers are even better than originals, but it is interesting to hear the original and how it compares to chiptune version.

I created a new page Retro Music & Gaming where I write about these. Some of the songs include Cobra, Zoids, Commando, Rob Hubbard’s “Delta” and “Monty on the Run”, Golden Axe,. Enjoy, and I will try to update the section as much as time permits.