mirror of
https://github.com/aljazceru/Auto-GPT.git
synced 2025-12-17 22:14:28 +01:00
Add documentation
This commit is contained in:
@@ -7,6 +7,7 @@ agents = {} # key, (task, full_message_history, model)
|
|||||||
|
|
||||||
|
|
||||||
def create_agent(task, prompt, model):
|
def create_agent(task, prompt, model):
|
||||||
|
"""Create a new agent and return its key"""
|
||||||
global next_key
|
global next_key
|
||||||
global agents
|
global agents
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ def create_agent(task, prompt, model):
|
|||||||
|
|
||||||
|
|
||||||
def message_agent(key, message):
|
def message_agent(key, message):
|
||||||
|
"""Send a message to an agent and return its response"""
|
||||||
global agents
|
global agents
|
||||||
|
|
||||||
task, messages, model = agents[int(key)]
|
task, messages, model = agents[int(key)]
|
||||||
@@ -57,6 +59,7 @@ def message_agent(key, message):
|
|||||||
|
|
||||||
|
|
||||||
def list_agents():
|
def list_agents():
|
||||||
|
"""Return a list of all agents"""
|
||||||
global agents
|
global agents
|
||||||
|
|
||||||
# Return a list of agent keys and their tasks
|
# Return a list of agent keys and their tasks
|
||||||
@@ -64,6 +67,7 @@ def list_agents():
|
|||||||
|
|
||||||
|
|
||||||
def delete_agent(key):
|
def delete_agent(key):
|
||||||
|
"""Delete an agent and return True if successful, False otherwise"""
|
||||||
global agents
|
global agents
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import openai
|
|||||||
# This is a magic function that can do anything with no-code. See
|
# This is a magic function that can do anything with no-code. See
|
||||||
# https://github.com/Torantulino/AI-Functions for more info.
|
# https://github.com/Torantulino/AI-Functions for more info.
|
||||||
def call_ai_function(function, args, description, model="gpt-4"):
|
def call_ai_function(function, args, description, model="gpt-4"):
|
||||||
# parse args to comma seperated string
|
"""Calls an AI function and returns the result."""
|
||||||
args = ", ".join(args)
|
args = ", ".join(args)
|
||||||
messages = [
|
messages = [
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,7 @@ def call_ai_function(function, args, description, model="gpt-4"):
|
|||||||
|
|
||||||
|
|
||||||
def evaluate_code(code: str) -> List[str]:
|
def evaluate_code(code: str) -> List[str]:
|
||||||
|
"""Evaluates the given code and returns a list of suggestions for improvements."""
|
||||||
function_string = "def analyze_code(code: str) -> List[str]:"
|
function_string = "def analyze_code(code: str) -> List[str]:"
|
||||||
args = [code]
|
args = [code]
|
||||||
description_string = """Analyzes the given code and returns a list of suggestions for improvements."""
|
description_string = """Analyzes the given code and returns a list of suggestions for improvements."""
|
||||||
@@ -39,6 +40,7 @@ def evaluate_code(code: str) -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
def improve_code(suggestions: List[str], code: str) -> str:
|
def improve_code(suggestions: List[str], code: str) -> str:
|
||||||
|
"""Improves the provided code based on the suggestions provided, making no other changes."""
|
||||||
function_string = (
|
function_string = (
|
||||||
"def generate_improved_code(suggestions: List[str], code: str) -> str:"
|
"def generate_improved_code(suggestions: List[str], code: str) -> str:"
|
||||||
)
|
)
|
||||||
@@ -53,6 +55,7 @@ def improve_code(suggestions: List[str], code: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def write_tests(code: str, focus: List[str]) -> str:
|
def write_tests(code: str, focus: List[str]) -> str:
|
||||||
|
"""Generates test cases for the existing code, focusing on specific areas if required."""
|
||||||
function_string = (
|
function_string = (
|
||||||
"def create_test_cases(code: str, focus: Optional[str] = None) -> str:"
|
"def create_test_cases(code: str, focus: Optional[str] = None) -> str:"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import openai
|
|||||||
|
|
||||||
|
|
||||||
def scrape_text(url):
|
def scrape_text(url):
|
||||||
|
"""Scrape text from a webpage"""
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
|
||||||
# Check if the response contains an HTTP error
|
# Check if the response contains an HTTP error
|
||||||
@@ -26,6 +27,7 @@ def scrape_text(url):
|
|||||||
|
|
||||||
|
|
||||||
def extract_hyperlinks(soup):
|
def extract_hyperlinks(soup):
|
||||||
|
"""Extract hyperlinks from a BeautifulSoup object"""
|
||||||
hyperlinks = []
|
hyperlinks = []
|
||||||
for link in soup.find_all('a', href=True):
|
for link in soup.find_all('a', href=True):
|
||||||
hyperlinks.append((link.text, link['href']))
|
hyperlinks.append((link.text, link['href']))
|
||||||
@@ -33,6 +35,7 @@ def extract_hyperlinks(soup):
|
|||||||
|
|
||||||
|
|
||||||
def format_hyperlinks(hyperlinks):
|
def format_hyperlinks(hyperlinks):
|
||||||
|
"""Format hyperlinks into a list of strings"""
|
||||||
formatted_links = []
|
formatted_links = []
|
||||||
for link_text, link_url in hyperlinks:
|
for link_text, link_url in hyperlinks:
|
||||||
formatted_links.append(f"{link_text} ({link_url})")
|
formatted_links.append(f"{link_text} ({link_url})")
|
||||||
@@ -40,6 +43,7 @@ def format_hyperlinks(hyperlinks):
|
|||||||
|
|
||||||
|
|
||||||
def scrape_links(url):
|
def scrape_links(url):
|
||||||
|
"""Scrape hyperlinks from a webpage"""
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
|
||||||
# Check if the response contains an HTTP error
|
# Check if the response contains an HTTP error
|
||||||
@@ -57,6 +61,7 @@ def scrape_links(url):
|
|||||||
|
|
||||||
|
|
||||||
def split_text(text, max_length=8192):
|
def split_text(text, max_length=8192):
|
||||||
|
"""Split text into chunks of a maximum length"""
|
||||||
paragraphs = text.split("\n")
|
paragraphs = text.split("\n")
|
||||||
current_length = 0
|
current_length = 0
|
||||||
current_chunk = []
|
current_chunk = []
|
||||||
@@ -75,6 +80,7 @@ def split_text(text, max_length=8192):
|
|||||||
|
|
||||||
|
|
||||||
def summarize_text(text, is_website=True):
|
def summarize_text(text, is_website=True):
|
||||||
|
"""Summarize text using GPT-3"""
|
||||||
if text == "":
|
if text == "":
|
||||||
return "Error: No text to summarize"
|
return "Error: No text to summarize"
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ def chat_with_ai(
|
|||||||
permanent_memory,
|
permanent_memory,
|
||||||
token_limit,
|
token_limit,
|
||||||
debug=False):
|
debug=False):
|
||||||
|
"""Interact with the OpenAI API, sending the prompt, user input, message history, and permanent memory."""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ cfg = Config()
|
|||||||
|
|
||||||
|
|
||||||
def get_command(response):
|
def get_command(response):
|
||||||
|
"""Parse the response and return the command name and arguments"""
|
||||||
try:
|
try:
|
||||||
response_json = json.loads(response)
|
response_json = json.loads(response)
|
||||||
command = response_json["command"]
|
command = response_json["command"]
|
||||||
@@ -30,6 +31,7 @@ def get_command(response):
|
|||||||
|
|
||||||
|
|
||||||
def execute_command(command_name, arguments):
|
def execute_command(command_name, arguments):
|
||||||
|
"""Execute the command and return the response"""
|
||||||
try:
|
try:
|
||||||
if command_name == "google":
|
if command_name == "google":
|
||||||
return google_search(arguments["input"])
|
return google_search(arguments["input"])
|
||||||
@@ -93,11 +95,13 @@ def execute_command(command_name, arguments):
|
|||||||
|
|
||||||
|
|
||||||
def get_datetime():
|
def get_datetime():
|
||||||
|
"""Return the current date and time"""
|
||||||
return "Current date and time: " + \
|
return "Current date and time: " + \
|
||||||
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
def google_search(query, num_results=8):
|
def google_search(query, num_results=8):
|
||||||
|
"""Return the results of a google search"""
|
||||||
search_results = []
|
search_results = []
|
||||||
for j in browse.search(query, num_results=num_results):
|
for j in browse.search(query, num_results=num_results):
|
||||||
search_results.append(j)
|
search_results.append(j)
|
||||||
@@ -106,6 +110,7 @@ def google_search(query, num_results=8):
|
|||||||
|
|
||||||
|
|
||||||
def browse_website(url):
|
def browse_website(url):
|
||||||
|
"""Return the results of a google search"""
|
||||||
summary = get_text_summary(url)
|
summary = get_text_summary(url)
|
||||||
links = get_hyperlinks(url)
|
links = get_hyperlinks(url)
|
||||||
|
|
||||||
@@ -119,23 +124,27 @@ def browse_website(url):
|
|||||||
|
|
||||||
|
|
||||||
def get_text_summary(url):
|
def get_text_summary(url):
|
||||||
|
"""Return the results of a google search"""
|
||||||
text = browse.scrape_text(url)
|
text = browse.scrape_text(url)
|
||||||
summary = browse.summarize_text(text)
|
summary = browse.summarize_text(text)
|
||||||
return """ "Result" : """ + summary
|
return """ "Result" : """ + summary
|
||||||
|
|
||||||
|
|
||||||
def get_hyperlinks(url):
|
def get_hyperlinks(url):
|
||||||
|
"""Return the results of a google search"""
|
||||||
link_list = browse.scrape_links(url)
|
link_list = browse.scrape_links(url)
|
||||||
return link_list
|
return link_list
|
||||||
|
|
||||||
|
|
||||||
def commit_memory(string):
|
def commit_memory(string):
|
||||||
|
"""Commit a string to memory"""
|
||||||
_text = f"""Committing memory with string "{string}" """
|
_text = f"""Committing memory with string "{string}" """
|
||||||
mem.permanent_memory.append(string)
|
mem.permanent_memory.append(string)
|
||||||
return _text
|
return _text
|
||||||
|
|
||||||
|
|
||||||
def delete_memory(key):
|
def delete_memory(key):
|
||||||
|
"""Delete a memory with a given key"""
|
||||||
if key >= 0 and key < len(mem.permanent_memory):
|
if key >= 0 and key < len(mem.permanent_memory):
|
||||||
_text = "Deleting memory with key " + str(key)
|
_text = "Deleting memory with key " + str(key)
|
||||||
del mem.permanent_memory[key]
|
del mem.permanent_memory[key]
|
||||||
@@ -147,6 +156,7 @@ def delete_memory(key):
|
|||||||
|
|
||||||
|
|
||||||
def overwrite_memory(key, string):
|
def overwrite_memory(key, string):
|
||||||
|
"""Overwrite a memory with a given key"""
|
||||||
if key >= 0 and key < len(mem.permanent_memory):
|
if key >= 0 and key < len(mem.permanent_memory):
|
||||||
_text = "Overwriting memory with key " + \
|
_text = "Overwriting memory with key " + \
|
||||||
str(key) + " and string " + string
|
str(key) + " and string " + string
|
||||||
@@ -159,11 +169,13 @@ def overwrite_memory(key, string):
|
|||||||
|
|
||||||
|
|
||||||
def shutdown():
|
def shutdown():
|
||||||
|
"""Shut down the program"""
|
||||||
print("Shutting down...")
|
print("Shutting down...")
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
||||||
def start_agent(name, task, prompt, model="gpt-3.5-turbo"):
|
def start_agent(name, task, prompt, model="gpt-3.5-turbo"):
|
||||||
|
"""Start an agent with a given name, task, and prompt"""
|
||||||
global cfg
|
global cfg
|
||||||
|
|
||||||
# Remove underscores from name
|
# Remove underscores from name
|
||||||
@@ -187,6 +199,7 @@ def start_agent(name, task, prompt, model="gpt-3.5-turbo"):
|
|||||||
|
|
||||||
|
|
||||||
def message_agent(key, message):
|
def message_agent(key, message):
|
||||||
|
"""Message an agent with a given key and message"""
|
||||||
global cfg
|
global cfg
|
||||||
agent_response = agents.message_agent(key, message)
|
agent_response = agents.message_agent(key, message)
|
||||||
|
|
||||||
@@ -198,10 +211,12 @@ def message_agent(key, message):
|
|||||||
|
|
||||||
|
|
||||||
def list_agents():
|
def list_agents():
|
||||||
|
"""List all agents"""
|
||||||
return agents.list_agents()
|
return agents.list_agents()
|
||||||
|
|
||||||
|
|
||||||
def delete_agent(key):
|
def delete_agent(key):
|
||||||
|
"""Delete an agent with a given key"""
|
||||||
result = agents.delete_agent(key)
|
result = agents.delete_agent(key)
|
||||||
if not result:
|
if not result:
|
||||||
return f"Agent {key} does not exist."
|
return f"Agent {key} does not exist."
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class Singleton(type):
|
|||||||
_instances = {}
|
_instances = {}
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
def __call__(cls, *args, **kwargs):
|
||||||
|
"""Call method for the singleton metaclass."""
|
||||||
if cls not in cls._instances:
|
if cls not in cls._instances:
|
||||||
cls._instances[cls] = super(
|
cls._instances[cls] = super(
|
||||||
Singleton, cls).__call__(
|
Singleton, cls).__call__(
|
||||||
@@ -19,11 +20,14 @@ class Config(metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""Initialize the configuration class."""
|
||||||
self.continuous_mode = False
|
self.continuous_mode = False
|
||||||
self.speak_mode = False
|
self.speak_mode = False
|
||||||
|
|
||||||
def set_continuous_mode(self, value: bool):
|
def set_continuous_mode(self, value: bool):
|
||||||
|
"""Set the continuous mode value."""
|
||||||
self.continuous_mode = value
|
self.continuous_mode = value
|
||||||
|
|
||||||
def set_speak_mode(self, value: bool):
|
def set_speak_mode(self, value: bool):
|
||||||
|
"""Set the speak mode value."""
|
||||||
self.speak_mode = value
|
self.speak_mode = value
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
def load_prompt():
|
def load_prompt():
|
||||||
|
"""Load the prompt from data/prompt.txt"""
|
||||||
try:
|
try:
|
||||||
# Load the promt from data/prompt.txt
|
# Load the promt from data/prompt.txt
|
||||||
with open("data/prompt.txt", "r") as prompt_file:
|
with open("data/prompt.txt", "r") as prompt_file:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def execute_python_file(file):
|
def execute_python_file(file):
|
||||||
|
"""Execute a Python file in a Docker container and return the output"""
|
||||||
workspace_folder = "auto_gpt_workspace"
|
workspace_folder = "auto_gpt_workspace"
|
||||||
|
|
||||||
if not file.endswith(".py"):
|
if not file.endswith(".py"):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ if not os.path.exists(working_directory):
|
|||||||
|
|
||||||
|
|
||||||
def safe_join(base, *paths):
|
def safe_join(base, *paths):
|
||||||
|
"""Join one or more path components intelligently."""
|
||||||
new_path = os.path.join(base, *paths)
|
new_path = os.path.join(base, *paths)
|
||||||
norm_new_path = os.path.normpath(new_path)
|
norm_new_path = os.path.normpath(new_path)
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ def safe_join(base, *paths):
|
|||||||
|
|
||||||
|
|
||||||
def read_file(filename):
|
def read_file(filename):
|
||||||
|
"""Read a file and return the contents"""
|
||||||
try:
|
try:
|
||||||
filepath = safe_join(working_directory, filename)
|
filepath = safe_join(working_directory, filename)
|
||||||
with open(filepath, "r") as f:
|
with open(filepath, "r") as f:
|
||||||
@@ -29,6 +31,7 @@ def read_file(filename):
|
|||||||
|
|
||||||
|
|
||||||
def write_to_file(filename, text):
|
def write_to_file(filename, text):
|
||||||
|
"""Write text to a file"""
|
||||||
try:
|
try:
|
||||||
filepath = safe_join(working_directory, filename)
|
filepath = safe_join(working_directory, filename)
|
||||||
with open(filepath, "w") as f:
|
with open(filepath, "w") as f:
|
||||||
@@ -39,6 +42,7 @@ def write_to_file(filename, text):
|
|||||||
|
|
||||||
|
|
||||||
def append_to_file(filename, text):
|
def append_to_file(filename, text):
|
||||||
|
"""Append text to a file"""
|
||||||
try:
|
try:
|
||||||
filepath = safe_join(working_directory, filename)
|
filepath = safe_join(working_directory, filename)
|
||||||
with open(filepath, "a") as f:
|
with open(filepath, "a") as f:
|
||||||
@@ -49,6 +53,7 @@ def append_to_file(filename, text):
|
|||||||
|
|
||||||
|
|
||||||
def delete_file(filename):
|
def delete_file(filename):
|
||||||
|
"""Delete a file"""
|
||||||
try:
|
try:
|
||||||
filepath = safe_join(working_directory, filename)
|
filepath = safe_join(working_directory, filename)
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# This file contains the API keys for the various APIs used in the project.
|
||||||
# Get yours from: https://beta.openai.com/account/api-keys
|
# Get yours from: https://beta.openai.com/account/api-keys
|
||||||
OPENAI_API_KEY = "YOUR-OPENAI-KEY"
|
OPENAI_API_KEY = "YOUR-OPENAI-KEY"
|
||||||
# To access your ElevenLabs API key, head to https://elevenlabs.io, you
|
# To access your ElevenLabs API key, head to https://elevenlabs.io, you
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from config import Config
|
|||||||
|
|
||||||
|
|
||||||
class Argument(Enum):
|
class Argument(Enum):
|
||||||
|
"""This class is used to define the different arguments that can be passed"""
|
||||||
CONTINUOUS_MODE = "continuous-mode"
|
CONTINUOUS_MODE = "continuous-mode"
|
||||||
SPEAK_MODE = "speak-mode"
|
SPEAK_MODE = "speak-mode"
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ def print_to_console(
|
|||||||
speak_text=False,
|
speak_text=False,
|
||||||
min_typing_speed=0.05,
|
min_typing_speed=0.05,
|
||||||
max_typing_speed=0.01):
|
max_typing_speed=0.01):
|
||||||
|
"""Prints text to the console with a typing effect"""
|
||||||
global cfg
|
global cfg
|
||||||
if speak_text and cfg.speak_mode:
|
if speak_text and cfg.speak_mode:
|
||||||
speak.say_text(f"{title}. {content}")
|
speak.say_text(f"{title}. {content}")
|
||||||
@@ -44,6 +46,7 @@ def print_to_console(
|
|||||||
|
|
||||||
|
|
||||||
def print_assistant_thoughts(assistant_reply):
|
def print_assistant_thoughts(assistant_reply):
|
||||||
|
"""Prints the assistant's thoughts to the console"""
|
||||||
global ai_name
|
global ai_name
|
||||||
global cfg
|
global cfg
|
||||||
try:
|
try:
|
||||||
@@ -102,6 +105,7 @@ def print_assistant_thoughts(assistant_reply):
|
|||||||
|
|
||||||
|
|
||||||
def construct_prompt():
|
def construct_prompt():
|
||||||
|
"""Constructs the prompt for the AI"""
|
||||||
global ai_name
|
global ai_name
|
||||||
# Construct the prompt
|
# Construct the prompt
|
||||||
print_to_console(
|
print_to_console(
|
||||||
@@ -165,6 +169,7 @@ def construct_prompt():
|
|||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
def parse_arguments():
|
||||||
|
"""Parses the arguments passed to the script"""
|
||||||
global cfg
|
global cfg
|
||||||
cfg.set_continuous_mode(False)
|
cfg.set_continuous_mode(False)
|
||||||
cfg.set_speak_mode(False)
|
cfg.set_speak_mode(False)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ tts_headers = {
|
|||||||
|
|
||||||
|
|
||||||
def say_text(text, voice_index=0):
|
def say_text(text, voice_index=0):
|
||||||
|
"""Say text using ElevenLabs API"""
|
||||||
tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format(
|
tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format(
|
||||||
voice_id=voices[voice_index])
|
voice_id=voices[voice_index])
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import time
|
|||||||
|
|
||||||
|
|
||||||
class Spinner:
|
class Spinner:
|
||||||
|
"""A simple spinner class"""
|
||||||
def __init__(self, message="Loading...", delay=0.1):
|
def __init__(self, message="Loading...", delay=0.1):
|
||||||
|
"""Initialize the spinner class"""
|
||||||
self.spinner = itertools.cycle(['-', '/', '|', '\\'])
|
self.spinner = itertools.cycle(['-', '/', '|', '\\'])
|
||||||
self.delay = delay
|
self.delay = delay
|
||||||
self.message = message
|
self.message = message
|
||||||
@@ -13,6 +15,7 @@ class Spinner:
|
|||||||
self.spinner_thread = None
|
self.spinner_thread = None
|
||||||
|
|
||||||
def spin(self):
|
def spin(self):
|
||||||
|
"""Spin the spinner"""
|
||||||
while self.running:
|
while self.running:
|
||||||
sys.stdout.write(next(self.spinner) + " " + self.message + "\r")
|
sys.stdout.write(next(self.spinner) + " " + self.message + "\r")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@@ -20,11 +23,13 @@ class Spinner:
|
|||||||
sys.stdout.write('\b' * (len(self.message) + 2))
|
sys.stdout.write('\b' * (len(self.message) + 2))
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
"""Start the spinner"""
|
||||||
self.running = True
|
self.running = True
|
||||||
self.spinner_thread = threading.Thread(target=self.spin)
|
self.spinner_thread = threading.Thread(target=self.spin)
|
||||||
self.spinner_thread.start()
|
self.spinner_thread.start()
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||||
|
"""Stop the spinner"""
|
||||||
self.running = False
|
self.running = False
|
||||||
self.spinner_thread.join()
|
self.spinner_thread.join()
|
||||||
sys.stdout.write('\r' + ' ' * (len(self.message) + 2) + '\r')
|
sys.stdout.write('\r' + ' ' * (len(self.message) + 2) + '\r')
|
||||||
|
|||||||
Reference in New Issue
Block a user