diff --git a/last_line.txt b/last_line.txt index 3f10ffe..d9f5f71 100644 --- a/last_line.txt +++ b/last_line.txt @@ -1 +1 @@ -15 \ No newline at end of file +130579 \ No newline at end of file diff --git a/main.py b/main.py index e49d1f9..0375b1b 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import time import os import logging -from typing import List, Tuple +from typing import List from dotenv import load_dotenv # Load environment variables from .env file @@ -11,7 +11,7 @@ # Configure logging logging.basicConfig( - level=logging.INFO, + level=logging.INFO, # Set to INFO for general logs; use DEBUG for more verbosity format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler("follow_users.log"), @@ -28,8 +28,8 @@ logging.error("GitHub token not found. Please set GITHUB_TOKEN in the environment variables.") exit(1) -# Semaphore to limit concurrent requests -SEM = asyncio.Semaphore(5) # Adjust the number based on your needs and testing +# Semaphore to limit concurrent requests (set to 1 for sequential processing) +SEM = asyncio.Semaphore(1) # Function to read usernames from a file def read_usernames(file_path: str) -> List[str]: @@ -72,47 +72,25 @@ def write_last_line(file_path: str, line_number: int) -> None: logging.exception(f"An error occurred while writing to '{file_path}': {e}") # Asynchronous function to follow a user on GitHub -async def follow_user(session: aiohttp.ClientSession, username: str) -> Tuple[int, str]: +async def follow_user(session: aiohttp.ClientSession, username: str, line_number: int) -> None: url = f'https://api.github.com/user/following/{username}' - async with SEM: # Limit concurrency + async with SEM: # Ensure sequential processing try: async with session.put(url) as response: status = response.status text = await response.text() - # Log rate limit headers - rate_limit_remaining = response.headers.get('X-RateLimit-Remaining') - rate_limit_reset = response.headers.get('X-RateLimit-Reset') - if rate_limit_remaining and rate_limit_reset: - logging.debug(f"Rate Limit Remaining: {rate_limit_remaining}") - logging.debug(f"Rate Limit Reset Time: {rate_limit_reset}") + if status == 204: + logging.info(f"Line {line_number + 1}: Successfully followed '{username}'.") + elif status == 404: + logging.warning(f"Line {line_number + 1}: User '{username}' not found.") + elif status == 403 or status == 429: + logging.error(f"Line {line_number + 1}: Rate limit exceeded or forbidden access.") + else: + logging.error(f"Line {line_number + 1}: Failed to follow '{username}': {status}, {text}") - return status, text except Exception as e: - logging.exception(f"Error following user '{username}': {e}") - return 0, str(e) - -# Function to handle rate limiting based on GitHub's response headers -async def handle_rate_limit(headers: dict): - rate_limit_remaining = headers.get('X-RateLimit-Remaining') - rate_limit_reset = headers.get('X-RateLimit-Reset') - - if rate_limit_remaining is not None and rate_limit_reset is not None: - rate_limit_remaining = int(rate_limit_remaining) - rate_limit_reset = int(rate_limit_reset) - - if rate_limit_remaining == 0: - current_time = int(time.time()) - sleep_duration = rate_limit_reset - current_time + 5 # Add a buffer of 5 seconds - if sleep_duration > 0: - reset_time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(rate_limit_reset)) - logging.warning(f"Rate limit reached. Sleeping until {reset_time_str} ({sleep_duration} seconds).") - await asyncio.sleep(sleep_duration) - -# Function to check and handle rate limits after each request -async def check_rate_limit_after_request(response: aiohttp.ClientResponse): - headers = response.headers - await handle_rate_limit(headers) + logging.exception(f"Line {line_number + 1}: Error following user '{username}': {e}") # Main asynchronous function async def main(): @@ -129,27 +107,15 @@ async def main(): async with aiohttp.ClientSession(headers=headers) as session: for i, username in enumerate(usernames[last_line:], start=last_line): - status_code, response_text = await follow_user(session, username) - - if status_code == 204: - logging.info(f"Line {i + 1}: Successfully followed '{username}'.") - write_last_line(LAST_LINE_FILE, i + 1) - elif status_code == 404: - logging.warning(f"Line {i + 1}: User '{username}' not found.") - write_last_line(LAST_LINE_FILE, i + 1) - elif status_code == 403: - if 'rate limit' in response_text.lower(): - logging.error(f"Line {i + 1}: Rate limit exceeded.") - # Extract headers from the last response - await handle_rate_limit(session._connector._session.headers) - else: - logging.error(f"Line {i + 1}: Forbidden access when trying to follow '{username}'.") - else: - logging.error(f"Line {i + 1}: Failed to follow '{username}': {status_code}, {response_text}") + await follow_user(session, username, i) - # Optional: Dynamic sleep based on remaining rate limit - # This example uses a fixed sleep; you can adjust it based on rate limits - await asyncio.sleep(1) # Adjust as needed + # Wait for 10 seconds before processing the next user + if i < total_usernames - 1: + #logging.info("Waiting for 10 seconds before following the next user...") + await asyncio.sleep(10) + + # Update the last processed line + write_last_line(LAST_LINE_FILE, i + 1) logging.info("Finished processing all usernames.") @@ -160,5 +126,3 @@ async def main(): logging.info("Script interrupted by user.") except Exception as e: logging.exception(f"An unexpected error occurred: {e}") - -###test! \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dafb249..3f34a1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ requests~=2.31.0 beautifulsoup4~=4.12.3 aiohttp -python-dotenv \ No newline at end of file +python-dotenv +aiolimiter \ No newline at end of file