I am studying this script for a class im taking for cybersecurity.

 It shouldn't block numbers with this error, only if they get the peerflood error.

2024-10-07 04:56:46 - ERROR - Failed to create session for <PRESIDIO_ANONYMIZED_PHONE_NUMBER> using API ID 22943488: The api_id/api_hash combination is invalid (caused by SendCodeRequest)
2024-10-07 04:56:46 - INFO - Added <PRESIDIO_ANONYMIZED_PHONE_NUMBER> to blocked numbers.

why does it do that? This is the script

import asyncio
import json
import logging
import os
from telethon import TelegramClient, errors, Button
from collections import defaultdict

# ----------------------------- Configuration -----------------------------

# Configure logging
logging.basicConfig(
    filename='bot_log.log',  # Log file name
    level=logging.INFO,       # Log level
    format='%(asctime)s - %(levelname)s - %(message)s',  # Log format
    datefmt='%Y-%m-%d %H:%M:%S'  # Date format
)

# Configuration Constants
GROUP_LINK = 'https://t.me/rocketproxyupdates'  # Telegram group link
BUTTON_TEXT = 'Join Our Telegram Group'         # Text displayed on the button

USER_LIST_FILE = 'targets.json'         # JSON file containing target users
SENT_FILE = 'sent.json'                 # JSON file to track sent users
API_CONFIG_FILE = 'clean_accounts.json' # JSON file containing API IDs and Hashes
PHONE_NUMBERS_FILE = 'phone_numbers.json' # JSON file containing phone numbers
BLOCKED_FILE = 'blocked_numbers.json'   # JSON file to track blocked phone numbers
BLOCKED_API_FILE = 'blocked_api_ids.json'  # JSON file to track blocked API IDs/hashes

# Directory to store session files
SESSION_DIR = 'sessions'

# Create the sessions directory if it doesn't exist
if not os.path.exists(SESSION_DIR):
    os.makedirs(SESSION_DIR)

# Rate Limiting Constants
WAIT_TIME_BASE = 2    # Base wait time in seconds between messages
WAIT_TIME_FACTOR = 2  # Additional wait time factor based on errors
MAX_WAIT_TIME = 3600    # Maximum wait time in seconds (1 hour)

# Source chat information
FORWARD_SOURCE_CHAT_ID = 'rocketproxyupdates'  # Telegram chat to fetch messages from

# ----------------------------- Functions -----------------------------

def read_api_config(filename, blocked_api_ids):
    """
    Reads API IDs and Hashes from the specified JSON file, excluding blocked ones.

    Args:
        filename (str): Path to the JSON configuration file.
        blocked_api_ids (set): Set of blocked API IDs/hashes.

    Returns:
        list: List of dictionaries containing 'api_id', 'api_hash', and 'api_key'.

    Raises:
        ValueError: If the number of API IDs and Hashes do not match.
    """
    with open(filename, 'r') as f:
        data = json.load(f)
    api_ids = data.get('API_ID_LIST', [])
    api_hashes = data.get('API_HASH_LIST', [])
    if len(api_ids) != len(api_hashes):
        logging.error("API_ID_LIST and API_HASH_LIST must be of the same length.")
        raise ValueError("API_ID_LIST and API_HASH_LIST must be of the same length.")
    api_credentials = []
    for api_id, api_hash in zip(api_ids, api_hashes):
        api_key = f"{api_id}_{api_hash}"
        if api_key not in blocked_api_ids:
            api_credentials.append({'api_id': api_id, 'api_hash': api_hash, 'api_key': api_key})
    return api_credentials

def read_phone_numbers(filename, blocked_numbers):
    """
    Reads phone numbers from the specified JSON file, excluding blocked numbers.

    Args:
        filename (str): Path to the JSON file containing phone numbers.
        blocked_numbers (set): Set of blocked phone numbers.

    Returns:
        list: A list of phone numbers in international format.
    """
    with open(filename, 'r') as f:
        phone_numbers = json.load(f)
    # Exclude blocked numbers
    return [phone for phone in phone_numbers if phone not in blocked_numbers]

def sanitize_phone_number(phone_number):
    """
    Sanitizes the phone number to create a valid filename.

    Args:
        phone_number (str): Phone number in international format.

    Returns:
        str: Sanitized phone number suitable for filenames.
    """
    return phone_number.replace('+', '').replace(' ', '').replace('-', '').replace('(', '').replace(')', '')

def get_session_file(phone_number):
    """
    Generates the session file path for a given phone number.

    Args:
        phone_number (str): Phone number in international format.

    Returns:
        str: Path to the session file.
    """
    sanitized_number = sanitize_phone_number(phone_number)
    return os.path.join(SESSION_DIR, f'session_{sanitized_number}.session')

async def get_or_create_client(phone_number, credentials):
    """
    Creates a new Telegram client or loads an existing session for a given phone number.

    Args:
        phone_number (str): Phone number in international format.
        credentials (dict): Dictionary containing 'api_id' and 'api_hash'.

    Returns:
        TelegramClient or None: The Telegram client instance or None if failed.
    """
    session_file = get_session_file(phone_number)
    api_id = credentials['api_id']
    api_hash = credentials['api_hash']
    client = TelegramClient(session_file, api_id, api_hash)

    try:
        await client.connect()
    except Exception as e:
        logging.error(f"Failed to connect client for {phone_number} using API ID {api_id}: {e}")
        return None

    if await client.is_user_authorized():
        logging.info(f"Loaded existing session for {phone_number} using API ID {api_id}")
        return client
    else:
        try:
            await client.send_code_request(phone_number)
            code = input(f"Enter the authentication code for {phone_number}: ")
            await client.sign_in(phone_number, code)
            logging.info(f"Created new session for {phone_number} using API ID {api_id}")
            return client
        except errors.PhoneCodeInvalidError:
            logging.error(f"Invalid authentication code for {phone_number}.")
        except errors.PhoneCodeExpiredError:
            logging.error(f"Authentication code expired for {phone_number}.")
        except errors.PhoneNumberBannedError:
            logging.error(f"Phone number {phone_number} is banned.")
        except Exception as e:
            logging.error(f"Failed to create session for {phone_number} using API ID {api_id}: {e}")
    return None

async def forward_to_user(client, user, source_chat_entity, message_id, phone_number):
    """
    Forwards a message from the source chat to the target user.

    Args:
        client (TelegramClient): The Telegram client instance.
        user (dict): Dictionary containing user information (e.g., {'username': 'example_user'}).
        source_chat_entity: The source chat entity from which to forward messages.
        message_id (int): The ID of the message to forward.
        phone_number (str): The phone number associated with the client.

    Returns:
        Message or None: The forwarded message or None if failed.
    """
    try:
        forwarded_message = await client.forward_messages(
            entity=user['username'],
            messages=message_id,
            from_peer=source_chat_entity
        )
        logging.info(f"Forwarded message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}")
        return forwarded_message
    except errors.FloodWaitError as e:
        wait_time = max(WAIT_TIME_BASE, e.seconds + WAIT_TIME_FACTOR)
        logging.warning(f"Flood wait error for phone {phone_number} on API ID {client.api_id}: Waiting for {wait_time} seconds before retrying.")
        await asyncio.sleep(wait_time)
        raise e  # Re-raise the exception to be caught in main()
    except errors.UsernameNotOccupiedError:
        logging.warning(f"No user has the username '{user['username']}' for phone {phone_number} using API ID {client.api_id}.")
        return None
    except errors.PeerFloodError as e:
        logging.error(f"Peer flood error when forwarding message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}.")
        logging.error(f"Details: {e}")  # Log exception details
        raise e  # Re-raise the exception to be caught in main()
    except Exception as e:
        logging.error(f"Failed to forward message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}. Error: {e}")
        return None

async def send_button_message(client, user, phone_number, reply_to_msg_id=None):
    """
    Sends a message with a button to the target user.

    Args:
        client (TelegramClient): The Telegram client instance.
        user (dict): Dictionary containing user information (e.g., {'username': 'example_user'}).
        phone_number (str): The phone number associated with the client.
        reply_to_msg_id (int, optional): The message ID to reply to. Defaults to None.

    Returns:
        bool: True if the message was sent successfully, False otherwise.
    """
    try:
        message_text = "hey bro check this out"
        
        # Send the message to the user with a button
        await client.send_message(
            entity=user['username'],
            message=message_text,
            buttons=[Button.url(BUTTON_TEXT, GROUP_LINK)]
        )
        
        logging.info(f"Sent message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}")
        await asyncio.sleep(1)
        return True

    except errors.FloodWaitError as e:
        wait_time = max(WAIT_TIME_BASE, e.seconds + WAIT_TIME_FACTOR)
        logging.warning(f"Flood wait error for phone {phone_number} on API ID {client.api_id}: Waiting for {wait_time} seconds before retrying.")
        await asyncio.sleep(wait_time)
        raise e  # Re-raise the exception to be caught in main()

    except errors.UsernameNotOccupiedError:
        logging.warning(f"No user has the username '{user['username']}' for phone {phone_number} using API ID {client.api_id}.")
        return False

    except errors.PeerFloodError as e:
        logging.error(f"Peer flood error when sending message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}.")
        logging.error(f"Details: {e}")  # Log exception details
        raise e  # Re-raise the exception to be caught in main()

    except Exception as e:
        logging.error(f"Failed to send message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}. Error: {e}")
        return False

async def get_source_chat_and_message(client, phone_number):
    """
    Retrieves the source chat entity and the latest message ID.

    Args:
        client (TelegramClient): The Telegram client instance.
        phone_number (str): The phone number associated with the client.

    Returns:
        tuple: Source chat entity and the latest message ID.
    """
    try:
        source_chat = await client.get_entity(FORWARD_SOURCE_CHAT_ID)
    except Exception as e:
        logging.error(f"Error resolving source chat '{FORWARD_SOURCE_CHAT_ID}' for phone {phone_number} using API ID {client.api_id}: {e}")
        return None, None

    try:
        message = await client.get_messages(source_chat, limit=1)
        if not message:
            logging.warning(f"No messages found in the source chat '{FORWARD_SOURCE_CHAT_ID}' for phone {phone_number} using API ID {client.api_id}.")
            return source_chat, None
        return source_chat, message[0].id
    except Exception as e:
        logging.error(f"Error fetching messages from '{FORWARD_SOURCE_CHAT_ID}' for phone {phone_number} using API ID {client.api_id}: {e}")
        return source_chat, None

# ----------------------------- Main Function -----------------------------

async def main():
    """
    The main function that orchestrates reading configurations, assigning API credentials,
    creating/loading clients, sending messages, forwarding messages, and handling rate limits.
    """
    # Load blocked numbers
    blocked_numbers = set()
    if os.path.exists(BLOCKED_FILE):
        try:
            with open(BLOCKED_FILE, 'r') as f:
                blocked_numbers = set(json.load(f))
            logging.info(f"Loaded {len(blocked_numbers)} blocked phone numbers.")
        except Exception as e:
            logging.error(f"Failed to read blocked numbers from '{BLOCKED_FILE}': {e}")
            return
    else:
        logging.info("No blocked phone numbers found. Starting fresh.")

    # Load blocked API IDs/hashes
    blocked_api_ids = set()
    if os.path.exists(BLOCKED_API_FILE):
        try:
            with open(BLOCKED_API_FILE, 'r') as f:
                blocked_api_ids = set(json.load(f))
            logging.info(f"Loaded {len(blocked_api_ids)} blocked API IDs/hashes.")
        except Exception as e:
            logging.error(f"Failed to read blocked API IDs/hashes from '{BLOCKED_API_FILE}': {e}")
            return
    else:
        logging.info("No blocked API IDs/hashes found. Starting fresh.")

    # Load API credentials, excluding blocked ones
    try:
        api_credentials = read_api_config(API_CONFIG_FILE, blocked_api_ids)
        logging.info(f"Loaded {len(api_credentials)} API credentials after excluding blocked ones.")
    except Exception as e:
        logging.error(f"Failed to read API configuration: {e}")
        return

    # Load phone numbers, excluding blocked ones
    try:
        phone_numbers = read_phone_numbers(PHONE_NUMBERS_FILE, blocked_numbers)
        logging.info(f"Loaded {len(phone_numbers)} phone numbers after excluding blocked numbers.")
    except Exception as e:
        logging.error(f"Failed to read phone numbers: {e}")
        return

    # Load target users
    try:
        with open(USER_LIST_FILE, 'r') as f:
            users = json.load(f)
        logging.info(f"Loaded {len(users)} target users.")
    except Exception as e:
        logging.error(f"Failed to read target users from '{USER_LIST_FILE}': {e}")
        return

    # Load already sent users to prevent duplicates
    sent_users = set()
    if os.path.exists(SENT_FILE):
        try:
            with open(SENT_FILE, 'r') as f:
                sent_users = set(json.load(f))
            logging.info(f"Loaded {len(sent_users)} already sent users.")
        except Exception as e:
            logging.error(f"Failed to read sent users from '{SENT_FILE}': {e}")
            return
    else:
        logging.info("No sent users found. Starting fresh.")

    # Initialize error tracking
    error_counts_phone_number = defaultdict(lambda: defaultdict(set))
    error_counts_api = defaultdict(lambda: defaultdict(set))

    # While there are API credentials to try
    available_api_credentials = api_credentials.copy()

    while available_api_credentials:
        creds = available_api_credentials.pop(0)
        api_id = creds['api_id']
        api_hash = creds['api_hash']
        api_key = creds['api_key']

        logging.info(f"Processing with API ID {api_id} and API Hash {api_hash}")

        # Start from the beginning of the phone number list for each new API credential
        phone_numbers_to_try = phone_numbers.copy()

        phone_number_index = 0
        api_blocked = False

        while phone_number_index < len(phone_numbers_to_try):
            phone_number = phone_numbers_to_try[phone_number_index]
            phone_number_index += 1

            # Skip blocked phone numbers
            if phone_number in blocked_numbers:
                continue

            client = await get_or_create_client(phone_number, {'api_id': api_id, 'api_hash': api_hash})

            if client is None:
                # Phone number is likely blocked or failed to connect
                blocked_numbers.add(phone_number)
                with open(BLOCKED_FILE, 'w') as f:
                    json.dump(list(blocked_numbers), f)
                logging.info(f"Added {phone_number} to blocked numbers.")
                continue

            try:
                # Retrieve the latest message from the source chat
                source_chat_entity, message_id = await get_source_chat_and_message(client, phone_number)
                if not source_chat_entity or not message_id:
                    logging.warning(f"Skipping phone number '{phone_number}' due to source chat or message retrieval failure.")
                    continue

                # Iterate through target users
                for user in users:
                    if user['username'] in sent_users:
                        continue  # Skip already sent users

                    # Send button message
                    await send_button_message(client, user, phone_number)

                    # Forward the original message
                    await forward_to_user(client, user, source_chat_entity, message_id, phone_number)

                    sent_users.add(user['username'])
                    # Update sent.json
                    try:
                        with open(SENT_FILE, 'w') as f:
                            json.dump(list(sent_users), f)
                        logging.info(f"Added '{user['username']}' to sent users.")
                    except Exception as e:
                        logging.error(f"Failed to update '{SENT_FILE}': {e}")

                    # Wait to respect rate limits
                    logging.info(f"Waiting {WAIT_TIME_BASE} seconds before sending the next message.")
                    await asyncio.sleep(WAIT_TIME_BASE)

            except (errors.PeerFloodError, errors.FloodWaitError) as e:
                error_type = type(e).__name__

                # Update error counts
                error_counts_phone_number[phone_number][error_type].add(api_key)
                error_counts_api[api_key][error_type].add(phone_number)

                # Check if we should block the phone number
                if len(error_counts_phone_number[phone_number][error_type]) >= 2:
                    logging.info(f"Phone number {phone_number} failed with error {error_type} under multiple API IDs. Blocking phone number.")
                    blocked_numbers.add(phone_number)
                    with open(BLOCKED_FILE, 'w') as f:
                        json.dump(list(blocked_numbers), f)
                    logging.info(f"Added {phone_number} to blocked numbers.")

                # Check if we should block the API credential
                if len(error_counts_api[api_key][error_type]) >= 2:
                    logging.info(f"API ID/Hash {api_key} failed with error {error_type} under multiple phone numbers. Blocking API ID/Hash.")
                    blocked_api_ids.add(api_key)
                    with open(BLOCKED_API_FILE, 'w') as f:
                        json.dump(list(blocked_api_ids), f)
                    api_blocked = True
                    break  # Exit the phone number loop to move to the next API credential

                continue  # Move to the next phone number

            except Exception as e:
                logging.error(f"An unexpected error occurred with phone number {phone_number}: {e}")
                continue

            finally:
                await client.disconnect()

        # If API credential is blocked, move to the next one
        if api_blocked:
            # Remove blocked API credential from the available list
            api_credentials = [cred for cred in api_credentials if cred['api_key'] not in blocked_api_ids]
            available_api_credentials = [cred for cred in available_api_credentials if cred['api_key'] not in blocked_api_ids]
            continue  # Proceed to next API credential

    logging.info("All API credentials have been processed.")

# ----------------------------- Entry Point -----------------------------

if __name__ == '__main__':
    asyncio.run(main())