diff --git a/config/template_config.ini b/config/template_config.ini index 53ec169..78abe7a 100644 --- a/config/template_config.ini +++ b/config/template_config.ini @@ -3,11 +3,14 @@ # Config file parameters [Config] ; Config file version. DO NOT EDIT THIS! -version = 1 +version = 2 ; Set this to no when you are done editing the file is_template = yes # Telegram bot parameters [Telegram] ; Your bot token goes here. Get one from @BotFather! -token = 123456789:YOUR_TOKEN_GOES_HERE_______________ \ No newline at end of file +token = 123456789:YOUR_TOKEN_GOES_HERE_______________ +; Time in seconds before a conversation with no new messages expires +; A lower value reduces memory usage but can be inconvenient for the users +conversation_timeout = 7200 \ No newline at end of file diff --git a/configloader.py b/configloader.py new file mode 100644 index 0000000..0893add --- /dev/null +++ b/configloader.py @@ -0,0 +1,42 @@ +import sys +import os +import configparser + +def load_config() -> configparser.ConfigParser: + # Check if a configuration file exists, create one if it doesn't and get the template version number. + with open("config/template_config.ini") as template_file: + # Check if the config file exists + if not os.path.isfile("config/config.ini"): + # Copy the template file to the config file + with open("config/config.ini", "w") as config_file: + config_file.write(template_file.read()) + # Find the template version number + config = configparser.ConfigParser() + config.read_file(template_file) + template_version = int(config["Config"]["version"]) + + # Overwrite the template config with the values in the config + with open("config/config.ini") as config_file: + config.read_file(config_file) + + # Check if the file has been edited + if config["Config"]["is_template"] == "yes": + print("A config file has been created in config/config.ini.\n" + "Edit it with your configuration, set the is_template flag to false and restart this script.") + sys.exit(1) + + # Check if the version has changed from the template + if template_version > int(config["Config"]["version"]): + # Reset the is_template flag + config["Config"]["is_template"] = "yes" + # Update the config version + config["Config"]["version"] = str(template_version) + # Save the file + with open("config/config.ini", "w") as config_file: + config.write(config_file) + # Notify the user and quit + print("The config file in config/config.ini has been updated.\n" + "Edit it with the new required data, set the is_template flag to true and restart this script.") + sys.exit(1) + + return config \ No newline at end of file diff --git a/core.py b/core.py index 9ed44eb..b7cf678 100644 --- a/core.py +++ b/core.py @@ -1,49 +1,15 @@ -import os import sys -import configparser import telegram import time import strings import worker +import configloader def main(): """The core code of the program. Should be run only in the main process!""" - # Check if a configuration file exists, create one if it doesn't and get the template version number. - with open("config/template_config.ini") as template_file: - # Check if the config file exists - if not os.path.isfile("config/config.ini"): - # Copy the template file to the config file - with open("config/config.ini", "w") as config_file: - config_file.write(template_file.read()) - # Find the template version number - config = configparser.ConfigParser() - config.read_file(template_file) - template_version = int(config["Config"]["version"]) - - # Overwrite the template config with the values in the config - with open("config/config.ini") as config_file: - config.read_file(config_file) - - # Check if the file has been edited - if config["Config"]["is_template"] == "yes": - print("A config file has been created in config/config.ini.\n" - "Edit it with your configuration, set the is_template flag to false and restart this script.") - sys.exit(1) - - # Check if the version has changed from the template - if template_version > int(config["Config"]["version"]): - # Reset the is_template flag - config["Config"]["is_template"] = "yes" - # Update the config version - config["Config"]["version"] = str(template_version) - # Save the file - with open("config/config.ini", "w") as config_file: - config.write(config_file) - # Notify the user and quit - print("The config file in config/config.ini has been updated.\n" - "Edit it with the new required data, set the is_template flag to true and restart this script.") - sys.exit(1) + # Load the config from config.ini + config = configloader.load_config() # Create a bot instance bot = telegram.Bot(config["Telegram"]["token"]) @@ -95,8 +61,8 @@ def main(): continue # Otherwise, forward the update to the corresponding worker receiving_worker = chat_workers.get(update.message.chat.id) - # Ensure a worker exists for the chat - if receiving_worker is None: + # Ensure a worker exists for the chat and is alive + if receiving_worker is None or not receiving_worker.process.is_alive(): # Suggest that the user restarts the chat with /start bot.send_message(update.message.chat.id, strings.error_no_worker_for_chat) # Skip the update @@ -119,7 +85,8 @@ def main(): if len(updates): # Mark them as read by increasing the update_offset next_update = updates[-1].update_id + 1 - # Temporarily prevent rate limits (remove this later) + # Temporarily prevent rate limits + # TODO: (remove this later) time.sleep(5) diff --git a/strings.py b/strings.py index 4115cb6..747b844 100644 --- a/strings.py +++ b/strings.py @@ -6,9 +6,13 @@ conversation_after_start = "Ciao!\n" \ "Benvenuto su greed!" +# Notification: the conversation has expired +conversation_expired = "🕐 Il bot non ha ricevuto messaggi per un po' di tempo, quindi ha chiuso la conversazione.\n" \ + "Per riavviarne una nuova, invia il comando /start." + # Error: message received not in a private chat error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private." # Error: a message was sent in a chat, but no worker exists for that chat. Suggest the creation of a new worker with /start -error_no_worker_for_chat = "⚠️ La conversazione con il bot si è interrotta.\n" \ +error_no_worker_for_chat = "⚠️ La conversazione con il bot è interrotta.\n" \ "Per riavviarla, manda il comando /start al bot." diff --git a/worker.py b/worker.py index 3a86163..2b8ed34 100644 --- a/worker.py +++ b/worker.py @@ -1,6 +1,11 @@ import multiprocessing import telegram import strings +import configloader +import sys + +# Load the configuration +config = configloader.load_config() class StopSignal: """A data class that should be sent to the worker when the conversation has to be stopped abnormally.""" @@ -31,26 +36,33 @@ class ChatWorker: # Wait for the process to stop self.process.join() +# TODO: maybe move these functions to a class -def receive_next_update(pipe) -> telegram.Update: +def graceful_stop(bot: telegram.Bot, chat: telegram.Chat, pipe): + """Handle the graceful stop of the process.""" + # Notify the user that the session has expired + bot.send_message(chat.id, strings.conversation_expired) + # End the process + sys.exit(0) + +def receive_next_update(bot: telegram.Bot, chat: telegram.Chat, pipe) -> telegram.Update: """Get the next update from a pipe. If no update is found, block the process until one is received. If a stop signal is sent, try to gracefully stop the process.""" + # Wait until some data is present in the pipe or the wait time runs out + if not pipe.poll(int(config["Telegram"]["conversation_timeout"])): + # If the conversation times out, gracefully stop the process + graceful_stop(bot, chat, pipe) # Receive data from the pipe data = pipe.recv() # Check if the data is a stop signal instance if isinstance(data, StopSignal): # Gracefully stop the process - graceful_stop() + graceful_stop(bot, chat, pipe) # Return the received update return data -def graceful_stop(): - """Handle the graceful stop of the process.""" - raise NotImplementedError() - - def conversation_handler(bot: telegram.Bot, chat: telegram.Chat, pipe): """This function is ran once for every conversation (/start command) by a separate process.""" # TODO: catch all the possible exceptions @@ -59,5 +71,5 @@ def conversation_handler(bot: telegram.Bot, chat: telegram.Chat, pipe): # TODO: Send a command list or something while True: # For now, echo the sent message - update = receive_next_update(pipe) - bot.send_message(chat.id, update.message.text) \ No newline at end of file + update = receive_next_update(bot, chat, pipe) + bot.send_message(chat.id, f"{multiprocessing.current_process().name} {update.message.text}") \ No newline at end of file