diff --git a/core.py b/core.py index a318191..781dc1d 100644 --- a/core.py +++ b/core.py @@ -46,7 +46,7 @@ def main(): log.debug("Bot token is valid!") # Finding default language - default_language = configloader.config["Config"]["language"] + default_language = configloader.config["Language"]["default_language"] # Creating localization object default_loc = localization.Localization(language=default_language, fallback=default_language) diff --git a/database.py b/database.py index 0745056..8d8b99b 100644 --- a/database.py +++ b/database.py @@ -7,14 +7,11 @@ import configloader import telegram import requests import utils -import importlib +import localization import logging log = logging.getLogger(__name__) -language = configloader.config["Config"]["language"] -strings = importlib.import_module("strings." + language) - # Create a (lazy) database engine engine = create_engine(configloader.config["Database"]["engine"]) @@ -50,7 +47,8 @@ class User(TableDeclarativeBase): self.first_name = telegram_user.first_name self.last_name = telegram_user.last_name self.username = telegram_user.username - self.language = telegram_user.language_code if telegram_user.language_code else configloader.config["Language"]["default_language"] + self.language = telegram_user.language_code if telegram_user.language_code else configloader.config["Language"][ + "default_language"] # The starting wallet value is 0 self.credit = 0 @@ -111,40 +109,37 @@ class Product(TableDeclarativeBase): # No __init__ is needed, the default one is sufficient - def __str__(self): - return self.text() - - def text(self, style: str="full", cart_qty: int=None): + def text(self, *, loc: localization.Localization, style: str = "full", cart_qty: int = None): """Return the product details formatted with Telegram HTML. The image is omitted.""" if style == "short": - return f"{cart_qty}x {utils.telegram_html_escape(self.name)} - {str(utils.Price(self.price) * cart_qty)}" + return f"{cart_qty}x {utils.telegram_html_escape(self.name)} - {str(utils.Price(self.price, loc) * cart_qty)}" elif style == "full": if cart_qty is not None: - cart = strings.in_cart_format_string.format(quantity=cart_qty) + cart = loc.get("in_cart_format_string", quantity=cart_qty) else: cart = '' - return strings.product_format_string.format(name=utils.telegram_html_escape(self.name), - description=utils.telegram_html_escape(self.description), - price=str(utils.Price(self.price)), - cart=cart) + return loc.get("product_format_string", name=utils.telegram_html_escape(self.name), + description=utils.telegram_html_escape(self.description), + price=str(utils.Price(self.price, loc)), + cart=cart) else: raise ValueError("style is not an accepted value") def __repr__(self): return f"" - def send_as_message(self, chat_id: int) -> dict: + def send_as_message(self, loc: localization.Localization, chat_id: int) -> dict: """Send a message containing the product data.""" if self.image is None: r = requests.get(f"https://api.telegram.org/bot{configloader.config['Telegram']['token']}/sendMessage", params={"chat_id": chat_id, - "text": self.text(), + "text": self.text(loc=loc), "parse_mode": "HTML"}) else: r = requests.post(f"https://api.telegram.org/bot{configloader.config['Telegram']['token']}/sendPhoto", files={"photo": self.image}, params={"chat_id": chat_id, - "caption": self.text(), + "caption": self.text(loc=loc), "parse_mode": "HTML"}) return r.json() @@ -193,10 +188,10 @@ class Transaction(TableDeclarativeBase): __tablename__ = "transactions" __table_args__ = (UniqueConstraint("provider", "provider_charge_id"),) - def __str__(self): - string = f"T{self.transaction_id} | {str(self.user)} | {utils.Price(self.value)}" + def text(self, *, loc: localization.Localization): + string = f"T{self.transaction_id} | {str(self.user)} | {utils.Price(self.value, loc)}" if self.refunded: - string += f" | {strings.emoji_refunded}" + string += f" | {loc.get('emoji_refunded')}" if self.provider: string += f" | {self.provider}" if self.notes: @@ -259,36 +254,38 @@ class Order(TableDeclarativeBase): def __repr__(self): return f"" - def get_text(self, session, user=False): + def text(self, *, loc: localization.Localization, session, user=False): joined_self = session.query(Order).filter_by(order_id=self.order_id).join(Transaction).one() items = "" for item in self.items: items += str(item) + "\n" if self.delivery_date is not None: - status_emoji = strings.emoji_completed - status_text = strings.text_completed + status_emoji = loc.get("emoji_completed") + status_text = loc.get("text_completed") elif self.refund_date is not None: - status_emoji = strings.emoji_refunded - status_text = strings.text_refunded + status_emoji = loc.get("emoji_refunded") + status_text = loc.get("text_refunded") else: - status_emoji = strings.emoji_not_processed - status_text = strings.text_not_processed + status_emoji = loc.get("emoji_not_processed") + status_text = loc.get("text_not_processed") if user and configloader.config["Appearance"]["full_order_info"] == "no": - return strings.user_order_format_string.format(status_emoji=status_emoji, - status_text=status_text, - items=items, - notes=self.notes, - value=str(utils.Price(-joined_self.transaction.value))) + \ - (strings.refund_reason.format(reason=self.refund_reason) if self.refund_date is not None else "") + return loc.get("user_order_format_string", + status_emoji=status_emoji, + status_text=status_text, + items=items, + notes=self.notes, + value=str(utils.Price(-joined_self.transaction.value, loc))) + \ + (loc.get("refund_reason", reason=self.refund_reason) if self.refund_date is not None else "") else: return status_emoji + " " + \ - strings.order_number.format(id=self.order_id) + "\n" + \ - strings.order_format_string.format(user=self.user.mention(), - date=self.creation_date.isoformat(), - items=items, - notes=self.notes if self.notes is not None else "", - value=str(utils.Price(-joined_self.transaction.value))) + \ - (strings.refund_reason.format(reason=self.refund_reason) if self.refund_date is not None else "") + loc.get("order_number", id=self.order_id) + "\n" + \ + loc.get("order_format_string", + user=self.user.mention(), + date=self.creation_date.isoformat(), + items=items, + notes=self.notes if self.notes is not None else "", + value=str(utils.Price(-joined_self.transaction.value, loc))) + \ + (loc.get("refund_reason", reason=self.refund_reason) if self.refund_date is not None else "") class OrderItem(TableDeclarativeBase): @@ -305,8 +302,8 @@ class OrderItem(TableDeclarativeBase): # Extra table parameters __tablename__ = "orderitems" - def __str__(self): - return f"{self.product.name} - {str(utils.Price(self.product.price))}" + def text(self, *, loc: localization.Localization): + return f"{self.product.name} - {str(utils.Price(self.product.price, loc))}" def __repr__(self): return f"" diff --git a/localization.py b/localization.py index ee02f87..e6e6249 100644 --- a/localization.py +++ b/localization.py @@ -42,3 +42,6 @@ class Localization: assert isinstance(string, str) formatter = IgnoreDict(**self.replacements, **kwargs) return string.format_map(formatter) + + def boolmoji(self, boolean: bool) -> str: + return self.get("emoji_yes") if boolean else self.get("emoji_no") diff --git a/strings/en.py b/strings/en.py index f153e77..55d1387 100644 --- a/strings/en.py +++ b/strings/en.py @@ -118,6 +118,9 @@ conversation_open_help_menu = "What kind of help do you need?" conversation_confirm_admin_promotion = "Are you sure you want to promote this user to ๐Ÿ’ผ Manager?\n" \ "It is an irreversible action!" +# Conversation: language select menu header +conversation_language_select = "Select a language:" + # Conversation: switching to user mode conversation_switch_to_user_mode = " You are switching to ๐Ÿ‘ค Customer mode.\n" \ "If you want to go back to the ๐Ÿ’ผ Manager menu, restart the conversation with /start." @@ -214,6 +217,9 @@ menu_csv = "๐Ÿ“„ .csv" # Menu: edit admins list menu_edit_admins = "๐Ÿต Edit Managers" +# Menu: language +menu_language = "๐Ÿ‡ฌ๐Ÿ‡ง Language" + # Emoji: unprocessed order emoji_not_processed = "*๏ธโƒฃ" diff --git a/strings/it.py b/strings/it.py index 5499ede..0392576 100644 --- a/strings/it.py +++ b/strings/it.py @@ -118,6 +118,9 @@ conversation_open_help_menu = "Che tipo di assistenza desideri ricevere?" conversation_confirm_admin_promotion = "Sei sicuro di voler promuovere questo utente a ๐Ÿ’ผ Gestore?\n" \ "E' un'azione irreversibile!" +# Conversation: language select menu header +conversation_language_select = "Scegli una lingua:" + # Conversation: switching to user mode conversation_switch_to_user_mode = "Stai passando alla modalitร  ๐Ÿ‘ค Cliente.\n" \ "Se vuoi riassumere il ruolo di ๐Ÿ’ผ Gestore, riavvia la conversazione con /start." @@ -214,6 +217,9 @@ menu_csv = "๐Ÿ“„ .csv" # Menu: edit admins list menu_edit_admins = "๐Ÿต Modifica gestori" +# Menu: language +menu_language = "๐Ÿ‡ฎ๐Ÿ‡น Lingua" + # Emoji: unprocessed order emoji_not_processed = "*๏ธโƒฃ" diff --git a/utils.py b/utils.py index d36bc6d..0b2e47d 100644 --- a/utils.py +++ b/utils.py @@ -8,18 +8,12 @@ import sys import importlib import logging import traceback +import localization log = logging.getLogger(__name__) -language = config["Config"]["language"] -try: - strings = importlib.import_module("strings." + language) -except ModuleNotFoundError: - print("The strings file you specified in the config file does not exist.") - sys.exit(1) - if config["Error Reporting"]["sentry_token"] != \ "https://00000000000000000000000000000000:00000000000000000000000000000000@sentry.io/0000000": import raven @@ -39,7 +33,9 @@ class Price: """The base class for the prices in greed. Its int value is in minimum units, while its float and str values are in decimal format.int(""" - def __init__(self, value: typing.Union[int, float, str, "Price"] = 0): + def __init__(self, value: typing.Union[int, float, str, "Price"], loc: localization.Localization): + # Keep a reference to the localization file + self.loc = loc if isinstance(value, int): # Keep the value as it is self.value = int(value) @@ -57,9 +53,9 @@ class Price: return f"" def __str__(self): - return strings.currency_format_string.format(symbol=(config["Payments"]["currency_symbol"] or strings.currency_symbol), - value="{0:.2f}".format( - self.value / (10 ** int(config["Payments"]["currency_exp"])))) + return self.loc.get("currency_format_string", + symbol=config["Payments"]["currency_symbol"], + value="{0:.2f}".format(self.value / (10 ** int(config["Payments"]["currency_exp"])))) def __int__(self): return self.value @@ -68,48 +64,48 @@ class Price: return self.value / (10 ** int(config["Payments"]["currency_exp"])) def __ge__(self, other): - return self.value >= Price(other).value + return self.value >= Price(other, self.loc).value def __le__(self, other): - return self.value <= Price(other).value + return self.value <= Price(other, self.loc).value def __eq__(self, other): - return self.value == Price(other).value + return self.value == Price(other, self.loc).value def __gt__(self, other): - return self.value > Price(other).value + return self.value > Price(other, self.loc).value def __lt__(self, other): - return self.value < Price(other).value + return self.value < Price(other, self.loc).value def __add__(self, other): - return Price(self.value + Price(other).value) + return Price(self.value + Price(other, self.loc).value, self.loc) def __sub__(self, other): - return Price(self.value - Price(other).value) + return Price(self.value - Price(other, self.loc).value, self.loc) def __mul__(self, other): - return Price(int(self.value * other)) + return Price(int(self.value * other), self.loc) def __floordiv__(self, other): - return Price(int(self.value // other)) + return Price(int(self.value // other), self.loc) def __radd__(self, other): return self.__add__(other) def __rsub__(self, other): - return Price(Price(other).value - self.value) + return Price(Price(other, self.loc).value - self.value, self.loc) def __rmul__(self, other): return self.__mul__(other) def __iadd__(self, other): - self.value += Price(other).value + self.value += Price(other, self.loc).value return self def __isub__(self, other): - self.value -= Price(other).value + self.value -= Price(other, self.loc).value return self def __imul__(self, other): @@ -235,7 +231,3 @@ class DuckBot: return self.bot.send_document(*args, **kwargs) # More methods can be added here - - -def boolmoji(boolean: bool): - return strings.emoji_yes if boolean else strings.emoji_no diff --git a/worker.py b/worker.py index cc44590..da9de39 100644 --- a/worker.py +++ b/worker.py @@ -100,23 +100,8 @@ class Worker(threading.Thread): log.info(f"Created new user: {self.user}") if will_be_owner: log.warning(f"User was auto-promoted to Admin as no other admins existed: {self.user}") - # Check if the user's language is enabled; if it isn't, change it to the default - if self.user.language not in configloader.config["Language"]["enabled_languages"]: - log.debug(f"User's language '{self.user.language}' is not enabled, changing it to the default") - self.user.language = configloader.config["Language"]["default_language"] - self.session.commit() - # Create a Localization object - self.loc = localization.Localization( - language=self.user.language, - fallback=configloader.config["Language"]["fallback_language"], - replacements={ - "user_string": str(self.user), - "user_mention": self.user.mention(), - "user_full_name": self.user.full_name, - "user_first_name": self.user.first_name, - "today": datetime.datetime.now().strftime("%a %d %b %Y"), - } - ) + # Create the localization object + self.__create_localization() # Capture exceptions that occour during the conversation # noinspection PyBroadException try: @@ -359,16 +344,23 @@ class Worker(threading.Thread): keyboard = [[telegram.KeyboardButton(self.loc.get("menu_order"))], [telegram.KeyboardButton(self.loc.get("menu_order_status"))], [telegram.KeyboardButton(self.loc.get("menu_add_credit"))], + [telegram.KeyboardButton(self.loc.get("menu_language"))], [telegram.KeyboardButton(self.loc.get("menu_help")), telegram.KeyboardButton(self.loc.get("menu_bot_info"))]] # Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) self.bot.send_message(self.chat.id, - self.loc.get("conversation_open_user_menu", credit=utils.Price(self.user.credit)), + self.loc.get("conversation_open_user_menu", + credit=utils.Price(self.user.credit, self.loc)), reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) # Wait for a reply from the user - selection = self.__wait_for_specific_message([self.loc.get("menu_order"), self.loc.get("menu_order_status"), - self.loc.get("menu_add_credit"), self.loc.get("menu_bot_info"), - self.loc.get("menu_help")]) + selection = self.__wait_for_specific_message([ + self.loc.get("menu_order"), + self.loc.get("menu_order_status"), + self.loc.get("menu_add_credit"), + self.loc.get("menu_language"), + self.loc.get("menu_help"), + self.loc.get("menu_bot_info"), + ]) # After the user reply, update the user data self.update_user() # If the user has selected the Order option... @@ -383,6 +375,10 @@ class Worker(threading.Thread): elif selection == self.loc.get("menu_add_credit"): # Display the add credit menu self.__add_credit_menu() + # If the user has selected the Language option... + elif selection == self.loc.get("menu_language"): + # Display the language menu + self.__language_menu() # If the user has selected the Bot Info option... elif selection == self.loc.get("menu_bot_info"): # Display information about the bot @@ -410,24 +406,27 @@ class Worker(threading.Thread): # Add the product to the cart cart[message['result']['message_id']] = [product, 0] # Create the inline keyboard to add the product to the cart - inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"), - callback_data="cart_add")]]) + inline_keyboard = telegram.InlineKeyboardMarkup( + [[telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"), callback_data="cart_add")]] + ) # Edit the sent message and add the inline keyboard if product.image is None: self.bot.edit_message_text(chat_id=self.chat.id, message_id=message['result']['message_id'], - text=product.text(), + text=product.text(loc=self.loc), reply_markup=inline_keyboard) else: self.bot.edit_message_caption(chat_id=self.chat.id, message_id=message['result']['message_id'], - caption=product.text(), + caption=product.text(loc=self.loc), reply_markup=inline_keyboard) # Create the keyboard with the cancel button inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_cancel"), callback_data="cart_cancel")]]) # Send a message containing the button to cancel or pay - final_msg = self.bot.send_message(self.chat.id, self.loc.get("conversation_cart_actions"), reply_markup=inline_keyboard) + final_msg = self.bot.send_message(self.chat.id, + self.loc.get("conversation_cart_actions"), + reply_markup=inline_keyboard) # Wait for user input while True: callback = self.__wait_for_inlinekeyboard_callback() @@ -448,8 +447,10 @@ class Worker(threading.Thread): # Create the product inline keyboard product_inline_keyboard = telegram.InlineKeyboardMarkup( [ - [telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"), callback_data="cart_add"), - telegram.InlineKeyboardButton(self.loc.get("menu_remove_from_cart"), callback_data="cart_remove")] + [telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"), + callback_data="cart_add"), + telegram.InlineKeyboardButton(self.loc.get("menu_remove_from_cart"), + callback_data="cart_remove")] ]) # Create the final inline keyboard final_inline_keyboard = telegram.InlineKeyboardMarkup( @@ -461,19 +462,22 @@ class Worker(threading.Thread): if product.image is None: self.bot.edit_message_text(chat_id=self.chat.id, message_id=callback.message.message_id, - text=product.text(cart_qty=cart[callback.message.message_id][1]), + text=product.text(loc=self.loc, + cart_qty=cart[callback.message.message_id][1]), reply_markup=product_inline_keyboard) else: self.bot.edit_message_caption(chat_id=self.chat.id, message_id=callback.message.message_id, - caption=product.text(cart_qty=cart[callback.message.message_id][1]), + caption=product.text(loc=self.loc, + cart_qty=cart[callback.message.message_id][1]), reply_markup=product_inline_keyboard) self.bot.edit_message_text( chat_id=self.chat.id, message_id=final_msg.message_id, - text=self.loc.get("conversation_confirm_cart", product_list=self.__get_cart_summary(cart), - total_cost=str(self.__get_cart_value(cart))), + text=self.loc.get("conversation_confirm_cart", + product_list=self.__get_cart_summary(cart), + total_cost=str(self.__get_cart_value(cart))), reply_markup=final_inline_keyboard) # If the Remove from cart button has been pressed... elif callback.data == "cart_remove": @@ -495,7 +499,8 @@ class Worker(threading.Thread): callback_data="cart_remove")) product_inline_keyboard = telegram.InlineKeyboardMarkup(product_inline_list) # Create the final inline keyboard - final_inline_list = [[telegram.InlineKeyboardButton(self.loc.get("menu_cancel"), callback_data="cart_cancel")]] + final_inline_list = [[telegram.InlineKeyboardButton(self.loc.get("menu_cancel"), + callback_data="cart_cancel")]] for product_id in cart: if cart[product_id][1] > 0: final_inline_list.append([telegram.InlineKeyboardButton(self.loc.get("menu_done"), @@ -505,18 +510,22 @@ class Worker(threading.Thread): # Edit the product message if product.image is None: self.bot.edit_message_text(chat_id=self.chat.id, message_id=callback.message.message_id, - text=product.text(cart_qty=cart[callback.message.message_id][1]), + text=product.text(loc=self.loc, + cart_qty=cart[callback.message.message_id][1]), reply_markup=product_inline_keyboard) else: self.bot.edit_message_caption(chat_id=self.chat.id, message_id=callback.message.message_id, - caption=product.text(cart_qty=cart[callback.message.message_id][1]), + caption=product.text(loc=self.loc, + cart_qty=cart[callback.message.message_id][1]), reply_markup=product_inline_keyboard) self.bot.edit_message_text( chat_id=self.chat.id, message_id=final_msg.message_id, - text=self.loc.get("conversation_confirm_cart", product_list=self.__get_cart_summary(cart), total_cost=str(self.__get_cart_value(cart))), + text=self.loc.get("conversation_confirm_cart", + product_list=self.__get_cart_summary(cart), + total_cost=str(self.__get_cart_value(cart))), reply_markup=final_inline_keyboard) # If the done button has been pressed... elif callback.data == "cart_done": @@ -551,10 +560,10 @@ class Worker(threading.Thread): # Suggest payment for missing credit value if configuration allows refill if configloader.config["Credit Card"]["credit_card_token"] != "" \ and configloader.config["Appearance"]["refill_on_checkout"] == 'yes' \ - and utils.Price(int(configloader.config["Credit Card"]["min_amount"])) <= \ + and utils.Price(int(configloader.config["Credit Card"]["min_amount"]), self.loc) <= \ credit_required <= \ - utils.Price(int(configloader.config["Credit Card"]["max_amount"])): - self.__make_payment(utils.Price(credit_required)) + utils.Price(int(configloader.config["Credit Card"]["max_amount"]), self.loc): + self.__make_payment(utils.Price(credit_required, self.loc)) # If afer requested payment credit is still insufficient (either payment failure or cancel) if self.user.credit < self.__get_cart_value(cart): # Rollback all the changes @@ -563,21 +572,21 @@ class Worker(threading.Thread): # User has credit and valid order, perform transaction now self.__order_transaction(order=order, value=-int(self.__get_cart_value(cart))) - @staticmethod - def __get_cart_value(cart): + def __get_cart_value(self, cart): # Calculate total items value in cart - value = utils.Price(0) + value = utils.Price(0, self.loc) for product in cart: value += cart[product][0].price * cart[product][1] return value - @staticmethod - def __get_cart_summary(cart): + def __get_cart_summary(self, cart): # Create the cart summary product_list = "" for product_id in cart: if cart[product_id][1] > 0: - product_list += cart[product_id][0].text(style="short", cart_qty=cart[product_id][1]) + "\n" + product_list += cart[product_id][0].text(loc=self.loc, + style="short", + cart_qty=cart[product_id][1]) + "\n" return product_list def __order_transaction(self, order, value): @@ -597,7 +606,9 @@ class Worker(threading.Thread): def __order_notify_admins(self, order): # Notify the user of the order result - self.bot.send_message(self.chat.id, self.loc.get("success_order_created", order=order.get_text(self.session, user=True))) + self.bot.send_message(self.chat.id, self.loc.get("success_order_created", order=order.text(loc=self.loc, + session=self.session, + user=True))) # Notify the admins (in Live Orders mode) of the new order admins = self.session.query(db.Admin).filter_by(live_mode=True).all() # Create the order keyboard @@ -609,7 +620,8 @@ class Worker(threading.Thread): # Notify them of the new placed order for admin in admins: self.bot.send_message(admin.user_id, - f"{self.loc.get('notification_order_placed', order=order.get_text(self.session))}", + self.loc.get('notification_order_placed', + order=order.text(loc=self.loc, session=self.session)), reply_markup=order_keyboard) def __order_status(self): @@ -626,7 +638,7 @@ class Worker(threading.Thread): self.bot.send_message(self.chat.id, self.loc.get("error_no_orders")) # Display the order status to the user for order in orders: - self.bot.send_message(self.chat.id, order.get_text(self.session, user=True)) + self.bot.send_message(self.chat.id, order.text(loc=self.loc, session=self.session, user=True)) # TODO: maybe add a page displayer instead of showing the latest 5 orders def __add_credit_menu(self): @@ -667,7 +679,7 @@ class Worker(threading.Thread): log.debug("Displaying __add_credit_cc") # Create a keyboard to be sent later presets = list(map(lambda s: s.strip(" "), configloader.config["Credit Card"]["payment_presets"].split('|'))) - keyboard = [[telegram.KeyboardButton(str(utils.Price(preset)))] for preset in presets] + keyboard = [[telegram.KeyboardButton(str(utils.Price(preset, self.loc)))] for preset in presets] keyboard.append([telegram.KeyboardButton(self.loc.get("menu_cancel"))]) # Boolean variable to check if the user has cancelled the action cancelled = False @@ -684,15 +696,15 @@ class Worker(threading.Thread): cancelled = True continue # Convert the amount to an integer - value = utils.Price(selection) + value = utils.Price(selection, self.loc) # Ensure the amount is within the range - if value > utils.Price(int(configloader.config["Credit Card"]["max_amount"])): + if value > utils.Price(int(configloader.config["Credit Card"]["max_amount"]), self.loc): self.bot.send_message(self.chat.id, - self.loc.get("error_payment_amount_over_max", max_amount=utils.Price(configloader.config["Credit Card"]["max_amount"]))) + self.loc.get("error_payment_amount_over_max", max_amount=utils.Price(configloader.config["Credit Card"]["max_amount"], self.loc))) continue - elif value < utils.Price(int(configloader.config["Credit Card"]["min_amount"])): + elif value < utils.Price(int(configloader.config["Credit Card"]["min_amount"]), self.loc): self.bot.send_message(self.chat.id, - self.loc.get("error_payment_amount_under_min", min_amount=utils.Price(configloader.config["Credit Card"]["min_amount"]))) + self.loc.get("error_payment_amount_under_min", min_amount=utils.Price(configloader.config["Credit Card"]["min_amount"], self.loc))) continue break # If the user cancelled the action... @@ -904,7 +916,7 @@ class Worker(threading.Thread): if product: self.bot.send_message(self.chat.id, self.loc.get("edit_current_value", - value=(str(utils.Price(product.price)) + value=(str(utils.Price(product.price, self.loc)) if product.price is not None else 'Non in vendita')), reply_markup=cancel) # Wait for an answer @@ -916,7 +928,7 @@ class Worker(threading.Thread): elif price.lower() == "x": price = None else: - price = utils.Price(price) + price = utils.Price(price, self.loc) # Ask for the product image self.bot.send_message(self.chat.id, self.loc.get("ask_product_image"), reply_markup=cancel) # Wait for an answer @@ -1007,7 +1019,7 @@ class Worker(threading.Thread): # Create a message for every one of them for order in orders: # Send the created message - self.bot.send_message(self.chat.id, order.get_text(session=self.session), + self.bot.send_message(self.chat.id, order.text(loc=self.loc, session=self.session), reply_markup=order_keyboard) # Set the Live mode flag to True self.admin.live_mode = True @@ -1036,11 +1048,11 @@ class Worker(threading.Thread): # Commit the transaction self.session.commit() # Update order message - self.bot.edit_message_text(order.get_text(session=self.session), chat_id=self.chat.id, + self.bot.edit_message_text(order.text(loc=self.loc, session=self.session), chat_id=self.chat.id, message_id=update.message.message_id) # Notify the user of the completition self.bot.send_message(order.user_id, - self.loc.get("notification_order_completed", order=order.get_text(self.session, user=True))) + self.loc.get("notification_order_completed", order=order.text(loc=self.loc, session=self.session, user=True))) # If the user pressed the refund order button, refund the order... elif update.data == "order_refund": # Ask for a refund reason @@ -1064,13 +1076,14 @@ class Worker(threading.Thread): # Commit the changes self.session.commit() # Update the order message - self.bot.edit_message_text(order.get_text(session=self.session), + self.bot.edit_message_text(order.text(loc=self.loc, session=self.session), chat_id=self.chat.id, message_id=update.message.message_id) # Notify the user of the refund self.bot.send_message(order.user_id, - self.loc.get("notification_order_refunded", order=order.get_text(self.session, - user=True))) + self.loc.get("notification_order_refunded", order=order.text(loc=self.loc, + session=self.session, + user=True))) # Notify the admin of the refund self.bot.send_message(self.chat.id, self.loc.get("success_order_refunded", order_id=order.order_id)) @@ -1093,7 +1106,7 @@ class Worker(threading.Thread): if isinstance(reply, CancelSignal): return # Convert the reply to a price object - price = utils.Price(reply) + price = utils.Price(reply, self.loc) # Ask the user for notes self.bot.send_message(self.chat.id, self.loc.get("ask_transaction_notes"), reply_markup=cancel) # Wait for an answer @@ -1279,15 +1292,15 @@ class Worker(threading.Thread): while True: # Create the inline keyboard with the admin status inline_keyboard = telegram.InlineKeyboardMarkup([ - [telegram.InlineKeyboardButton(f"{utils.boolmoji(admin.edit_products)} {self.loc.get('prop_edit_products')}", + [telegram.InlineKeyboardButton(f"{self.loc.boolmoji(admin.edit_products)} {self.loc.get('prop_edit_products')}", callback_data="toggle_edit_products")], - [telegram.InlineKeyboardButton(f"{utils.boolmoji(admin.receive_orders)} {self.loc.get('prop_receive_orders')}", + [telegram.InlineKeyboardButton(f"{self.loc.boolmoji(admin.receive_orders)} {self.loc.get('prop_receive_orders')}", callback_data="toggle_receive_orders")], [telegram.InlineKeyboardButton( - f"{utils.boolmoji(admin.create_transactions)} {self.loc.get('prop_create_transactions')}", + f"{self.loc.boolmoji(admin.create_transactions)} {self.loc.get('prop_create_transactions')}", callback_data="toggle_create_transactions")], [telegram.InlineKeyboardButton( - f"{utils.boolmoji(admin.display_on_help)} {self.loc.get('prop_display_on_help')}", + f"{self.loc.boolmoji(admin.display_on_help)} {self.loc.get('prop_display_on_help')}", callback_data="toggle_display_on_help")], [telegram.InlineKeyboardButton(self.loc.get('menu_done'), callback_data="cmd_done")] ]) @@ -1310,6 +1323,60 @@ class Worker(threading.Thread): break self.session.commit() + def __language_menu(self): + """Select a language.""" + log.debug("Displaying __language_menu") + keyboard = [] + options: Dict[str, str] = {} + # https://en.wikipedia.org/wiki/List_of_language_names + if "it" in configloader.config["Language"]["enabled_languages"]: + lang = "๐Ÿ‡ฎ๐Ÿ‡น Italiano" + keyboard.append([telegram.KeyboardButton(lang)]) + options[lang] = "it" + if "en" in configloader.config["Language"]["enabled_languages"]: + lang = "๐Ÿ‡ฌ๐Ÿ‡ง English" + keyboard.append([telegram.KeyboardButton(lang)]) + options[lang] = "en" + if "ru" in configloader.config["Language"]["enabled_languages"]: + lang = "๐Ÿ‡ท๐Ÿ‡บ ะ ัƒััะบะธะน" + keyboard.append([telegram.KeyboardButton(lang)]) + options[lang] = "ru" + if "uk" in configloader.config["Language"]["enabled_languages"]: + lang = "๐Ÿ‡บ๐Ÿ‡ฆ ะฃะบั€ะฐั—ะฝััŒะบะฐ" + keyboard.append([telegram.KeyboardButton(lang)]) + options[lang] = "uk" + # Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) + self.bot.send_message(self.chat.id, + self.loc.get("conversation_language_select"), + reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) + # Wait for an answer + response = self.__wait_for_specific_message(list(options.keys())) + # Set the language to the corresponding value + self.user.language = options[response] + # Commit the edit to the database + self.session.commit() + # Recreate the localization object + self.__create_localization() + + def __create_localization(self): + # Check if the user's language is enabled; if it isn't, change it to the default + if self.user.language not in configloader.config["Language"]["enabled_languages"]: + log.debug(f"User's language '{self.user.language}' is not enabled, changing it to the default") + self.user.language = configloader.config["Language"]["default_language"] + self.session.commit() + # Create a new Localization object + self.loc = localization.Localization( + language=self.user.language, + fallback=configloader.config["Language"]["fallback_language"], + replacements={ + "user_string": str(self.user), + "user_mention": self.user.mention(), + "user_full_name": self.user.full_name, + "user_first_name": self.user.first_name, + "today": datetime.datetime.now().strftime("%a %d %b %Y"), + } + ) + def __graceful_stop(self, stop_trigger: StopSignal): """Handle the graceful stop of the thread.""" log.debug("Gracefully stopping the conversation")