Repository: theAbdoSabbagh/UnlimitedGPT (language: python)

Issue:
headless flag prevents code from running
this is the code i currently have:
```py
import os
from UnlimitedGPT import ChatGPT

def cls():
    os.system("cls" if os.name == "nt" else "clear")
cls()

session_token = input("Enter your session token:\nThis data will be stored locally for convenience.\n> ")
cls()
cnv = input("Enter your conversation ID:\nYou can obtain this by opening a conversation on the ChatGPT client and copying everything after the /c/ in the URL.\n> ")
cls()
msg = input("Enter message:\n> ")
cls()

api = ChatGPT(
    session_token,
    proxy=None,
    disable_moderation=True,
    conversation_id=cnv,
    verbose=True,
    input_mode="SLOW",
    input_delay=0.2,
    headless="True"
)

api.clear_conversations()
message = api.send_message(msg)
print(message.response)
```

whenever ran, it initializing the browser then throws this error:
```
[_init_browser] Initializing browser...
Traceback (most recent call last):
  File "C:\Users\Noah Coodin\Downloads\coodingpt\main.py", line 15, in <module>
    api = ChatGPT(
          ^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 77, in __init__
    self._init_browser()
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 158, in _init_browser
    raise e
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 154, in _init_browser
    self.driver = ChatGPTDriver(options=options, headless=self._headless)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\internal\driver.py", line 11, in __init__
    super().__init__(options=options, headless=headless)
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\undetected_chromedriver\__init__.py", line 386, in __init__
    if self.patcher.version_main < 108:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: '<' not supported between instances of 'NoneType' and 'int'
```
im on python 3.11.3
```



Discussion / hints:
Thanks for reporting the issue, I'll look into it in a few minutes and get back to you. At the meantime, could you answer the following questions?

1. What OS are you on?
2. Are you running the latest version of UnlimitedGPT?
3. Does the code work when headless mode is not set to True?
1. Windows 10 LTSC Build 19044.2965
2. Yes, if the latest version is available on pip as that's what I used to install it.
3. Yes, but with issues. If there is no conversation ID, the message is never sent. If there is an error finding the conversation with the ID provided, the message doesn't send and it doesn't even close the popups, I believe this is because it detects the red error that shows on top of the screen as the popup, closes that and thinks it did it's job. Whenever it does start typing the message, chromedriver crashes instantly. I can get back to you with the error when that happens if you'd like.
Also I'm now realizing in the code provided I have true in quotes, but I tried no quotes and the same error occurred.
Log of trying to use with a valid conversation ID:
```
[_init_browser] Initializing browser...
[_init_browser] Restoring session_token...
[_init_browser] Blocking moderation...
[_init_browser] Ensuring Cloudflare cookies...
[_ensure_cf] Opening new tab...
[_ensure_cf] Getting Cloudflare challenge...
[_ensure_cf] Cloudflare challenge passed
[_ensure_cf] Validating authorization...
[_ensure_cf] Authorization is valid
[_ensure_cf] Closing tab...
[_init_browser] Opening chat page...
[_check_blocking_elements] Looking for blocking elements...
[_keep_alive] Updating session...
[clear_conversations] Clearing all conversations...
Traceback (most recent call last):
  File "C:\Users\Noah Coodin\Downloads\coodingpt\main.py", line 25, in <module>
    api.clear_conversations()
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 383, in clear_conversations
    clear_conversations.click() # Confirm button is the same path
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webelement.py", line 94, in click
    self._execute(Command.CLICK_ELEMENT)
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webelement.py", line 403, in _execute
    return self._parent.execute(command, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 440, in execute
    self.error_handler.check_response(response)
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 245, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: stale element not found
  (Session info: chrome=114.0.5735.91)
Stacktrace:
Backtrace:
        GetHandleVerifier [0x00CE8893+48451]
        (No symbol) [0x00C7B8A1]
        (No symbol) [0x00B85058]
        (No symbol) [0x00B883F1]
        (No symbol) [0x00B89631]
        (No symbol) [0x00B896D0]
        (No symbol) [0x00BB1879]
        (No symbol) [0x00BA98FD]
        (No symbol) [0x00BCA2BC]
        (No symbol) [0x00BA9586]
        (No symbol) [0x00BCA614]
        (No symbol) [0x00BDC482]
        (No symbol) [0x00BCA0B6]
        (No symbol) [0x00BA7E08]
        (No symbol) [0x00BA8F2D]
        GetHandleVerifier [0x00F48E3A+2540266]
        GetHandleVerifier [0x00F88959+2801161]
        GetHandleVerifier [0x00F8295C+2776588]
        GetHandleVerifier [0x00D72280+612144]
        (No symbol) [0x00C84F6C]
        (No symbol) [0x00C811D8]
        (No symbol) [0x00C812BB]
        (No symbol) [0x00C74857]
        BaseThreadInitThunk [0x75F100C9+25]
        RtlGetAppContainerNamedObjectPath [0x77047B4E+286]
        RtlGetAppContainerNamedObjectPath [0x77047B1E+238]

[__del__] Closing browser...
```
Yeah I just noticed the headless mode in your code having the value set as `str`, but I'm currently downloading a new version of chrome as apparently mine is not supported anymore by the newest `undetected-chromedriver` library. Once the installation is done I'll look into the bug and try to fix it.
> Log of trying to use with a valid conversation ID:
> 
> ```
> [_init_browser] Initializing browser...
> [_init_browser] Restoring session_token...
> [_init_browser] Blocking moderation...
> [_init_browser] Ensuring Cloudflare cookies...
> [_ensure_cf] Opening new tab...
> [_ensure_cf] Getting Cloudflare challenge...
> [_ensure_cf] Cloudflare challenge passed
> [_ensure_cf] Validating authorization...
> [_ensure_cf] Authorization is valid
> [_ensure_cf] Closing tab...
> [_init_browser] Opening chat page...
> [_check_blocking_elements] Looking for blocking elements...
> [_keep_alive] Updating session...
> [clear_conversations] Clearing all conversations...
> Traceback (most recent call last):
>   File "C:\Users\Noah Coodin\Downloads\coodingpt\main.py", line 25, in <module>
>     api.clear_conversations()
>   File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 383, in clear_conversations
>     clear_conversations.click() # Confirm button is the same path
>     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webelement.py", line 94, in click
>     self._execute(Command.CLICK_ELEMENT)
>   File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webelement.py", line 403, in _execute
>     return self._parent.execute(command, params)
>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 440, in execute
>     self.error_handler.check_response(response)
>   File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 245, in check_response
>     raise exception_class(message, screen, stacktrace)
> selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: stale element not found
>   (Session info: chrome=114.0.5735.91)
> Stacktrace:
> Backtrace:
>         GetHandleVerifier [0x00CE8893+48451]
>         (No symbol) [0x00C7B8A1]
>         (No symbol) [0x00B85058]
>         (No symbol) [0x00B883F1]
>         (No symbol) [0x00B89631]
>         (No symbol) [0x00B896D0]
>         (No symbol) [0x00BB1879]
>         (No symbol) [0x00BA98FD]
>         (No symbol) [0x00BCA2BC]
>         (No symbol) [0x00BA9586]
>         (No symbol) [0x00BCA614]
>         (No symbol) [0x00BDC482]
>         (No symbol) [0x00BCA0B6]
>         (No symbol) [0x00BA7E08]
>         (No symbol) [0x00BA8F2D]
>         GetHandleVerifier [0x00F48E3A+2540266]
>         GetHandleVerifier [0x00F88959+2801161]
>         GetHandleVerifier [0x00F8295C+2776588]
>         GetHandleVerifier [0x00D72280+612144]
>         (No symbol) [0x00C84F6C]
>         (No symbol) [0x00C811D8]
>         (No symbol) [0x00C812BB]
>         (No symbol) [0x00C74857]
>         BaseThreadInitThunk [0x75F100C9+25]
>         RtlGetAppContainerNamedObjectPath [0x77047B4E+286]
>         RtlGetAppContainerNamedObjectPath [0x77047B1E+238]
> 
> [__del__] Closing browser...
> ```

Could you try again but with headless mode set to False?
Alright so this error should be fairly simple to fix. It is being raised because the code is trying to click the elements without checking if they are "clickable". I can add a check before clicking which should fix it. I'll work on that now.
Not near my computer at the moment, I’ll get back to you once I’m back
home. If you could please reply to this issue if you push an update that’d
be greatly appreciated.

On Wed, May 31, 2023 at 12:05 PM Sxvxge ***@***.***> wrote:

> Alright so this error should be fairly simple to fix. It is being raised
> because the code is trying to click the elements without checking if they
> are "clickable". I can add a check before clicking which should fix it.
> I'll work on that now.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/Sxvxgee/UnlimitedGPT/issues/1#issuecomment-1570515286>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AQRHZHMRZ5WQGEPRRNYTDILXI5T3VANCNFSM6AAAAAAYVUWZRI>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>

I'm currently fixing the bug, once the update is pushed I'll make sure to let you know.
I believe the issue has been fixed now. Please do let me know once you test your code once again after updating the library.

You can update the library using the following command:
```sh
pip install UnlimitedGPT -U
```
It's launching chrome and authenticating successfully, but hangs when sending the message on both SLOW and FAST mode for me.

This is what it says after timing out while waiting for the message to send:
```
[_init_browser] Initializing browser...
[_init_browser] Initializing browser...
[_init_browser] Restoring session_token...
[_init_browser] Blocking moderation...        
[_init_browser] Ensuring Cloudflare cookies...
[_ensure_cf] Opening new tab...
[_ensure_cf] Getting Cloudflare challenge...
[_ensure_cf] Cloudflare challenge passed
[_ensure_cf] Validating authorization...
[_ensure_cf] Authorization is valid
[_ensure_cf] Closing tab...
[_init_browser] Opening chat page...
[_check_blocking_elements] Looking for blocking elements...
[_keep_alive] Updating session...
[send_message] Sending message with mode SLOW with 0.2 delay...
[_keep_alive] Updating session...
Traceback (most recent call last):
  File "C:\Users\Noah Coodin\Downloads\coodingpt\main.py", line 26, in <module>
    message = api.send_message(msg)
              ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 304, in send_message
    textbox = WebDriverWait(self.driver, 60).until(
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\support\wait.py", line 95, in until
    raise TimeoutException(message, screen, stacktrace)
selenium.common.exceptions.TimeoutException: Message:
Stacktrace:
Backtrace:
        GetHandleVerifier [0x00248893+48451]
        (No symbol) [0x001DB8A1]
        (No symbol) [0x000E5058]
        (No symbol) [0x00110467]
        (No symbol) [0x0011069B]
        (No symbol) [0x0013DD92]
        (No symbol) [0x0012A304]
        (No symbol) [0x0013C482]
        (No symbol) [0x0012A0B6]
        (No symbol) [0x00107E08]
        (No symbol) [0x00108F2D]
        GetHandleVerifier [0x004A8E3A+2540266]
        GetHandleVerifier [0x004E8959+2801161]
        GetHandleVerifier [0x004E295C+2776588]
        GetHandleVerifier [0x002D2280+612144]
        (No symbol) [0x001E4F6C]
        (No symbol) [0x001E11D8]
        (No symbol) [0x001E12BB]
        (No symbol) [0x001D4857]
        BaseThreadInitThunk [0x75F100C9+25]
        RtlGetAppContainerNamedObjectPath [0x77047B4E+286]
        RtlGetAppContainerNamedObjectPath [0x77047B1E+238]
        (No symbol) [0x00000000]

[__del__] Closing browser...
```
PS: I used no conversation ID for this, I will now try using one and reply to this with results.
Could you show me your code? Also did you update the library?
Update: It works flawlessly when provided a conversation ID, however if the variable is left blank or never specified it will just hang while waiting to send the message.

Yes, I updated the library. I am now on version 0.1.1.

Here is my code:
```py
import os
from UnlimitedGPT import ChatGPT

def cls():
    os.system("cls" if os.name == "nt" else "clear")
cls()

session_token = input("Enter your session token:\nThis data will be stored locally for convenience.\n> ")
cls()
cnv = input("Enter your conversation ID:\nYou can obtain this by opening a conversation on the ChatGPT client and copying everything after the /c/ in the URL.\n> ")
cls()
msg = input("Enter message:\n> ")
cls()

api = ChatGPT(
    session_token,
    proxy=None,
    disable_moderation=True,
    conversation_id=cnv,
    headless=False,
    verbose=True,
    input_mode="SLOW",
    input_delay=0.2,
)

message = api.send_message(msg)
print(message.response)
```

Also note that if I leave the conversation ID variable blank or remove it entirely it has the same effect.
I see, well I'll look into it now. Thanks!
I've just realized the majority of functions no longer work because OpenAI released a new update to the ChatGPT website. I'm currently working on a release that will fix them all.
Version `0.1.2` has been released now. It fixes all functions since they were bugged because of the new ChatGPT website update. You can update the library using the following command:
```sh
pip install UnlimitedGPT -U
```

Please let me know if this fixes the bugs you're facing after you update the library.

Edit: make sure to view the `CHANGELOG.md` file as there is some breaking changes that will affect your current code.
Working close tonight, I’ll let you know later into the evening.

On Wed, May 31, 2023 at 4:51 PM Sxvxge ***@***.***> wrote:

> Version 0.1.2 has been released now. It fixes all functions since they
> were bugged because of the new ChatGPT website update. You can update the
> library using the following command:
>
> pip install UnlimitedGPT -U
>
> Please let me know if this fixes the bugs you're facing after you update
> the library.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/Sxvxgee/UnlimitedGPT/issues/1#issuecomment-1570935078>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AQRHZHNP73S4ACQ3F2FZJDDXI6VL7ANCNFSM6AAAAAAYVUWZRI>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>

Headless still appears to be broken, when set to true it throws this error:
```
[_init_browser] Initializing browser...
Traceback (most recent call last):
  File "C:\Users\Noah Coodin\Downloads\coodingpt\main.py", line 15, in <module> 
    api = ChatGPT(
          ^^^^^^^^
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 72, in __init__
    self._init_browser()
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 153, in _init_browser
    raise e
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\UnlimitedGPT.py", line 149, in _init_browser
    self.driver = ChatGPTDriver(options=options, headless=self._headless)       
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\UnlimitedGPT\internal\driver.py", line 11, in __init__
    super().__init__(options=options, headless=headless)
  File "C:\Users\Noah Coodin\AppData\Local\Programs\Python\Python311\Lib\site-packages\undetected_chromedriver\__init__.py", line 386, in __init__
    if self.patcher.version_main < 108:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: '<' not supported between instances of 'NoneType' and 'int'
```
using this code:
```py
import os
from UnlimitedGPT import ChatGPT

def cls():
    os.system("cls" if os.name == "nt" else "clear")
cls()

session_token = input("Enter your session token:\nThis data will be stored locally for convenience.\n> ")
cls()
cnv = input("Enter your conversation ID:\nYou can obtain this by opening a conversation on the ChatGPT client and copying everything after the /c/ in the URL.\n> ")
cls()
msg = input("Enter message:\n> ")
cls()

api = ChatGPT(
    session_token,
    proxy=None,
    disable_moderation=True,
    conversation_id=cnv,
    headless=True,
    verbose=True
)
api.get_session_data()
message = api.send_message(msg)
print(message.response)
```

Also whatever you're passing the conversation_id variable is missing the c in between the two slashes. Currently, it attempts to go to https://chat.openai.com//conversation_id, when there should be a c in between the 2 last slashes.
Have you updated the library? If you did and you still got that issue with headless mode, then I suggest making an issue on the [undetected-chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver) repository as headless mode works for me, and it has been pointed out some users were facing issues with it based on the OS they're using the library on.

As for the issue with the conversation ID, I'm currently working on the fix and I'll push it soon.
Version `0.1.2.5` is now released. Update the library then try your code and let me know if it works. If you still face the same issue with headless mode, it's not because of this library and you need to open an issue at the repository of `undetected-chromedriver`.
Closing the issue as there has been no reply.

Fix patch:
diff --git a/src/UnlimitedGPT/UnlimitedGPT.py b/src/UnlimitedGPT/UnlimitedGPT.py
index bf8823a..c239426 100644
--- a/src/UnlimitedGPT/UnlimitedGPT.py
+++ b/src/UnlimitedGPT/UnlimitedGPT.py
@@ -1,26 +1,31 @@
 import re
-from typing import Optional, Literal
-from time import sleep, time
-from threading import Thread
-from platform import system
-from os import environ
 from json import loads
-from logging import getLogger, DEBUG, Formatter, StreamHandler
+from logging import DEBUG, Formatter, StreamHandler, getLogger
+from os import environ
+from platform import system
+from threading import Thread
+from time import sleep, time
+from typing import Literal, Optional
 from weakref import finalize
 
 from markdownify import markdownify
+from selenium.common.exceptions import (
+    NoSuchElementException,
+    StaleElementReferenceException,
+    TimeoutException,
+)
+from selenium.webdriver.common.action_chains import ActionChains
 from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.support import expected_conditions as EC
 from selenium.webdriver.support.wait import WebDriverWait
-from selenium.common.exceptions import TimeoutException, NoSuchElementException, StaleElementReferenceException
-from selenium.webdriver.common.keys import Keys
-from selenium.webdriver.common.action_chains import ActionChains
 from undetected_chromedriver import ChromeOptions
 
-from .internal.driver import ChatGPTDriver
 from .internal.chatgpt_data import ChatGPTVariables as CGPTV
-from .internal.objects import ChatGPTResponse, User, SessionData
+from .internal.driver import ChatGPTDriver
 from .internal.exceptions import InvalidConversationID
+from .internal.objects import ChatGPTResponse, SessionData, User
+
 
 class ChatGPT:
     """
@@ -35,7 +40,7 @@ class ChatGPT:
         verbose (bool, optional): Whether to enable verbose logging. Defaults to False.
         headless (bool, optional): Whether to run the browser in headless mode. Defaults to False.
         chrome_args (list, optional): Additional arguments for the Chrome browser. Defaults to [].
-    
+
     Raises:
     ----------
         InvalidConversationID: If the conversation ID is invalid.
@@ -46,7 +51,7 @@ class ChatGPT:
     def __init__(
         self,
         session_token: str,
-        conversation_id: str = '',
+        conversation_id: str = "",
         proxy: Optional[str] = None,
         disable_moderation: bool = False,
         verbose: bool = False,
@@ -63,9 +68,9 @@ def __init__(
         self._init_logger(verbose)
 
         if self._proxy and not re.findall(
-            r'(https?|socks(4|5)?):\/\/.+:\d{1,5}', self._proxy # type: ignore
+            r"(https?|socks(4|5)?):\/\/.+:\d{1,5}", self._proxy  # type: ignore
         ):
-            raise ValueError('Invalid proxy format')
+            raise ValueError("Invalid proxy format")
 
         self._init_browser()
         finalize(self, self.__del__)
@@ -75,11 +80,11 @@ def __del__(self) -> None:
         Close the browser and display.
         """
         self._is_active = False
-        if hasattr(self, 'driver'):
-            self.logger.debug('Closing browser...')
+        if hasattr(self, "driver"):
+            self.logger.debug("Closing browser...")
             self.driver.quit()
-        if hasattr(self, 'display'):
-            self.logger.debug('Closing display...')
+        if hasattr(self, "display"):
+            self.logger.debug("Closing display...")
             self.display.stop()
 
     def _get_out_of_menu(self) -> None:
@@ -90,7 +95,7 @@ def _get_out_of_menu(self) -> None:
             # First escape click is to remove the options menu
             # Second escape click is to get out of the settings menu
             # The rest are just to be safe
-            self.driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.ESCAPE)
+            self.driver.find_element(By.TAG_NAME, "body").send_keys(Keys.ESCAPE)
 
     def _init_logger(self, verbose: bool) -> None:
         """
@@ -100,14 +105,14 @@ def _init_logger(self, verbose: bool) -> None:
         ----------
             verbose (bool): Whether to enable verbose logging.
         """
-        self.logger = getLogger('pyChatGPT')
+        self.logger = getLogger("pyChatGPT")
         self.logger.setLevel(DEBUG)
         if verbose:
-            formatter = Formatter('[%(funcName)s] %(message)s')
+            formatter = Formatter("[%(funcName)s] %(message)s")
             stream_handler = StreamHandler()
             stream_handler.setFormatter(formatter)
             self.logger.addHandler(stream_handler)
-    
+
     def _init_browser(self) -> None:
         """
         Initialize the browser.
@@ -122,61 +127,66 @@ def _init_browser(self) -> None:
         ----------
             If the system is Linux and the DISPLAY environment variable is not set, a virtual display will be started.
         """
-        if system() == 'Linux' and 'DISPLAY' not in environ:
-            self.logger.debug('Starting virtual display...')
+        if system() == "Linux" and "DISPLAY" not in environ:
+            self.logger.debug("Starting virtual display...")
             try:
                 from pyvirtualdisplay.display import Display
+
                 self.display = Display()
                 self.display.start()
             except ModuleNotFoundError:
-                raise ValueError('Please install PyVirtualDisplay to start a virtual display by running `pip install PyVirtualDisplay`')
+                raise ValueError(
+                    "Please install PyVirtualDisplay to start a virtual display by running `pip install PyVirtualDisplay`"
+                )
             except FileNotFoundError as e:
-                if 'No such file or directory: \'Xvfb\'' in str(e):
-                    raise ValueError('Please install Xvfb to start a virtual display by running `sudo apt install xvfb`')
+                if "No such file or directory: 'Xvfb'" in str(e):
+                    raise ValueError(
+                        "Please install Xvfb to start a virtual display by running `sudo apt install xvfb`"
+                    )
                 raise e
 
-        self.logger.debug('Initializing browser...')
-        
+        self.logger.debug("Initializing browser...")
+
         options = ChromeOptions()
-        options.add_argument('--window-size=1024,768')
-        options.add_argument('--disable-popup-blocking')
+        options.add_argument("--window-size=1024,768")
+        options.add_argument("--disable-popup-blocking")
         if self._proxy:
-            options.add_argument(f'--proxy-server={self._proxy}')
+            options.add_argument(f"--proxy-server={self._proxy}")
         for arg in self._chrome_args:
             options.add_argument(arg)
         try:
             self.driver = ChatGPTDriver(options=options, headless=self._headless)
         except TypeError as e:
-            if str(e) == 'expected str, bytes or os.PathLike object, not NoneType':
-                raise ValueError('Chrome installation not found')
+            if str(e) == "expected str, bytes or os.PathLike object, not NoneType":
+                raise ValueError("Chrome installation not found")
             raise e
 
         if self._session_token:
-            self.logger.debug('Restoring session_token...')
+            self.logger.debug("Restoring session_token...")
             self.driver.execute_cdp_cmd(
-                'Network.setCookie',
+                "Network.setCookie",
                 {
-                    'domain': 'chat.openai.com',
-                    'path': '/',
-                    'name': '__Secure-next-auth.session-token',
-                    'value': self._session_token,
-                    'httpOnly': True,
-                    'secure': True,
+                    "domain": "chat.openai.com",
+                    "path": "/",
+                    "name": "__Secure-next-auth.session-token",
+                    "value": self._session_token,
+                    "httpOnly": True,
+                    "secure": True,
                 },
             )
 
         if self._disable_moderation:
-            self.logger.debug('Blocking moderation...')
+            self.logger.debug("Blocking moderation...")
             self.driver.execute_cdp_cmd(
-                'Network.setBlockedURLs',
-                {'urls': ['https://chat.openai.com/backend-api/moderations']},
+                "Network.setBlockedURLs",
+                {"urls": ["https://chat.openai.com/backend-api/moderations"]},
             )
 
-        self.logger.debug('Ensuring Cloudflare cookies...')
+        self.logger.debug("Ensuring Cloudflare cookies...")
         self._ensure_cf()
 
-        self.logger.debug('Opening chat page...')
-        self.driver.get(f'{CGPTV.chat_url}/{self._conversation_id}')
+        self.logger.debug("Opening chat page...")
+        self.driver.get(f"{CGPTV.chat_url}/{self._conversation_id}")
         self._check_blocking_elements()
 
         self._is_active = True
@@ -187,7 +197,7 @@ def _keep_alive(self) -> None:
         Keep the session alive by updating the local storage.
         """
         while self._is_active:
-            self.logger.debug('Updating session...')
+            self.logger.debug("Updating session...")
             payload = (
                 '{"event":"session","data":{"trigger":"getSession"},"timestamp":%d}'
                 % int(time())
@@ -198,33 +208,33 @@ def _keep_alive(self) -> None:
                     payload,
                 )
             except Exception as e:
-                self.logger.debug(f'Failed to update session: {str(e)}')
+                self.logger.debug(f"Failed to update session: {str(e)}")
             sleep(60)
 
     def _check_blocking_elements(self) -> None:
         """
         Check for blocking elements and dismiss them.
         """
-        self.logger.debug('Looking for blocking elements...')
+        self.logger.debug("Looking for blocking elements...")
         try:
             intro = WebDriverWait(self.driver, 5).until(
                 EC.presence_of_element_located(CGPTV.intro)
             )
-            self.logger.debug('Dismissing intro...')
-            self.driver.execute_script('arguments[0].remove()', intro)
-        except TimeoutException: # type: ignore
+            self.logger.debug("Dismissing intro...")
+            self.driver.execute_script("arguments[0].remove()", intro)
+        except TimeoutException:  # type: ignore
             pass
 
         alerts = self.driver.find_elements(*CGPTV.alert)
         if alerts:
-            if 'unable to load conversation' in alerts[0].text.lower():
+            if "unable to load conversation" in alerts[0].text.lower():
                 raise InvalidConversationID(alerts[0].text)
-            self.logger.debug('Dismissing alert...')
-            self.driver.execute_script('arguments[0].remove()', alerts[0])
+            self.logger.debug("Dismissing alert...")
+            self.driver.execute_script("arguments[0].remove()", alerts[0])
 
         if not self._clicked_buttons:
             for button in CGPTV.info_buttons:
-                self.driver.safe_click(button, timeout = 60)
+                self.driver.safe_click(button, timeout=60)
             self._clicked_buttons = True
 
     def _ensure_cf(self, retry: int = 3) -> None:
@@ -239,38 +249,38 @@ def _ensure_cf(self, retry: int = 3) -> None:
         ----------
             TimeoutException: If the Cloudflare challenge fails.
         """
-        self.logger.debug('Opening new tab...')
+        self.logger.debug("Opening new tab...")
         original_window = self.driver.current_window_handle
-        self.driver.switch_to.new_window('tab')
+        self.driver.switch_to.new_window("tab")
 
-        self.logger.debug('Getting Cloudflare challenge...')
-        self.driver.get('https://chat.openai.com/api/auth/session')
+        self.logger.debug("Getting Cloudflare challenge...")
+        self.driver.get("https://chat.openai.com/api/auth/session")
         try:
             WebDriverWait(self.driver, 10).until_not(
                 EC.presence_of_element_located(CGPTV.cf_challenge_form)
             )
-        except TimeoutException: # type: ignore
-            self.logger.debug(f'Cloudflare challenge failed, retrying {retry}...')
+        except TimeoutException:  # type: ignore
+            self.logger.debug(f"Cloudflare challenge failed, retrying {retry}...")
             if retry > 0:
-                self.logger.debug('Closing tab...')
+                self.logger.debug("Closing tab...")
                 self.driver.close()
                 self.driver.switch_to.window(original_window)
                 return self._ensure_cf(retry - 1)
-            raise ValueError('Cloudflare challenge failed')
-        self.logger.debug('Cloudflare challenge passed')
+            raise ValueError("Cloudflare challenge failed")
+        self.logger.debug("Cloudflare challenge passed")
 
-        self.logger.debug('Validating authorization...')
+        self.logger.debug("Validating authorization...")
         response = self.driver.page_source
-        if response[0] != '{':
-            response = self.driver.find_element(By.TAG_NAME, 'pre').text
+        if response[0] != "{":
+            response = self.driver.find_element(By.TAG_NAME, "pre").text
         response = loads(response)
         if (not response) or (
-            'error' in response and response['error'] == 'RefreshAccessTokenError'
+            "error" in response and response["error"] == "RefreshAccessTokenError"
         ):
-            raise ValueError('Invalid session token')
-        self.logger.debug('Authorization is valid')
+            raise ValueError("Invalid session token")
+        self.logger.debug("Authorization is valid")
 
-        self.logger.debug('Closing tab...')
+        self.logger.debug("Closing tab...")
         self.driver.close()
         self.driver.switch_to.window(original_window)
 
@@ -286,57 +296,58 @@ def _continue_generating(self, timeout: int) -> Optional[str]:
         ----------
             Optional[str]: The newly generated response.
         """
-        self.logger.debug('Continuing generating...')
+        self.logger.debug("Continuing generating...")
         # Click "Continue generating" button
         continue_response_clicked = self.driver.safe_click(
-            CGPTV.continue_regenerating, timeout = 5
+            CGPTV.continue_regenerating, timeout=5
         )
         if not continue_response_clicked:
-            self.logger.debug('Could not click continue regenerating button')
+            self.logger.debug("Could not click continue regenerating button")
             return None
 
         # Get the response, same way as send_message without the part of sending the message
-        self.logger.debug('Waiting for completion...')
+        self.logger.debug("Waiting for completion...")
         try:
             WebDriverWait(self.driver, timeout).until_not(
                 EC.presence_of_element_located(CGPTV.streaming)
             )
         except:
-            self.logger.debug('Could not continue generating')
+            self.logger.debug("Could not continue generating")
             return None
 
-        self.logger.debug('Getting response...')
+        self.logger.debug("Getting response...")
         responses = self.driver.find_elements(*CGPTV.big_response)
         if responses:
             response = responses[-1]
-            if 'text-red' in response.get_attribute('class'):
-                self.logger.debug('Response is an error')
+            if "text-red" in response.get_attribute("class"):
+                self.logger.debug("Response is an error")
                 return None
         response = self.driver.find_elements(*CGPTV.small_response)
         try:
             response = response[-1]
         except IndexError:
-            self.logger.debug('Response not found, resetting conversation...')
+            self.logger.debug("Response not found, resetting conversation...")
             self.reset_conversation()
             return None
-        
-        content = markdownify(
-            response.get_attribute('innerHTML'),
-            escape_asterisks=False,
-            escape_underscores=False,
-
-        ).replace(
-            'Copy code`', '`'
-        ).rstrip("\n")
-        
-        self.logger.debug('Continued regenerating the response')
+
+        content = (
+            markdownify(
+                response.get_attribute("innerHTML"),
+                escape_asterisks=False,
+                escape_underscores=False,
+            )
+            .replace("Copy code`", "`")
+            .rstrip("\n")
+        )
+
+        self.logger.debug("Continued regenerating the response")
         return content
 
     def send_message(
         self,
         message: str,
         timeout: int = 240,
-        input_mode: Literal['INSTANT', 'SLOW'] = "INSTANT",
+        input_mode: Literal["INSTANT", "SLOW"] = "INSTANT",
         input_delay: float = 0.1,
         continue_generating: bool = True,
     ) -> ChatGPTResponse:
@@ -361,13 +372,17 @@ def send_message(
             ValueError: If the response is invalid.
             ValueError: If the response is not found.
         """
-        self.logger.debug(f'Sending message with mode {input_mode}{f" with {input_delay} delay" if input_mode == "SLOW" else ""}...')
+        self.logger.debug(
+            f'Sending message with mode {input_mode}{f" with {input_delay} delay" if input_mode == "SLOW" else ""}...'
+        )
 
         textbox = WebDriverWait(self.driver, 60).until(
             EC.element_to_be_clickable(CGPTV.textbox)
         )
-        if input_mode == 'INSTANT':
-            self.driver.execute_script("arguments[0].value = arguments[1];", textbox, message)
+        if input_mode == "INSTANT":
+            self.driver.execute_script(
+                "arguments[0].value = arguments[1];", textbox, message
+            )
         else:
             for char in message:
                 try:
@@ -378,46 +393,47 @@ def send_message(
                     )
                     textbox.send_keys(char)
 
-        textbox.send_keys('a')
+        textbox.send_keys("a")
         textbox.send_keys(Keys.BACKSPACE)
-        
+
         textbox.send_keys(Keys.ENTER)
 
-        self.logger.debug('Waiting for completion...')
+        self.logger.debug("Waiting for completion...")
         WebDriverWait(self.driver, timeout).until_not(
             EC.presence_of_element_located(CGPTV.streaming)
         )
 
-        self.logger.debug('Getting response...')
+        self.logger.debug("Getting response...")
         responses = self.driver.find_elements(*CGPTV.big_response)
         if responses:
             response = responses[-1]
-            if 'text-red' in response.get_attribute('class'):
-                self.logger.debug('Response is an error')
+            if "text-red" in response.get_attribute("class"):
+                self.logger.debug("Response is an error")
                 raise ValueError(response.text)
         response = self.driver.find_elements(*CGPTV.small_response)
         try:
             response = response[-1]
         except IndexError:
-            self.logger.debug('Response not found, resetting conversation...')
+            self.logger.debug("Response not found, resetting conversation...")
             self.reset_conversation()
-            raise ValueError('Response not found')
-
-        content = markdownify(
-            response.get_attribute('innerHTML'),
-            escape_asterisks=False,
-            escape_underscores=False,
+            raise ValueError("Response not found")
 
-        ).replace(
-            'Copy code`', '`'
-        ).rstrip("\n")
+        content = (
+            markdownify(
+                response.get_attribute("innerHTML"),
+                escape_asterisks=False,
+                escape_underscores=False,
+            )
+            .replace("Copy code`", "`")
+            .rstrip("\n")
+        )
 
         if continue_generating:
-            continuation = self._continue_generating(timeout = timeout)
+            continuation = self._continue_generating(timeout=timeout)
             if continuation:
                 content = continuation
 
-        self.logger.debug(f'Message sent')
+        self.logger.debug(f"Message sent")
 
         return ChatGPTResponse(content, self._conversation_id)
 
@@ -425,7 +441,7 @@ def regenerate_response(
         self,
         message_timeout: int = 240,
         click_timeout: int = 20,
-        continue_generating: bool = True
+        continue_generating: bool = True,
     ) -> ChatGPTResponse:
         """
         Regenerate the response.
@@ -447,103 +463,106 @@ def regenerate_response(
             ValueError: If the response is invalid.
             ValueError: If the response is not found.
         """
-        self.logger.debug('Regenerating response...')
+        self.logger.debug("Regenerating response...")
 
         # Click "Regenerate response" button
         regenerate_response_clicked = self.driver.safe_click(
-            CGPTV.regenerate_response, timeout = click_timeout
+            CGPTV.regenerate_response, timeout=click_timeout
         )
         if not regenerate_response_clicked:
-            self.logger.debug('Could not click regenerate response button')
-            raise TimeoutException('Could not click regenerate response button')
+            self.logger.debug("Could not click regenerate response button")
+            raise TimeoutException("Could not click regenerate response button")
 
         # Get the response, same way as send_message without the part of sending the message
-        self.logger.debug('Waiting for completion...')
+        self.logger.debug("Waiting for completion...")
         WebDriverWait(self.driver, message_timeout).until_not(
             EC.presence_of_element_located(CGPTV.streaming)
         )
 
-        self.logger.debug('Getting response...')
+        self.logger.debug("Getting response...")
         responses = self.driver.find_elements(*CGPTV.big_response)
         if responses:
             response = responses[-1]
-            if 'text-red' in response.get_attribute('class'):
-                self.logger.debug('Response is an error')
+            if "text-red" in response.get_attribute("class"):
+                self.logger.debug("Response is an error")
                 raise ValueError(response.text)
         response = self.driver.find_elements(*CGPTV.small_response)
         try:
             response = response[-1]
         except IndexError:
-            self.logger.debug('Response not found, resetting conversation...')
+            self.logger.debug("Response not found, resetting conversation...")
             self.reset_conversation()
-            raise ValueError('Response not found')
-
-        content = markdownify(
-            response.get_attribute('innerHTML'),
-            escape_asterisks=False,
-            escape_underscores=False,
+            raise ValueError("Response not found")
 
-        ).replace(
-            'Copy code`', '`'
-        ).rstrip("\n")
+        content = (
+            markdownify(
+                response.get_attribute("innerHTML"),
+                escape_asterisks=False,
+                escape_underscores=False,
+            )
+            .replace("Copy code`", "`")
+            .rstrip("\n")
+        )
 
         if continue_generating:
-            continuation = self._continue_generating(timeout = message_timeout)
+            continuation = self._continue_generating(timeout=message_timeout)
             if continuation:
                 content = continuation
-        
-        self.logger.debug('Regenerated response')
+
+        self.logger.debug("Regenerated response")
         return ChatGPTResponse(content, self._conversation_id)
 
     def reset_conversation(self) -> None:
         """
         Resets the conversation.
         """
-        if not self.driver.current_url.startswith('https://chat.openai.com/'):
-            return self.logger.debug('Current URL is not chat page, skipping reset')
+        if not self.driver.current_url.startswith("https://chat.openai.com/"):
+            return self.logger.debug("Current URL is not chat page, skipping reset")
 
-        self.logger.debug('Resetting conversation...')
-        clicked = self.driver.safe_click(CGPTV.new_chat, timeout = 60)
+        self.logger.debug("Resetting conversation...")
+        clicked = self.driver.safe_click(CGPTV.new_chat, timeout=60)
         if not clicked:
-            self.logger.debug('New chat button not found')
+            self.logger.debug("New chat button not found")
             return self._get_out_of_menu()
-        self.logger.debug('Conversation reset')
+        self.logger.debug("Conversation reset")
 
     def clear_conversations(self) -> None:
         """
         Clears all conversations.
         """
-        self.logger.debug('Clearing all conversations...')
+        self.logger.debug("Clearing all conversations...")
         try:
-            menu_button_clicked = self.driver.safe_click(CGPTV.menu_button, timeout = 60)
+            menu_button_clicked = self.driver.safe_click(CGPTV.menu_button, timeout=60)
             if not menu_button_clicked:
-                self.logger.debug('Could not click menu button')
+                self.logger.debug("Could not click menu button")
                 return self._get_out_of_menu()
-            self.logger.debug('Clicked menu button')
-            
+            self.logger.debug("Clicked menu button")
+
             clear_conversations_button_clicked = self.driver.safe_click(
-                CGPTV.menu_clear_conversations, timeout = 60
+                CGPTV.menu_clear_conversations, timeout=60
             )
             if not clear_conversations_button_clicked:
-                self.logger.debug('Could not click clear conversations button')
+                self.logger.debug("Could not click clear conversations button")
                 return self._get_out_of_menu()
-            self.logger.debug('Clicked clear conversations button')
-            
+            self.logger.debug("Clicked clear conversations button")
+
             confirm_clear_button_clicked = self.driver.safe_click(
-                CGPTV.menu_confirm_clear_conversations, timeout = 60
+                CGPTV.menu_confirm_clear_conversations, timeout=60
             )
             if not confirm_clear_button_clicked:
-                self.logger.debug('Could not click confirm clear conversations button')
+                self.logger.debug("Could not click confirm clear conversations button")
                 return self._get_out_of_menu()
-            self.logger.debug('Clicked confirm clear conversations button')
+            self.logger.debug("Clicked confirm clear conversations button")
 
-            self.logger.debug('Cleared all conversations')
+            self.logger.debug("Cleared all conversations")
             self._get_out_of_menu()
         except NoSuchElementException:
-            self.logger.debug('Could not find menu buttons')
+            self.logger.debug("Could not find menu buttons")
             return self._get_out_of_menu()
 
-    def switch_theme(self, theme: Literal['LIGHT', 'DARK', 'OPPOSITE', 'SYSTEM']) -> None:
+    def switch_theme(
+        self, theme: Literal["LIGHT", "DARK", "OPPOSITE", "SYSTEM"]
+    ) -> None:
         """
         Switch the theme.
 
@@ -558,57 +577,65 @@ def switch_theme(self, theme: Literal['LIGHT', 'DARK', 'OPPOSITE', 'SYSTEM']) ->
             - `OPPOSITE`: Switch to the opposite theme.
             - `SYSTEM`: Switch to the system theme.
         """
-        self.logger.debug(f'Switching theme to {theme}...')
+        self.logger.debug(f"Switching theme to {theme}...")
         try:
             menu_button_clicked = self.driver.safe_click(CGPTV.menu_button)
             if not menu_button_clicked:
-                self.logger.debug('Could not click menu button')
+                self.logger.debug("Could not click menu button")
                 return self._get_out_of_menu()
-            self.logger.debug('Clicked menu button')
-            
+            self.logger.debug("Clicked menu button")
+
             settings_button_clicked = self.driver.safe_click(CGPTV.menu_settings)
             if not settings_button_clicked:
-                self.logger.debug('Could not click settings button')
+                self.logger.debug("Could not click settings button")
                 return self._get_out_of_menu()
-            self.logger.debug('Clicked settings button')
-            
-            current_theme_value = self.driver.find_element(*CGPTV.outer_html).get_attribute('class')
-            current_theme = 'LIGHT' if 'light' in current_theme_value else 'DARK'
+            self.logger.debug("Clicked settings button")
+
+            current_theme_value = self.driver.find_element(
+                *CGPTV.outer_html
+            ).get_attribute("class")
+            current_theme = "LIGHT" if "light" in current_theme_value else "DARK"
             if theme == current_theme:
-                self.logger.debug('Theme is already set to the desired theme')
+                self.logger.debug("Theme is already set to the desired theme")
                 return self._get_out_of_menu()
-            self.logger.debug(f'Current theme is {current_theme}')
+            self.logger.debug(f"Current theme is {current_theme}")
 
             select_element = self.driver.find_element(*CGPTV.theme_select)
             ActionChains(self.driver).move_to_element(select_element).perform()
-            select_clicked = self.driver.safe_click(CGPTV.theme_select, timeout = 60)
+            select_clicked = self.driver.safe_click(CGPTV.theme_select, timeout=60)
             if not select_clicked:
-                self.logger.debug('Could not click theme select')
+                self.logger.debug("Could not click theme select")
                 return self._get_out_of_menu()
-            self.logger.debug('Clicked theme select')
+            self.logger.debug("Clicked theme select")
 
-            if theme == 'OPPOSITE':
-                if current_theme == 'SYSTEM':
-                    self.logger.debug('Theme cannot be set to opposite of system theme')
+            if theme == "OPPOSITE":
+                if current_theme == "SYSTEM":
+                    self.logger.debug("Theme cannot be set to opposite of system theme")
                     return self._get_out_of_menu()
-                    
-                opposite_theme = 'dark' if current_theme == 'LIGHT' else 'light'
-                option_clicked = self.driver.safe_click((By.CSS_SELECTOR, f'select.rounded option[value={opposite_theme}]'), timeout = 60)
+
+                opposite_theme = "dark" if current_theme == "LIGHT" else "light"
+                option_clicked = self.driver.safe_click(
+                    (By.CSS_SELECTOR, f"select.rounded option[value={opposite_theme}]"),
+                    timeout=60,
+                )
                 if not option_clicked:
-                    self.logger.debug('Could not click opposite theme option')
+                    self.logger.debug("Could not click opposite theme option")
                     return self._get_out_of_menu()
-                self.logger.debug(f'Selected opposite theme of {current_theme}')
+                self.logger.debug(f"Selected opposite theme of {current_theme}")
             else:
-                option_clicked = self.driver.safe_click((By.CSS_SELECTOR, f'select.rounded option[value={theme.lower()}]'), timeout = 60)
+                option_clicked = self.driver.safe_click(
+                    (By.CSS_SELECTOR, f"select.rounded option[value={theme.lower()}]"),
+                    timeout=60,
+                )
                 if not option_clicked:
-                    self.logger.debug('Could not click theme option')
+                    self.logger.debug("Could not click theme option")
                     return self._get_out_of_menu()
-                self.logger.debug(f'Selected theme {theme}')
-            
-            self.logger.debug('Theme switched')
+                self.logger.debug(f"Selected theme {theme}")
+
+            self.logger.debug("Theme switched")
             self._get_out_of_menu()
         except NoSuchElementException:
-            self.logger.debug('Could not find theme buttons')
+            self.logger.debug("Could not find theme buttons")
             return self._get_out_of_menu()
 
     def switch_account(self, session_token: str) -> SessionData:
@@ -628,46 +655,48 @@ def switch_account(self, session_token: str) -> SessionData:
             ValueError: If the session token is not provided.
             ValueError: If the response is invalid.
         """
-        self.logger.debug('Switching account...')
-        self.conversation_id = '' # Old conversation ID cannot be loaded in the new account
+        self.logger.debug("Switching account...")
+        self.conversation_id = (
+            ""  # Old conversation ID cannot be loaded in the new account
+        )
         self.driver.execute_cdp_cmd(
-            'Network.setCookie',
+            "Network.setCookie",
             {
-                'domain': 'chat.openai.com',
-                'path': '/',
-                'name': '__Secure-next-auth.session-token',
-                'value': session_token,
-                'httpOnly': True,
-                'secure': True,
+                "domain": "chat.openai.com",
+                "path": "/",
+                "name": "__Secure-next-auth.session-token",
+                "value": session_token,
+                "httpOnly": True,
+                "secure": True,
             },
         )
-        self.logger.debug('Executed CDP command')
+        self.logger.debug("Executed CDP command")
 
-        self.logger.debug('Validating authorization...')
-        self.driver.get('https://chat.openai.com/api/auth/session')
+        self.logger.debug("Validating authorization...")
+        self.driver.get("https://chat.openai.com/api/auth/session")
         response = self.driver.page_source
-        if response[0] != '{':
-            response = self.driver.find_element(By.TAG_NAME, 'pre').text
+        if response[0] != "{":
+            response = self.driver.find_element(By.TAG_NAME, "pre").text
         response = loads(response)
         if (not response) or (
-            'error' in response and response['error'] == 'RefreshAccessTokenError'
+            "error" in response and response["error"] == "RefreshAccessTokenError"
         ):
-            raise ValueError('Invalid session token')
+            raise ValueError("Invalid session token")
         session_data = SessionData(
-            User(**response['user']),
+            User(**response["user"]),
             response["expires"],
             response["accessToken"],
-            response["authProvider"]
+            response["authProvider"],
         )
-        self.logger.debug('Authorization is valid')
+        self.logger.debug("Authorization is valid")
 
-        self.logger.debug('Opening chat page...')
-        self.driver.get(f'{CGPTV.chat_url}/{self._conversation_id}')
-        self.logger.debug('Opened chat page')
+        self.logger.debug("Opening chat page...")
+        self.driver.get(f"{CGPTV.chat_url}/{self._conversation_id}")
+        self.logger.debug("Opened chat page")
         self._check_blocking_elements()
-        self.logger.debug('Switched account')
+        self.logger.debug("Switched account")
         return session_data
-    
+
     def get_session_data(self) -> SessionData:
         """
         Get the session data.
@@ -676,22 +705,22 @@ def get_session_data(self) -> SessionData:
         ----------
             SessionData: The current account's session data.
         """
-        self.logger.debug('Getting account data...')
-        self.logger.debug('Opening new tab...')
+        self.logger.debug("Getting account data...")
+        self.logger.debug("Opening new tab...")
         self.driver.execute_script("window.open();")
         self.driver.switch_to.window(self.driver.window_handles[-1])
-        self.driver.get('https://chat.openai.com/api/auth/session')
+        self.driver.get("https://chat.openai.com/api/auth/session")
         response = self.driver.page_source
-        if response[0] != '{':
-            response = self.driver.find_element(By.TAG_NAME, 'pre').text
+        if response[0] != "{":
+            response = self.driver.find_element(By.TAG_NAME, "pre").text
         response = loads(response)
         session_data = SessionData(
-            User(**response['user']),
+            User(**response["user"]),
             response["expires"],
             response["accessToken"],
-            response["authProvider"]
+            response["authProvider"],
         )
-        self.logger.debug('Closing tab...')
+        self.logger.debug("Closing tab...")
         self.driver.close()
         self.driver.switch_to.window(self.driver.window_handles[0])
         return session_data
@@ -700,22 +729,22 @@ def logout(self) -> None:
         """
         Logs out of the current account signed into https://chat.openai.com
         """
-        self.logger.debug('Logging out...')
+        self.logger.debug("Logging out...")
         self.driver.execute_cdp_cmd(
-            'Network.deleteCookies',
+            "Network.deleteCookies",
             {
-                'name': '__Secure-next-auth.session-token',
-                'url': 'https://chat.openai.com',
+                "name": "__Secure-next-auth.session-token",
+                "url": "https://chat.openai.com",
             },
         )
-        self.logger.debug('Executed CDP command')
-        self.driver.get('https://chat.openai.com/api/auth/session')
+        self.logger.debug("Executed CDP command")
+        self.driver.get("https://chat.openai.com/api/auth/session")
         response = self.driver.page_source
-        if response[0] != '{':
-            response = self.driver.find_element(By.TAG_NAME, 'pre').text
+        if response[0] != "{":
+            response = self.driver.find_element(By.TAG_NAME, "pre").text
         response = loads(response)
         if response == {}:
-            self.logger.debug('Logout successful')
+            self.logger.debug("Logout successful")
             return
 
     def toggle_chat_history(self, state: bool = False) -> None:
@@ -726,47 +755,59 @@ def toggle_chat_history(self, state: bool = False) -> None:
         ----------
             state (bool, optional): The state to set the chat history toggle to. Defaults to False.
         """
-        self.logger.debug('Disabling chat history...')
+        self.logger.debug("Disabling chat history...")
         try:
             menu_button_clicked = self.driver.safe_click(CGPTV.menu_button)
             if not menu_button_clicked:
-                self.logger.debug('Could not click menu button')
+                self.logger.debug("Could not click menu button")
                 return self._get_out_of_menu()
 
-            self.logger.debug('Clicked menu button')
-            
+            self.logger.debug("Clicked menu button")
+
             settings_button_clicked = self.driver.safe_click(CGPTV.menu_settings)
             if not settings_button_clicked:
-                self.logger.debug('Could not click settings button')
+                self.logger.debug("Could not click settings button")
                 return self._get_out_of_menu()
 
-            self.logger.debug('Clicked settings button')
+            self.logger.debug("Clicked settings button")
 
             wait = WebDriverWait(self.driver, 60)
 
             # Click "Data controls" button
             data_controls_clicked = self.driver.safe_click(
-                CGPTV.data_controls, timeout = 60
+                CGPTV.data_controls, timeout=60
             )
             if not data_controls_clicked:
-                self.logger.debug('Could not click data controls button')
+                self.logger.debug("Could not click data controls button")
                 return self._get_out_of_menu()
 
             # Click "Disable chat history" button
             # Not using safe_click because it there are some checks that need to be done before clicking
-            chat_history_toggle = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, f'button[aria-label="Chat History & Training"]')))
-            current_state = True if chat_history_toggle.get_attribute('aria-checked') == 'true' else False
+            chat_history_toggle = wait.until(
+                EC.element_to_be_clickable(
+                    (By.CSS_SELECTOR, f'button[aria-label="Chat History & Training"]')
+                )
+            )
+            current_state = (
+                True
+                if chat_history_toggle.get_attribute("aria-checked") == "true"
+                else False
+            )
             if current_state == state:
-                self.logger.debug('Chat history is already set to the desired state')
+                self.logger.debug("Chat history is already set to the desired state")
                 return self._get_out_of_menu()
 
             chat_history_toggle.click()
-            self.logger.debug(f'Chat history is now {"enabled" if state else "disabled"}')
+            self.logger.debug(
+                f'Chat history is now {"enabled" if state else "disabled"}'
+            )
             self._get_out_of_menu()
         except:
-            self.logger.debug(f'Could not {"enable" if state else "disable"} chat history')
+            self.logger.debug(
+                f'Could not {"enable" if state else "disable"} chat history'
+            )
             return self._get_out_of_menu()
-    
+
     def switch_conversation(self, conversation_id: str) -> None:
         """
         Switch the conversation.
@@ -779,8 +820,8 @@ def switch_conversation(self, conversation_id: str) -> None:
         ----------
             InvalidConversationID: If the conversation ID is invalid.
         """
-        self.logger.debug('Switching conversation...')
-        self.driver.get(f'{CGPTV.chat_url}/{conversation_id}')
+        self.logger.debug("Switching conversation...")
+        self.driver.get(f"{CGPTV.chat_url}/{conversation_id}")
         self._check_blocking_elements()
         self._conversation_id = conversation_id
-        self.logger.debug(f'Switched conversation to {conversation_id}')
+        self.logger.debug(f"Switched conversation to {conversation_id}")
diff --git a/src/UnlimitedGPT/__init__.py b/src/UnlimitedGPT/__init__.py
index 18f9fb1..32c75b6 100644
--- a/src/UnlimitedGPT/__init__.py
+++ b/src/UnlimitedGPT/__init__.py
@@ -4,7 +4,7 @@
 An unofficial Python wrapper for OpenAI's ChatGPT API
 """
 
-from .UnlimitedGPT import ChatGPT
-from .internal.objects import ChatGPTResponse, User, SessionData
 from .internal.chatgpt_data import ChatGPTVariables
-from .internal.driver import ChatGPTDriver
\ No newline at end of file
+from .internal.driver import ChatGPTDriver
+from .internal.objects import ChatGPTResponse, SessionData, User
+from .UnlimitedGPT import ChatGPT
diff --git a/src/UnlimitedGPT/internal/chatgpt_data.py b/src/UnlimitedGPT/internal/chatgpt_data.py
index 8d54757..cea9af8 100644
--- a/src/UnlimitedGPT/internal/chatgpt_data.py
+++ b/src/UnlimitedGPT/internal/chatgpt_data.py
@@ -1,11 +1,13 @@
 from dataclasses import dataclass
+
 from selenium.webdriver.common.by import By
 
+
 @dataclass
 class ChatGPTVariables:
     # Other
     outer_html = (By.XPATH, "/html")
-    cf_challenge_form = (By.ID, 'challenge-form')
+    cf_challenge_form = (By.ID, "challenge-form")
     chats_list_first_node = (
         By.XPATH,
         '//div[substring(@class, string-length(@class) - string-length("text-sm") + 1)  = "text-sm"]//a',
@@ -13,78 +15,57 @@ class ChatGPTVariables:
 
     # Popups and such
     info_buttons = (
-        (By.XPATH, '/html/body/div[3]/div/div/div/div[2]/div/div[2]/button'),
-        (By.XPATH, '/html/body/div[3]/div/div/div/div[2]/div/div[2]/button[2]'),
-        (By.XPATH, '/html/body/div[3]/div/div/div/div[2]/div/div[2]/button[2]'),
-    )
-    alert = (
-        By.XPATH,
-        '//div[@role="alert"]'
-    )
-    intro = (
-        By.ID,
-        'headlessui-portal-root'
+        (By.XPATH, "/html/body/div[3]/div/div/div/div[2]/div/div[2]/button"),
+        (By.XPATH, "/html/body/div[3]/div/div/div/div[2]/div/div[2]/button[2]"),
+        (By.XPATH, "/html/body/div[3]/div/div/div/div[2]/div/div[2]/button[2]"),
     )
-    
+    alert = (By.XPATH, '//div[@role="alert"]')
+    intro = (By.ID, "headlessui-portal-root")
+
     # Responses and such
     streaming = (
         By.XPATH,
-        '//div[starts-with(@class, "result-streaming markdown prose")]'
-    )
-    big_response = (
-        By.XPATH,
-        '//div[@class="flex-1 overflow-hidden"]//div[p]'
+        '//div[starts-with(@class, "result-streaming markdown prose")]',
     )
+    big_response = (By.XPATH, '//div[@class="flex-1 overflow-hidden"]//div[p]')
     small_response = (
         By.XPATH,
         '//div[starts-with(@class, "markdown prose w-full break-words")]',
     )
-    textbox = (
-        By.XPATH,
-        '//textarea[@id="prompt-textarea"]'
-    )
+    textbox = (By.XPATH, '//textarea[@id="prompt-textarea"]')
     continue_regenerating = (
         By.XPATH,
-        "/html/body/div[1]/div[2]/div[2]/div/main/div[3]/form/div/div[1]/div/button[2]"
+        "/html/body/div[1]/div[2]/div[2]/div/main/div[3]/form/div/div[1]/div/button[2]",
     )
     regenerate_response = (
         By.XPATH,
-        "/html/body/div[1]/div[2]/div[2]/div/main/div[3]/form/div/div[1]/div/button"
-    )
-    new_chat = (
-        By.LINK_TEXT,
-        'New chat'
+        "/html/body/div[1]/div[2]/div[2]/div/main/div[3]/form/div/div[1]/div/button",
     )
+    new_chat = (By.LINK_TEXT, "New chat")
 
     # Menu buttons
     menu_button = (
         By.XPATH,
-        "/html/body/div[1]/div[2]/div[1]/div/div/div/nav/div[4]/div/button"
+        "/html/body/div[1]/div[2]/div[1]/div/div/div/nav/div[4]/div/button",
     )
     menu_clear_conversations = (
         # By.XPATH,
         # "/html/body/div[1]/div[2]/div[1]/div/div/div/nav/div[3]/div/div/nav/a[2]"
         By.LINK_TEXT,
-        'Clear conversations'
-    )
-    menu_confirm_clear_conversations = (
-        By.LINK_TEXT,
-        "Confirm clear conversations"
+        "Clear conversations",
     )
+    menu_confirm_clear_conversations = (By.LINK_TEXT, "Confirm clear conversations")
     menu_settings = (
         # By.XPATH,
         # "/html/body/div[1]/div[2]/div[1]/div/div/div/nav/div[3]/div/div/nav/a[3]"
         By.LINK_TEXT,
-        'Settings'
-    )
-    theme_select = (
-        By.CSS_SELECTOR,
-        'select.rounded'
+        "Settings",
     )
+    theme_select = (By.CSS_SELECTOR, "select.rounded")
     data_controls = (
         By.CSS_SELECTOR,
-        'button[data-state="inactive"][id^="radix-"][id$="-trigger-DataControls"]'
+        'button[data-state="inactive"][id^="radix-"][id$="-trigger-DataControls"]',
     )
-    
+
     # URLs
-    chat_url = 'https://chat.openai.com/chat'
+    chat_url = "https://chat.openai.com/chat"
diff --git a/src/UnlimitedGPT/internal/driver.py b/src/UnlimitedGPT/internal/driver.py
index 1211506..f560e72 100644
--- a/src/UnlimitedGPT/internal/driver.py
+++ b/src/UnlimitedGPT/internal/driver.py
@@ -1,12 +1,14 @@
 import undetected_chromedriver as uc
-from selenium.webdriver.support.wait import WebDriverWait
 from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.wait import WebDriverWait
+
 
 class ChatGPTDriver(uc.Chrome):
     """
     Custom selenium driver for ChatGPT.
     ##### Still in development.
     """
+
     def __init__(self, options: uc.ChromeOptions, headless: bool = False):
         super().__init__(options=options, headless=headless)
 
@@ -18,7 +20,7 @@ def safe_click(self, mark, timeout: int = 10) -> bool:
         ----------
             mark: (By, str): The element to click.
             timeout: (int): The amount of time to wait for the element to be clickable.
-        
+
         Returns:
         ----------
             bool: Whether or not the element was clicked.
diff --git a/src/UnlimitedGPT/internal/exceptions.py b/src/UnlimitedGPT/internal/exceptions.py
index 8bb74d4..4131a9a 100644
--- a/src/UnlimitedGPT/internal/exceptions.py
+++ b/src/UnlimitedGPT/internal/exceptions.py
@@ -1,5 +1,6 @@
 class UnlimitedGPTException(BaseException):
     pass
 
+
 class InvalidConversationID(UnlimitedGPTException):
     pass
diff --git a/src/UnlimitedGPT/internal/objects.py b/src/UnlimitedGPT/internal/objects.py
index 2998536..d9903c5 100644
--- a/src/UnlimitedGPT/internal/objects.py
+++ b/src/UnlimitedGPT/internal/objects.py
@@ -1,28 +1,28 @@
-from typing import Optional, List
 from datetime import datetime
+from typing import List, Optional
+
 
 class ChatGPTResponse:
     """
     The response object returned by ChatGPT
     """
-    def __init__(
-        self,
-        response: str,
-        conversation_id: Optional[str] = None
-    ):
+
+    def __init__(self, response: str, conversation_id: Optional[str] = None):
         self.response = response
         self.conversation_id = conversation_id
-    
+
     def __str__(self):
         return self.response
-    
+
     def __repr__(self):
         return f'<ChatGPTResponse response="{self.response}" conversation_id="{self.conversation_id}">'
 
+
 class User:
     """
     The user object returned by ChatGPT.
     """
+
     def __init__(
         self,
         id: str,
@@ -34,7 +34,7 @@ def __init__(
         iat: int,
         mfa: bool,
         groups: List[str],
-        intercom_hash: str
+        intercom_hash: str,
     ) -> None:
         """
         Initialize a User object.
@@ -62,22 +62,19 @@ def __init__(
         self.mfa = mfa
         self.groups = groups
         self.intercom_hash = intercom_hash
-    
+
     def __str__(self):
         return self.name
 
     def __repr__(self):
         return f'<User name="{self.name}" id="{self.id}" email="{self.email}" image="{self.image}" picture="{self.picture}" idp="{self.idp}" iat="{self.iat}" mfa="{self.mfa}" groups="{self.groups}" intercom_hash="{self.intercom_hash}">'
 
+
 class SessionData:
     """Class representing session data."""
 
     def __init__(
-        self,
-        user: User,
-        expires: str,
-        accessToken: str,
-        authProvider: str
+        self, user: User, expires: str, accessToken: str, authProvider: str
     ) -> None:
         """
         Initialize a SessionData object.
diff --git a/src/setup.py b/src/setup.py
index 34a6638..dd56bac 100644
--- a/src/setup.py
+++ b/src/setup.py
@@ -1,48 +1,49 @@
 import os
-from setuptools import setup, find_packages
+
+from setuptools import find_packages, setup
 
 base_path = os.path.abspath(os.path.dirname(__file__))
 
 requirements = []
-with open(os.path.join(os.path.dirname(base_path), 'requirements.txt')) as f:
+with open(os.path.join(os.path.dirname(base_path), "requirements.txt")) as f:
     requirements = f.read().splitlines()
 
-readme = ''
-with open(os.path.join(os.path.dirname(base_path), 'README.md')) as f:
+readme = ""
+with open(os.path.join(os.path.dirname(base_path), "README.md")) as f:
     readme = f.read()
 
 setup(
-    name='UnlimitedGPT',
-    author='Sxvxge',
-    url='https://github.com/Sxvxgee/UnlimitedGPT',
+    name="UnlimitedGPT",
+    author="Sxvxge",
+    url="https://github.com/Sxvxgee/UnlimitedGPT",
     project_urls={
-        'Documentation': 'https://github.com/Sxvxgee/UnlimitedGPT/blob/main/README.md',
-        'Issue tracker': 'https://github.com/Sxvxgee/UnlimitedGPT/issues',
-        'Changelog': 'https://github.com/Sxvxgee/UnlimitedGPT/blob/main/CHANGELOG.md',
+        "Documentation": "https://github.com/Sxvxgee/UnlimitedGPT/blob/main/README.md",
+        "Issue tracker": "https://github.com/Sxvxgee/UnlimitedGPT/issues",
+        "Changelog": "https://github.com/Sxvxgee/UnlimitedGPT/blob/main/CHANGELOG.md",
     },
     version="0.1.5.5",
-    packages=['UnlimitedGPT', 'UnlimitedGPT/internal'],
-    py_modules=['UnlimitedGPT'],
-    license='GPL-3.0 license',
-    description='An unofficial Python wrapper for OpenAI\'s ChatGPT API',
+    packages=["UnlimitedGPT", "UnlimitedGPT/internal"],
+    py_modules=["UnlimitedGPT"],
+    license="GPL-3.0 license",
+    description="An unofficial Python wrapper for OpenAI's ChatGPT API",
     long_description=readme,
-    long_description_content_type='text/markdown',
+    long_description_content_type="text/markdown",
     include_package_data=True,
     install_requires=requirements,
-    python_requires='>=3.8.0',
+    python_requires=">=3.8.0",
     classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
-        'Intended Audience :: Developers',
-        'Natural Language :: English',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Topic :: Internet',
-        'Topic :: Software Development :: Libraries',
-        'Topic :: Software Development :: Libraries :: Python Modules',
-        'Topic :: Utilities',
-        'Typing :: Typed',
+        "Development Status :: 5 - Production/Stable",
+        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+        "Intended Audience :: Developers",
+        "Natural Language :: English",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Topic :: Internet",
+        "Topic :: Software Development :: Libraries",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Utilities",
+        "Typing :: Typed",
     ],
 )


Test patch:
diff --git a/tests/UnlimitedGPT/__init__.py b/tests/UnlimitedGPT/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/UnlimitedGPT/example.env b/tests/UnlimitedGPT/example.env
new file mode 100644
index 0000000..db565e2
--- /dev/null
+++ b/tests/UnlimitedGPT/example.env
@@ -0,0 +1,2 @@
+SESSION_TOKEN_1=your_first_session_token_here
+SESSION_TOKEN_2=your_second_session_token_here
\ No newline at end of file
diff --git a/tests/UnlimitedGPT/test_UnlimitedGPT.py b/tests/UnlimitedGPT/test_UnlimitedGPT.py
new file mode 100644
index 0000000..e0d9f97
--- /dev/null
+++ b/tests/UnlimitedGPT/test_UnlimitedGPT.py
@@ -0,0 +1,69 @@
+import os
+import unittest
+from unittest.mock import patch
+from src.UnlimitedGPT.UnlimitedGPT import ChatGPT
+from src.UnlimitedGPT.internal.objects import ChatGPTResponse, SessionData
+from dotenv import load_dotenv, find_dotenv
+
+load_dotenv(find_dotenv())
+
+class TestChatGPT(unittest.TestCase):
+    def setUp(self):
+        self.session_token_1 = os.getenv("SESSION_TOKEN_1")
+        self.session_token_2 = os.getenv("SESSION_TOKEN_2")
+        self.api = ChatGPT(self.session_token_1)
+
+    def tearDown(self):
+        self.api.__del__()
+        del self.api
+
+    def test_session_token(self):
+        self.assertEqual(self.api._session_token, self.session_token_1)
+
+    def test_send_message_short(self):
+        message = self.api.send_message("Hello World.")
+        self.assertIsInstance(message, ChatGPTResponse)
+        
+    def test_send_message_long(self):
+        message = self.api.send_message("Write me an essay on AI over 3000 words")
+        self.assertIsInstance(message, ChatGPTResponse)
+
+    def test_get_session_data(self):
+        session_data = self.api.get_session_data()
+        self.assertIsInstance(session_data, SessionData)
+
+    def test_toggle_chat_history_false(self):
+        self.api.toggle_chat_history(False)
+    
+    def test_toggle_chat_history_true(self):
+        self.api.toggle_chat_history(True)
+
+    def test_switch_theme(self):
+        self.api.switch_theme("DARK")
+
+    def test_clear_conversations(self):
+        message = self.api.send_message("Hello World 0.")
+        self.api.clear_conversations()
+        # Add assertions to check if conversations are cleared
+
+    def test_reset_conversation(self):
+        self.api.reset_conversation()
+        # Add assertions to check if conversation is reset
+
+    def test_switch_conversation(self):
+        message_1 = self.api.send_message("Hello World 1.")
+        message_2 = self.api.send_message("Hello World 2.")
+        self.api.switch_conversation(message_1.conversation_id)
+        message_3 = self.api.send_message("Hello World 3.")
+        self.assertEqual(message_1.conversation_id, message_3.conversation_id)
+
+    def test_switch_account(self):
+        self.api.switch_account(self.session_token_2)
+        message = self.api.send_message("Hello World.")
+
+    def test_logout(self):
+        self.api.logout()
+        # Add assertions to check if logout is successful
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29