From 5f04a1ffe93de7fc4f9aeb64118008f1f821062d Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Thu, 21 Dec 2017 10:42:23 +0100 Subject: [PATCH] Start working on the code for the bot --- core.py | 2 +- database.py | 10 +++---- strings.py | 16 ++++++++++ worker.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 99 insertions(+), 15 deletions(-) diff --git a/core.py b/core.py index 0a5ee22..91c2d08 100644 --- a/core.py +++ b/core.py @@ -75,7 +75,7 @@ def main(): # Skip the update continue # If the message is a start command... - if update.message.text == "/start": + if update.message.text is not None and update.message.text == "/start": # Check if a worker already exists for that chat old_worker = chat_workers.get(update.message.chat.id) # If it exists, gracefully stop the worker diff --git a/database.py b/database.py index d098679..8c43211 100644 --- a/database.py +++ b/database.py @@ -33,14 +33,14 @@ class User(TableDeclarativeBase): # Extra table parameters __tablename__ = "users" - def __init__(self, telegram_user: telegram.User, **kwargs): + def __init__(self, telegram_chat: telegram.Chat, **kwargs): # Initialize the super super().__init__(**kwargs) # Get the data from telegram - self.id = telegram_user.id - self.first_name = telegram_user.first_name - self.last_name = telegram_user.last_name - self.username = telegram_user.username + self.user_id = telegram_chat.id + self.first_name = telegram_chat.first_name + self.last_name = telegram_chat.last_name + self.username = telegram_chat.username # The starting wallet value is 0 self.credit = decimal.Decimal("0") diff --git a/strings.py b/strings.py index bd7aef8..fb55e26 100644 --- a/strings.py +++ b/strings.py @@ -15,10 +15,26 @@ in_stock_format_string = "{quantity} disponibili" conversation_after_start = "Ciao!\n" \ "Benvenuto su greed!" +# Answer: to send an inline keyboard you need to send a message with it +conversation_open_user_menu = "Allora, {username}, cosa vorresti fare?" + # 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." +# User menu: order +menu_order = "🛍 Ordina" + + +# User menu: order status +menu_order_status = "❓ Stato ordini" + +# User menu: add credit +menu_add_credit = "đŸ’ĩ Ricarica" + +# User menu: bot info +menu_info = "ℹī¸ Informazioni sul bot" + # Error: message received not in a private chat error_nonprivate_chat = "⚠ī¸ Questo bot funziona solo in chat private." diff --git a/worker.py b/worker.py index 9a98537..07d8072 100644 --- a/worker.py +++ b/worker.py @@ -1,9 +1,11 @@ import threading +import typing import telegram import strings import configloader import sys import queue as queuem +import database as db class StopSignal: """A data class that should be sent to the worker when the conversation has to be stopped abnormally.""" @@ -21,6 +23,11 @@ class ChatWorker(threading.Thread): # Store the bot and chat info inside the class self.bot = bot self.chat = chat + # Open a new database session + self.session = db.Session() + # Get the user db data from the users and admin tables + self.user = self.session.query(db.User).filter(db.User.user_id == self.chat.id).one_or_none() + self.admin = self.session.query(db.Admin).filter(db.Admin.user_id == self.chat.id).one_or_none() # The sending pipe is stored in the ChatWorker class, allowing the forwarding of messages to the chat process self.queue = queuem.Queue() @@ -29,11 +36,20 @@ class ChatWorker(threading.Thread): # TODO: catch all the possible exceptions # Welcome the user to the bot self.bot.send_message(self.chat.id, strings.conversation_after_start) - # TODO: Send a command list or something - while True: - # For now, echo the sent message - update = self._receive_next_update() - self.bot.send_message(self.chat.id, f"{threading.current_thread().name} {update.message.text}") + # If the user isn't registered, create a new record and add it to the db + if self.user is None: + # Create the new record + self.user = db.User(self.chat) + # Add the new record to the db + self.session.add(self.user) + # Commit the transaction + self.session.commit() + # If the user is not an admin, send him to the user menu + if self.admin is None: + self.__user_menu() + # If the user is an admin, send him to the admin menu + else: + self.__admin_menu() def stop(self, reason: str=""): """Gracefully stop the worker process""" @@ -42,7 +58,7 @@ class ChatWorker(threading.Thread): # Wait for the thread to stop self.join() - def _receive_next_update(self) -> telegram.Update: + def __receive_next_update(self) -> telegram.Update: """Get the next update from the queue. If no update is found, block the process until one is received. If a stop signal is sent, try to gracefully stop the thread.""" @@ -51,17 +67,69 @@ class ChatWorker(threading.Thread): data = self.queue.get(timeout=int(configloader.config["Telegram"]["conversation_timeout"])) except queuem.Empty: # If the conversation times out, gracefully stop the thread - self._graceful_stop() + self.__graceful_stop() # Check if the data is a stop signal instance if isinstance(data, StopSignal): # Gracefully stop the process - self._graceful_stop() + self.__graceful_stop() # Return the received update return data - def _graceful_stop(self): + def __wait_for_specific_message(self, items:typing.List[str]) -> str: + """Continue getting updates until until one of the strings contained in the list is received as a message.""" + while True: + # Get the next update + update = self.__receive_next_update() + # Ensure the update contains a message + if update.message is None: + continue + # Ensure the message contains text + if update.message.text is None: + continue + # Check if the message is contained in the list + if update.message.text not in items: + continue + # Return the message text + return update.message.text + + def __user_menu(self): + """Function called from the run method when the user is not an administrator. + Normal bot actions should be placed here.""" + # Create a keyboard with the user main menu + keyboard = [[telegram.KeyboardButton(strings.menu_order)], + [telegram.KeyboardButton(strings.menu_order_status)], + [telegram.KeyboardButton(strings.menu_add_credit)], + [telegram.KeyboardButton(strings.menu_info)]] + # Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) + self.bot.send_message(self.chat.id, strings.conversation_open_user_menu.format(username=str(self.user)), + reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) + # Wait for a reply from the user + # TODO: change this + selection = self.__wait_for_specific_message([strings.menu_order, strings.menu_order_status, + strings.menu_add_credit, strings.menu_info]) + # If the user has selected the Order option... + if selection == strings.menu_order: + ... + # If the user has selected the Order Status option... + elif selection == strings.menu_order_status: + ... + # If the user has selected the Add Credit option... + elif selection == strings.menu_add_credit: + ... + # If the user has selected the Bot Info option... + elif selection == strings.menu_info: + ... + + + def __admin_menu(self): + """Function called from the run method when the user is an administrator. + Administrative bot actions should be placed here.""" + self.bot.send_message(self.chat.id, "Sei un Amministralol") + + def __graceful_stop(self): """Handle the graceful stop of the thread.""" # Notify the user that the session has expired self.bot.send_message(self.chat.id, strings.conversation_expired) + # Close the database session # End the process sys.exit(0)