Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

429 Too Many Requests (Rate Limit Error) #2

Open
joebloom opened this issue Oct 27, 2024 · 0 comments
Open

429 Too Many Requests (Rate Limit Error) #2

joebloom opened this issue Oct 27, 2024 · 0 comments

Comments

@joebloom
Copy link

The script works nicely except it seems to hit a rate limit quite quickly and you have to wait a while to try the script again, so it involves quite a bit of manual labour to deal with thousands of likes.

I brought the current version into Claude and asked it to create a 90 second pause every 100 posts unliked, in order to slow it down somewhat to try and avoid the rate limit. So far, it seems to be working:

rapidunlike_rate_limit_preview

I thought I'd share it here and maybe the dev can human-sense check it and make it even better/more reliable?

Such as defining the best number of posts to process before the auto-pause, as well as the amount of time to auto-pause/wait, if there is such a number, or making it selectable, in order to avoid the rate limit.

It also doesn't seem that the script tries to do anything differently when the 429 error happens, so in addition or alternatively, the script could change its response/actions when the 429 error shows up? (If possible)

Here's the code with my batching auto-pause:

//┌─────────────────────────────────────────────────────┐
//│  ____             _     _ _   _       _ _ _         │
//│ |  _ \ __ _ _ __ (_) __| | | | |_ __ | (_) | _____  │
//│ | |_) / _` | '_ \| |/ _` | | | | '_ \| | | |/ / _ \ │
//│ |  _ < (_| | |_) | | (_| | |_| | | | | | |   <  __/ │
//│ |_| \_\__,_| .__/|_|\__,_|\___/|_| |_|_|_|_|\_\___| │
//│            |_|   https://github.com/ajwdd           │
//└─────────────────────────────────────────────────────┘

// Configuration with improved rate limiting
const config = {
  MAX_UNLIKES: 5500,
  BASE_WAIT_TIME: 500,  // Increased base wait time
  INCREMENT_WAIT: 200,
  DECREMENT_WAIT: 50,
  RETRY_COUNT: 3,
  BATCH_SIZE: 100,  // Number of unlikes before forced pause
  BATCH_PAUSE_TIME: 90 * 1000,  // 90 second pause between batches
  PROGRESS_REPORT_INTERVAL: 60 * 1000,
};

// Helper functions (unchanged)
function fetchLikes(lastButton = null) {
  const buttons = document.querySelectorAll('[data-testid="unlike"]');
  if (lastButton) {
    const lastButtonIndex = Array.from(buttons).findIndex(
      (button) => button === lastButton
    );
    return Array.from(buttons).slice(lastButtonIndex + 1);
  }
  return buttons;
}

function fetchTweetText(button) {
  const tweetElement = button
    .closest("article")
    .querySelector('[data-testid="tweetText"]');
  return tweetElement ? tweetElement.textContent : "No text found";
}

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function saveProgress(count) {
  localStorage.setItem("totalUnlikeCount", count);
}

function loadProgress() {
  return localStorage.getItem("totalUnlikeCount") || 0;
}

// UI setup (same as before)
const uiContainer = document.createElement("div");
uiContainer.style.position = "fixed";
uiContainer.style.top = "10px";
uiContainer.style.right = "10px";
uiContainer.style.backgroundColor = "#333";
uiContainer.style.color = "#fff";
uiContainer.style.padding = "10px";
uiContainer.style.zIndex = "9999";

const startButton = document.createElement("button");
startButton.textContent = "Start";
startButton.style.marginRight = "10px";
const stopButton = document.createElement("button");
stopButton.textContent = "Stop";
stopButton.disabled = true;
const pauseButton = document.createElement("button");
pauseButton.textContent = "Pause";
pauseButton.disabled = true;
pauseButton.style.marginRight = "10px";
const resumeButton = document.createElement("button");
resumeButton.textContent = "Resume";
resumeButton.disabled = true;
const statusText = document.createElement("div");
statusText.style.marginTop = "10px";
const errorText = document.createElement("div");
errorText.style.marginTop = "5px";
const batchText = document.createElement("div");
batchText.style.marginTop = "5px";

uiContainer.appendChild(startButton);
uiContainer.appendChild(stopButton);
uiContainer.appendChild(pauseButton);
uiContainer.appendChild(resumeButton);
uiContainer.appendChild(statusText);
uiContainer.appendChild(errorText);
uiContainer.appendChild(batchText);
document.body.appendChild(uiContainer);

let isRunning = false;
let isPaused = false;
let shouldStop = false;
let unlikeCount = 0;
let totalUnlikeCount = loadProgress();
let errorCount = 0;
let waitTime = config.BASE_WAIT_TIME;
let lastProcessedButton = null;
let batchCount = 0;

async function updateBatchStatus(timeRemaining = 0) {
  if (timeRemaining > 0) {
    batchText.textContent = `Batch cooldown: ${Math.ceil(timeRemaining / 1000)}s remaining`;
  } else {
    batchText.textContent = `Current batch: ${batchCount}/${config.BATCH_SIZE}`;
  }
}

async function performBatchPause() {
  const pauseStart = Date.now();
  console.log(`%cBatch pause for ${config.BATCH_PAUSE_TIME / 1000} seconds`, "color: yellow;");
  
  while (Date.now() - pauseStart < config.BATCH_PAUSE_TIME) {
    if (shouldStop) break;
    await updateBatchStatus(config.BATCH_PAUSE_TIME - (Date.now() - pauseStart));
    await wait(1000);
  }
  
  batchCount = 0;
  updateBatchStatus();
}

async function unlikeAll() {
  isRunning = true;
  isPaused = false;
  shouldStop = false;
  startButton.disabled = true;
  stopButton.disabled = false;
  pauseButton.disabled = false;
  resumeButton.disabled = true;
  batchCount = 0;

  const startTime = performance.now();
  let likeButtons = fetchLikes();
  let retryCount = 0;

  while (
    likeButtons.length > 0 &&
    unlikeCount < config.MAX_UNLIKES &&
    !shouldStop
  ) {
    for (const button of likeButtons) {
      if (isPaused) {
        await waitForResume();
      }

      if (shouldStop) {
        break;
      }

      try {
        // Check if we need a batch pause
        if (batchCount >= config.BATCH_SIZE) {
          await performBatchPause();
          if (shouldStop) break;
        }

        const tweetText = fetchTweetText(button).slice(0, 150);
        console.log(`Unliking tweet: "${tweetText}"`);
        button.click();
        console.log(`%cUnliked ${++unlikeCount} tweets`, "color: aqua;");
        batchCount++;
        totalUnlikeCount++;
        saveProgress(totalUnlikeCount);
        updateUI();
        updateBatchStatus();
        await wait(waitTime);

        // Adaptive timing
        if (waitTime > config.BASE_WAIT_TIME && errorCount === 0) {
          waitTime -= config.DECREMENT_WAIT;
        }

        retryCount = 0;
        lastProcessedButton = button;
      } catch (error) {
        console.error(`%cError unliking tweet: ${error}`, "color: red;");
        errorCount++;
        updateError(error);
        waitTime += config.INCREMENT_WAIT;
        retryCount++;

        if (retryCount >= config.RETRY_COUNT) {
          break;
        }
      }
    }

    if (errorCount === 0 && likeButtons.length > 0) {
      window.scrollTo(0, document.body.scrollHeight);
      await wait(3000);
      likeButtons = fetchLikes(lastProcessedButton);
    } else {
      errorCount = 0;
    }
  }

  const endTime = performance.now();
  const totalTime = (endTime - startTime) / 1000;
  console.log(`%cUnliked this session: ${unlikeCount}`, "color: aquamarine;");
  console.log(
    `%cTotal unliked with RapidUnlike = ${totalUnlikeCount}`,
    "color: aquamarine;"
  );
  console.log(
    `%cTotal time taken: ${totalTime.toFixed(2)} seconds`,
    "color: aquamarine;"
  );

  isRunning = false;
  startButton.disabled = false;
  stopButton.disabled = true;
  pauseButton.disabled = true;
  resumeButton.disabled = true;
  unlikeCount = 0;
}

function updateUI() {
  statusText.textContent = `Unliked this session: ${unlikeCount} | Total unliked with RapidUnlike: ${totalUnlikeCount}`;

  if (isRunning && !shouldStop) {
    setTimeout(updateUI, config.PROGRESS_REPORT_INTERVAL);
  }
}

function updateError(error) {
  errorText.textContent = `Error: ${error}`;
}

function waitForResume() {
  return new Promise((resolve) => {
    const checkResume = () => {
      if (!isPaused) {
        resolve();
      } else {
        setTimeout(checkResume, 1000);
      }
    };
    checkResume();
  });
}

startButton.addEventListener("click", unlikeAll);
stopButton.addEventListener("click", () => {
  shouldStop = true;
});
pauseButton.addEventListener("click", () => {
  isPaused = true;
  pauseButton.disabled = true;
  resumeButton.disabled = false;
});
resumeButton.addEventListener("click", () => {
  isPaused = false;
  pauseButton.disabled = false;
  resumeButton.disabled = true;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant