I am studying this script for a class im taking for cybersecurity. It currently is not correctly switching to a new phone number when the previous one fails, even though its supposed to. Why?

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

# ----------------------------- 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

# 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):
    """
    Reads API IDs and Hashes from the specified JSON file.

    Args:
        filename (str): Path to the JSON configuration file.

    Returns:
        tuple: Two lists containing API IDs and API Hashes respectively.

    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.")
    return api_ids, api_hashes

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')

def assign_api_credentials(api_ids, api_hashes, phone_numbers):
    """
    Assigns API credentials to phone numbers in a round-robin fashion.

    Args:
        api_ids (list): List of API IDs.
        api_hashes (list): List of API Hashes.
        phone_numbers (list): List of phone numbers.

    Returns:
        dict: A dictionary mapping each API credential to its assigned phone numbers.
    """
    assignments = {}
    num_apis = len(api_ids)
    for index, phone in enumerate(phone_numbers):
        api_index = index % num_apis
        api_key = f"{api_ids[api_index]}_{api_hashes[api_index]}"
        if api_key not in assignments:
            assignments[api_key] = {
                'api_id': api_ids[api_index],
                'api_hash': api_hashes[api_index],
                'phone_numbers': []
            }
        assignments[api_key]['phone_numbers'].append(phone)
    return assignments

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)
        return None
    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 sending message to '{user['username']}' using API ID {client.api_id} and phone {phone_number}.")
        logging.error(f"Details: {e}")  # Log exception details
    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)
        return False

    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
        return False

    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 API credentials
    try:
        api_ids, api_hashes = read_api_config(API_CONFIG_FILE)
    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

    # Assign API credentials to phone numbers
    assignments = assign_api_credentials(api_ids, api_hashes, phone_numbers)

    # Log the assignments (optional, can be commented out for privacy)
    for api_key, creds in assignments.items():
        api_id = creds['api_id']
        api_hash = creds['api_hash']
        phones = creds['phone_numbers']
        logging.info(f"Assigning API ID {api_id} to API Hash {api_hash} with phone numbers: {phones}")

    # 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.")

    # Iterate through each API credential and their assigned phone numbers
    for api_key, creds in assignments.items():
        api_id = creds['api_id']
        api_hash = creds['api_hash']
        phone_numbers_assigned = creds['phone_numbers']

        # Track if any phone number under this API credential is still active
        active_phone_numbers = phone_numbers_assigned.copy()

        for phone_number in phone_numbers_assigned:
            if phone_number in blocked_numbers:
                logging.info(f"Skipping blocked phone number {phone_number}.")
                continue

            client = await get_or_create_client(phone_number, creds)
            if client is None:
                logging.error(f"Skipping phone number {phone_number} due to client creation failure.")
                blocked_numbers.add(phone_number)
                # Update blocked_numbers.json
                try:
                    with open(BLOCKED_FILE, 'w') as f:
                        json.dump(list(blocked_numbers), f)
                    logging.info(f"Added {phone_number} to blocked numbers.")
                except Exception as e:
                    logging.error(f"Failed to update '{BLOCKED_FILE}': {e}")
                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
                    button_sent = await send_button_message(client, user, phone_number)
                    
                    # Forward the original message
                    forwarded_message = await forward_to_user(client, user, source_chat_entity, message_id, phone_number)
                    
                    if forwarded_message:
                        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:
                logging.error(f"Encountered {type(e).__name__} for phone {phone_number} using API ID {api_id}: {e}")
                logging.info(f"Marking phone number {phone_number} as blocked and moving to the next phone number.")
                blocked_numbers.add(phone_number)
                # Update blocked_numbers.json
                try:
                    with open(BLOCKED_FILE, 'w') as f:
                        json.dump(list(blocked_numbers), f)
                    logging.info(f"Added {phone_number} to blocked numbers.")
                except Exception as e:
                    logging.error(f"Failed to update '{BLOCKED_FILE}': {e}")
                continue
            except Exception as e:
                logging.error(f"An unexpected error occurred with phone number {phone_number}: {e}")
                continue
            finally:
                await client.disconnect()

        logging.info(f"Finished processing API ID {api_id}. Moving to the next API credential.")

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

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

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



this is the output


2024-10-07 04:32:18 - ERROR - Peer flood error when sending message to 'jravee' using API ID 25294599 and phone <PRESIDIO_ANONYMIZED_PHONE_NUMBER>.
2024-10-07 04:32:18 - ERROR - Details: Too many requests (caused by SendMessageRequest)
2024-10-07 04:32:18 - ERROR - Peer flood error when sending message to 'jravee' using API ID 25294599 and phone <PRESIDIO_ANONYMIZED_PHONE_NUMBER>.
2024-10-07 04:32:18 - ERROR - Details: Too many requests (caused by ForwardMessagesRequest)
2024-10-07 04:32:18 - INFO - Waiting 2 seconds before sending the next message.
2024-10-07 04:32:20 - ERROR - Peer flood error when sending message to 'redmarkz' using API ID 25294599 and phone <PRESIDIO_ANONYMIZED_PHONE_NUMBER>.
2024-10-07 04:32:20 - ERROR - Details: Too many requests (caused by SendMessageRequest)
2024-10-07 04:32:20 - ERROR - Peer flood error when sending message to 'redmarkz' using API ID 25294599 and phone <PRESIDIO_ANONYMIZED_PHONE_NUMBER>.
2024-10-07 04:32:20 - ERROR - Details: Too many requests (caused by ForwardMessagesRequest)
2024-10-07 04:32:20 - INFO - Waiting 2 seconds before sending the next message.
2024-10-07 04:32:21 - INFO - Disconnecting from 149.154.175.58:443/TcpFull...
2024-10-07 04:32:21 - INFO - Disconnection from 149.154.175.58:443/TcpFull complete!