1
Fork 0
mirror of https://github.com/Steffo99/greed.git synced 2024-11-24 14:54:18 +00:00

Refactor localization support to close #51

This commit is contained in:
Steffo 2020-05-03 01:01:44 +02:00
parent 8f471412ee
commit 5d50008936
9 changed files with 225 additions and 170 deletions

View file

@ -5,15 +5,16 @@
# Config file parameters # Config file parameters
[Config] [Config]
; Config file version. DO NOT EDIT THIS! ; Config file version. DO NOT EDIT THIS!
version = 17 version = 18
; Set this to no when you are done editing the file ; Set this to no when you are done editing the file
is_template = yes is_template = yes
; Language code for string file ; Language code for string file
; Uses the https://en.wikipedia.org/wiki/IETF_language_tag name of the language
; Available languages: ; Available languages:
; it_IT - Italian, by Steffo ; it - Italian, by Steffo
; en_US - English, by https://github.com/DarrenWestwood (incomplete, please improve it!) ; en - English, by https://github.com/DarrenWestwood (incomplete, please improve it!)
; ua_UK - Ukrainian, by https://github.com/pzhuk ; uk - Ukrainian, by https://github.com/pzhuk
; ru_RU - Russian, by https://github.com/pzhuk ; ru - Russian, by https://github.com/pzhuk
language = it_IT language = it_IT
# Telegram bot parameters # Telegram bot parameters

23
core.py
View file

@ -4,7 +4,7 @@ import worker
import configloader import configloader
import utils import utils
import threading import threading
import importlib import localization
import logging import logging
try: try:
@ -12,9 +12,6 @@ try:
except ImportError: except ImportError:
coloredlogs = None coloredlogs = None
language = configloader.config["Config"]["language"]
strings = importlib.import_module("strings." + language)
def main(): def main():
"""The core code of the program. Should be run only in the main process!""" """The core code of the program. Should be run only in the main process!"""
@ -48,6 +45,10 @@ def main():
sys.exit(1) sys.exit(1)
log.debug("Bot token is valid!") log.debug("Bot token is valid!")
# Finding default language
default_language = configloader.config["Config"]["language"]
default_loc = localization.Localization(default_language)
# Create a dictionary linking the chat ids to the Worker objects # Create a dictionary linking the chat ids to the Worker objects
# {"1234": <Worker>} # {"1234": <Worker>}
chat_workers = {} chat_workers = {}
@ -72,7 +73,7 @@ def main():
if update.message.chat.type != "private": if update.message.chat.type != "private":
log.debug(f"Received a message from a non-private chat: {update.message.chat.id}") log.debug(f"Received a message from a non-private chat: {update.message.chat.id}")
# Notify the chat # Notify the chat
bot.send_message(update.message.chat.id, strings.error_nonprivate_chat) bot.send_message(update.message.chat.id, default_loc.get("error_nonprivate_chat"))
# Skip the update # Skip the update
continue continue
# If the message is a start command... # If the message is a start command...
@ -85,7 +86,9 @@ def main():
log.debug(f"Received request to stop {old_worker.name}") log.debug(f"Received request to stop {old_worker.name}")
old_worker.stop("request") old_worker.stop("request")
# Initialize a new worker for the chat # Initialize a new worker for the chat
new_worker = worker.Worker(bot, update.message.chat) new_worker = worker.Worker(bot=bot,
chat=update.message.chat,
telegram_user=update.message.from_user)
# Start the worker # Start the worker
log.debug(f"Starting {new_worker.name}") log.debug(f"Starting {new_worker.name}")
new_worker.start() new_worker.start()
@ -99,12 +102,12 @@ def main():
if receiving_worker is None or not receiving_worker.is_alive(): if receiving_worker is None or not receiving_worker.is_alive():
log.debug(f"Received a message in a chat without worker: {update.message.chat.id}") log.debug(f"Received a message in a chat without worker: {update.message.chat.id}")
# Suggest that the user restarts the chat with /start # Suggest that the user restarts the chat with /start
bot.send_message(update.message.chat.id, strings.error_no_worker_for_chat, bot.send_message(update.message.chat.id, default_loc.get("error_no_worker_for_chat"),
reply_markup=telegram.ReplyKeyboardRemove()) reply_markup=telegram.ReplyKeyboardRemove())
# Skip the update # Skip the update
continue continue
# If the message contains the "Cancel" string defined in the strings file... # If the message contains the "Cancel" string defined in the strings file...
if update.message.text == strings.menu_cancel: if update.message.text == receiving_worker.loc.get("menu_cancel"):
log.debug(f"Forwarding CancelSignal to {receiving_worker}") log.debug(f"Forwarding CancelSignal to {receiving_worker}")
# Send a CancelSignal to the worker instead of the update # Send a CancelSignal to the worker instead of the update
receiving_worker.queue.put(worker.CancelSignal()) receiving_worker.queue.put(worker.CancelSignal())
@ -120,7 +123,7 @@ def main():
if receiving_worker is None: if receiving_worker is None:
log.debug(f"Received a callback query in a chat without worker: {update.callback_query.from_user.id}") log.debug(f"Received a callback query in a chat without worker: {update.callback_query.from_user.id}")
# Suggest that the user restarts the chat with /start # Suggest that the user restarts the chat with /start
bot.send_message(update.callback_query.from_user.id, strings.error_no_worker_for_chat) bot.send_message(update.callback_query.from_user.id, default_loc.get("error_no_worker_for_chat"))
# Skip the update # Skip the update
continue continue
# Check if the pressed inline key is a cancel button # Check if the pressed inline key is a cancel button
@ -146,7 +149,7 @@ def main():
try: try:
bot.answer_pre_checkout_query(update.pre_checkout_query.id, bot.answer_pre_checkout_query(update.pre_checkout_query.id,
ok=False, ok=False,
error_message=strings.error_invoice_expired) error_message=default_loc.get("error_invoice_expired"))
except telegram.error.BadRequest: except telegram.error.BadRequest:
log.error("pre-checkout query expired before an answer could be sent!") log.error("pre-checkout query expired before an answer could be sent!")
# Go to the next update # Go to the next update

View file

@ -74,6 +74,13 @@ class User(TableDeclarativeBase):
valid_transactions: typing.List[Transaction] = [t for t in self.transactions if not t.refunded] valid_transactions: typing.List[Transaction] = [t for t in self.transactions if not t.refunded]
self.credit = sum(map(lambda t: t.value, valid_transactions)) self.credit = sum(map(lambda t: t.value, valid_transactions))
@property
def full_name(self):
if self.last_name:
return f"{self.first_name} {self.last_name}"
else:
return self.first_name
def __repr__(self): def __repr__(self):
return f"<User {self} having {self.credit} credit>" return f"<User {self} having {self.credit} credit>"

29
localization.py Normal file
View file

@ -0,0 +1,29 @@
import importlib
class IgnoreDict(dict):
"""A dictionary that if passed to format_map, ignores the missing replacement fields."""
def __missing__(self, key):
return "{" + key + "}"
class Localization:
def __init__(self, language, replacements=None):
self.language = language
self.module = importlib.import_module("strings." + language)
self.replacements = replacements if replacements else {}
@staticmethod
def is_supported(language) -> bool:
try:
importlib.import_module("strings." + language)
except ImportError:
return False
else:
return True
def get(self, key, **kwargs) -> str:
string = self.module.__getattribute__(key)
assert isinstance(string, str)
formatter = IgnoreDict(**self.replacements, **kwargs)
return string.format_map(formatter)

325
worker.py
View file

@ -13,14 +13,11 @@ import os
import traceback import traceback
from html import escape from html import escape
import requests import requests
import importlib
import logging import logging
import localization
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
language = configloader.config["Config"]["language"]
strings = importlib.import_module("strings." + language)
class StopSignal: class StopSignal:
"""A data class that should be sent to the worker when the conversation has to be stopped abnormally.""" """A data class that should be sent to the worker when the conversation has to be stopped abnormally."""
@ -36,12 +33,13 @@ class CancelSignal:
class Worker(threading.Thread): class Worker(threading.Thread):
"""A worker for a single conversation. A new one is created every time the /start command is sent.""" """A worker for a single conversation. A new one is created every time the /start command is sent."""
def __init__(self, bot: utils.DuckBot, chat: telegram.Chat, *args, **kwargs): def __init__(self, bot: utils.DuckBot, chat: telegram.Chat, telegram_user: telegram.User, *args, **kwargs):
# Initialize the thread # Initialize the thread
super().__init__(name=f"Worker {chat.id}", *args, **kwargs) super().__init__(name=f"Worker {chat.id}", *args, **kwargs)
# Store the bot and chat info inside the class # Store the bot and chat info inside the class
self.bot: utils.DuckBot = bot self.bot: utils.DuckBot = bot
self.chat: telegram.Chat = chat self.chat: telegram.Chat = chat
self.telegram_user: telegram.User = telegram_user
# Open a new database session # Open a new database session
log.debug(f"Opening new database session for {self.name}") log.debug(f"Opening new database session for {self.name}")
self.session = db.Session() self.session = db.Session()
@ -52,6 +50,8 @@ class Worker(threading.Thread):
self.queue = queuem.Queue() self.queue = queuem.Queue()
# The current active invoice payload; reject all invoices with a different payload # The current active invoice payload; reject all invoices with a different payload
self.invoice_payload = None self.invoice_payload = None
# The localization strings for this user
self.loc = None
# The Sentry client for reporting errors encountered by the user # The Sentry client for reporting errors encountered by the user
if configloader.config["Error Reporting"]["sentry_token"] != \ if configloader.config["Error Reporting"]["sentry_token"] != \
"https://00000000000000000000000000000000:00000000000000000000000000000000@sentry.io/0000000": "https://00000000000000000000000000000000:00000000000000000000000000000000@sentry.io/0000000":
@ -69,9 +69,7 @@ class Worker(threading.Thread):
def run(self): def run(self):
"""The conversation code.""" """The conversation code."""
# Welcome the user to the bot
log.debug("Starting conversation") log.debug("Starting conversation")
self.bot.send_message(self.chat.id, strings.conversation_after_start)
# Get the user db data from the users and admin tables # 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.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() self.admin = self.session.query(db.Admin).filter(db.Admin.user_id == self.chat.id).one_or_none()
@ -102,9 +100,33 @@ class Worker(threading.Thread):
log.info(f"Created new user: {self.user}") log.info(f"Created new user: {self.user}")
if will_be_owner: if will_be_owner:
log.warning(f"User was auto-promoted to Admin as no other admins existed: {self.user}") log.warning(f"User was auto-promoted to Admin as no other admins existed: {self.user}")
# Detect language from Telegram metadata
default_language = configloader.config["Config"]["language"]
language = self.telegram_user.language_code
if language:
log.debug(f"Detected language: {language}")
if not localization.Localization.is_supported(language):
log.debug(f"Unsupported language, using default: {default_language}")
language = default_language
else:
log.debug(f"No language detected, using default: {default_language}")
language = default_language
# Create a Localization object
self.loc = localization.Localization(
language=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"),
}
)
# Capture exceptions that occour during the conversation # Capture exceptions that occour during the conversation
# noinspection PyBroadException # noinspection PyBroadException
try: try:
# Welcome the user to the bot
self.bot.send_message(self.chat.id, self.loc.get("conversation_after_start"))
# If the user is not an admin, send him to the user menu # If the user is not an admin, send him to the user menu
if self.admin is None: if self.admin is None:
self.__user_menu() self.__user_menu()
@ -120,7 +142,7 @@ class Worker(threading.Thread):
# Try to notify the user of the exception # Try to notify the user of the exception
# noinspection PyBroadException # noinspection PyBroadException
try: try:
self.bot.send_message(self.chat.id, strings.fatal_conversation_exception) self.bot.send_message(self.chat.id, self.loc.get("fatal_conversation_exception"))
except Exception: except Exception:
pass pass
# If the Sentry integration is enabled, log the exception # If the Sentry integration is enabled, log the exception
@ -309,7 +331,7 @@ class Worker(threading.Thread):
# Find all the users in the database # Find all the users in the database
users = self.session.query(db.User).order_by(db.User.user_id).all() users = self.session.query(db.User).order_by(db.User.user_id).all()
# Create a list containing all the keyboard button strings # Create a list containing all the keyboard button strings
keyboard_buttons = [[strings.menu_cancel]] keyboard_buttons = [[self.loc.get("menu_cancel")]]
# Add to the list all the users # Add to the list all the users
for user in users: for user in users:
keyboard_buttons.append([user.identifiable_str()]) keyboard_buttons.append([user.identifiable_str()])
@ -318,7 +340,7 @@ class Worker(threading.Thread):
# Keep asking until a result is returned # Keep asking until a result is returned
while True: while True:
# Send the keyboard # Send the keyboard
self.bot.send_message(self.chat.id, strings.conversation_admin_select_user, reply_markup=keyboard) self.bot.send_message(self.chat.id, self.loc.get("conversation_admin_select_user"), reply_markup=keyboard)
# Wait for a reply # Wait for a reply
reply = self.__wait_for_regex("user_([0-9]+)", cancellable=True) reply = self.__wait_for_regex("user_([0-9]+)", cancellable=True)
# Propagate CancelSignals # Propagate CancelSignals
@ -328,7 +350,7 @@ class Worker(threading.Thread):
user = self.session.query(db.User).filter_by(user_id=int(reply)).one_or_none() user = self.session.query(db.User).filter_by(user_id=int(reply)).one_or_none()
# Ensure the user exists # Ensure the user exists
if not user: if not user:
self.bot.send_message(self.chat.id, strings.error_user_does_not_exist) self.bot.send_message(self.chat.id, self.loc.get("error_user_does_not_exist"))
continue continue
return user return user
@ -339,38 +361,39 @@ class Worker(threading.Thread):
# Loop used to returning to the menu after executing a command # Loop used to returning to the menu after executing a command
while True: while True:
# Create a keyboard with the user main menu # Create a keyboard with the user main menu
keyboard = [[telegram.KeyboardButton(strings.menu_order)], keyboard = [[telegram.KeyboardButton(self.loc.get("menu_order"))],
[telegram.KeyboardButton(strings.menu_order_status)], [telegram.KeyboardButton(self.loc.get("menu_order_status"))],
[telegram.KeyboardButton(strings.menu_add_credit)], [telegram.KeyboardButton(self.loc.get("menu_add_credit"))],
[telegram.KeyboardButton(strings.menu_help), telegram.KeyboardButton(strings.menu_bot_info)]] [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) # Send the previously created keyboard to the user (ensuring it can be clicked only 1 time)
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.conversation_open_user_menu.format(credit=utils.Price(self.user.credit)), self.loc.get("conversation_open_user_menu", credit=utils.Price(self.user.credit)),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message([strings.menu_order, strings.menu_order_status, selection = self.__wait_for_specific_message([self.loc.get("menu_order"), self.loc.get("menu_order_status"),
strings.menu_add_credit, strings.menu_bot_info, self.loc.get("menu_add_credit"), self.loc.get("menu_bot_info"),
strings.menu_help]) self.loc.get("menu_help")])
# After the user reply, update the user data # After the user reply, update the user data
self.update_user() self.update_user()
# If the user has selected the Order option... # If the user has selected the Order option...
if selection == strings.menu_order: if selection == self.loc.get("menu_order"):
# Open the order menu # Open the order menu
self.__order_menu() self.__order_menu()
# If the user has selected the Order Status option... # If the user has selected the Order Status option...
elif selection == strings.menu_order_status: elif selection == self.loc.get("menu_order_status"):
# Display the order(s) status # Display the order(s) status
self.__order_status() self.__order_status()
# If the user has selected the Add Credit option... # If the user has selected the Add Credit option...
elif selection == strings.menu_add_credit: elif selection == self.loc.get("menu_add_credit"):
# Display the add credit menu # Display the add credit menu
self.__add_credit_menu() self.__add_credit_menu()
# If the user has selected the Bot Info option... # If the user has selected the Bot Info option...
elif selection == strings.menu_bot_info: elif selection == self.loc.get("menu_bot_info"):
# Display information about the bot # Display information about the bot
self.__bot_info() self.__bot_info()
# If the user has selected the Help option... # If the user has selected the Help option...
elif selection == strings.menu_help: elif selection == self.loc.get("menu_help"):
# Go to the Help menu # Go to the Help menu
self.__help_menu() self.__help_menu()
@ -392,7 +415,7 @@ class Worker(threading.Thread):
# Add the product to the cart # Add the product to the cart
cart[message['result']['message_id']] = [product, 0] cart[message['result']['message_id']] = [product, 0]
# Create the inline keyboard to add the product to the cart # Create the inline keyboard to add the product to the cart
inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_add_to_cart, inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"),
callback_data="cart_add")]]) callback_data="cart_add")]])
# Edit the sent message and add the inline keyboard # Edit the sent message and add the inline keyboard
if product.image is None: if product.image is None:
@ -406,10 +429,10 @@ class Worker(threading.Thread):
caption=product.text(), caption=product.text(),
reply_markup=inline_keyboard) reply_markup=inline_keyboard)
# Create the keyboard with the cancel button # Create the keyboard with the cancel button
inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_cancel, inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_cancel"),
callback_data="cart_cancel")]]) callback_data="cart_cancel")]])
# Send a message containing the button to cancel or pay # Send a message containing the button to cancel or pay
final_msg = self.bot.send_message(self.chat.id, strings.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 # Wait for user input
while True: while True:
callback = self.__wait_for_inlinekeyboard_callback() callback = self.__wait_for_inlinekeyboard_callback()
@ -430,14 +453,14 @@ class Worker(threading.Thread):
# Create the product inline keyboard # Create the product inline keyboard
product_inline_keyboard = telegram.InlineKeyboardMarkup( product_inline_keyboard = telegram.InlineKeyboardMarkup(
[ [
[telegram.InlineKeyboardButton(strings.menu_add_to_cart, callback_data="cart_add"), [telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"), callback_data="cart_add"),
telegram.InlineKeyboardButton(strings.menu_remove_from_cart, callback_data="cart_remove")] telegram.InlineKeyboardButton(self.loc.get("menu_remove_from_cart"), callback_data="cart_remove")]
]) ])
# Create the final inline keyboard # Create the final inline keyboard
final_inline_keyboard = telegram.InlineKeyboardMarkup( final_inline_keyboard = telegram.InlineKeyboardMarkup(
[ [
[telegram.InlineKeyboardButton(strings.menu_cancel, callback_data="cart_cancel")], [telegram.InlineKeyboardButton(self.loc.get("menu_cancel"), callback_data="cart_cancel")],
[telegram.InlineKeyboardButton(strings.menu_done, callback_data="cart_done")] [telegram.InlineKeyboardButton(self.loc.get("menu_done"), callback_data="cart_done")]
]) ])
# Edit both the product and the final message # Edit both the product and the final message
if product.image is None: if product.image is None:
@ -454,7 +477,7 @@ class Worker(threading.Thread):
self.bot.edit_message_text( self.bot.edit_message_text(
chat_id=self.chat.id, chat_id=self.chat.id,
message_id=final_msg.message_id, message_id=final_msg.message_id,
text=strings.conversation_confirm_cart.format(product_list=self.__get_cart_summary(cart), text=self.loc.get("conversation_confirm_cart", product_list=self.__get_cart_summary(cart),
total_cost=str(self.__get_cart_value(cart))), total_cost=str(self.__get_cart_value(cart))),
reply_markup=final_inline_keyboard) reply_markup=final_inline_keyboard)
# If the Remove from cart button has been pressed... # If the Remove from cart button has been pressed...
@ -470,17 +493,17 @@ class Worker(threading.Thread):
else: else:
continue continue
# Create the product inline keyboard # Create the product inline keyboard
product_inline_list = [[telegram.InlineKeyboardButton(strings.menu_add_to_cart, product_inline_list = [[telegram.InlineKeyboardButton(self.loc.get("menu_add_to_cart"),
callback_data="cart_add")]] callback_data="cart_add")]]
if cart[callback.message.message_id][1] > 0: if cart[callback.message.message_id][1] > 0:
product_inline_list[0].append(telegram.InlineKeyboardButton(strings.menu_remove_from_cart, product_inline_list[0].append(telegram.InlineKeyboardButton(self.loc.get("menu_remove_from_cart"),
callback_data="cart_remove")) callback_data="cart_remove"))
product_inline_keyboard = telegram.InlineKeyboardMarkup(product_inline_list) product_inline_keyboard = telegram.InlineKeyboardMarkup(product_inline_list)
# Create the final inline keyboard # Create the final inline keyboard
final_inline_list = [[telegram.InlineKeyboardButton(strings.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: for product_id in cart:
if cart[product_id][1] > 0: if cart[product_id][1] > 0:
final_inline_list.append([telegram.InlineKeyboardButton(strings.menu_done, final_inline_list.append([telegram.InlineKeyboardButton(self.loc.get("menu_done"),
callback_data="cart_done")]) callback_data="cart_done")])
break break
final_inline_keyboard = telegram.InlineKeyboardMarkup(final_inline_list) final_inline_keyboard = telegram.InlineKeyboardMarkup(final_inline_list)
@ -498,18 +521,17 @@ class Worker(threading.Thread):
self.bot.edit_message_text( self.bot.edit_message_text(
chat_id=self.chat.id, chat_id=self.chat.id,
message_id=final_msg.message_id, message_id=final_msg.message_id,
text=strings.conversation_confirm_cart.format(product_list=self.__get_cart_summary(cart), text=self.loc.get("conversation_confirm_cart", product_list=self.__get_cart_summary(cart), total_cost=str(self.__get_cart_value(cart))),
total_cost=str(self.__get_cart_value(cart))),
reply_markup=final_inline_keyboard) reply_markup=final_inline_keyboard)
# If the done button has been pressed... # If the done button has been pressed...
elif callback.data == "cart_done": elif callback.data == "cart_done":
# End the loop # End the loop
break break
# Create an inline keyboard with a single skip button # Create an inline keyboard with a single skip button
cancel = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_skip, cancel = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_skip"),
callback_data="cmd_cancel")]]) callback_data="cmd_cancel")]])
# Ask if the user wants to add notes to the order # Ask if the user wants to add notes to the order
self.bot.send_message(self.chat.id, strings.ask_order_notes, reply_markup=cancel) self.bot.send_message(self.chat.id, self.loc.get("ask_order_notes"), reply_markup=cancel)
# Wait for user input # Wait for user input
notes = self.__wait_for_regex(r"(.*)", cancellable=True) notes = self.__wait_for_regex(r"(.*)", cancellable=True)
# Create a new Order # Create a new Order
@ -530,7 +552,7 @@ class Worker(threading.Thread):
credit_required = self.__get_cart_value(cart) - self.user.credit credit_required = self.__get_cart_value(cart) - self.user.credit
# Notify user in case of insufficient credit # Notify user in case of insufficient credit
if credit_required > 0: if credit_required > 0:
self.bot.send_message(self.chat.id, strings.error_not_enough_credit) self.bot.send_message(self.chat.id, self.loc.get("error_not_enough_credit"))
# Suggest payment for missing credit value if configuration allows refill # Suggest payment for missing credit value if configuration allows refill
if configloader.config["Credit Card"]["credit_card_token"] != "" \ if configloader.config["Credit Card"]["credit_card_token"] != "" \
and configloader.config["Appearance"]["refill_on_checkout"] == 'yes' \ and configloader.config["Appearance"]["refill_on_checkout"] == 'yes' \
@ -580,20 +602,19 @@ class Worker(threading.Thread):
def __order_notify_admins(self, order): def __order_notify_admins(self, order):
# Notify the user of the order result # Notify the user of the order result
self.bot.send_message(self.chat.id, strings.success_order_created.format(order=order.get_text(self.session, self.bot.send_message(self.chat.id, self.loc.get("success_order_created", order=order.get_text(self.session, user=True)))
user=True)))
# Notify the admins (in Live Orders mode) of the new order # Notify the admins (in Live Orders mode) of the new order
admins = self.session.query(db.Admin).filter_by(live_mode=True).all() admins = self.session.query(db.Admin).filter_by(live_mode=True).all()
# Create the order keyboard # Create the order keyboard
order_keyboard = telegram.InlineKeyboardMarkup( order_keyboard = telegram.InlineKeyboardMarkup(
[ [
[telegram.InlineKeyboardButton(strings.menu_complete, callback_data="order_complete")], [telegram.InlineKeyboardButton(self.loc.get("menu_complete"), callback_data="order_complete")],
[telegram.InlineKeyboardButton(strings.menu_refund, callback_data="order_refund")] [telegram.InlineKeyboardButton(self.loc.get("menu_refund"), callback_data="order_refund")]
]) ])
# Notify them of the new placed order # Notify them of the new placed order
for admin in admins: for admin in admins:
self.bot.send_message(admin.user_id, self.bot.send_message(admin.user_id,
f"{strings.notification_order_placed.format(order=order.get_text(self.session))}", f"{self.loc.get('notification_order_placed', order=order.get_text(self.session))}",
reply_markup=order_keyboard) reply_markup=order_keyboard)
def __order_status(self): def __order_status(self):
@ -607,7 +628,7 @@ class Worker(threading.Thread):
.all() .all()
# Ensure there is at least one order to display # Ensure there is at least one order to display
if len(orders) == 0: if len(orders) == 0:
self.bot.send_message(self.chat.id, strings.error_no_orders) self.bot.send_message(self.chat.id, self.loc.get("error_no_orders"))
# Display the order status to the user # Display the order status to the user
for order in orders: 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.get_text(self.session, user=True))
@ -620,25 +641,25 @@ class Worker(threading.Thread):
keyboard = list() keyboard = list()
# Add the supported payment methods to the keyboard # Add the supported payment methods to the keyboard
# Cash # Cash
keyboard.append([telegram.KeyboardButton(strings.menu_cash)]) keyboard.append([telegram.KeyboardButton(self.loc.get("menu_cash"))])
# Telegram Payments # Telegram Payments
if configloader.config["Credit Card"]["credit_card_token"] != "": if configloader.config["Credit Card"]["credit_card_token"] != "":
keyboard.append([telegram.KeyboardButton(strings.menu_credit_card)]) keyboard.append([telegram.KeyboardButton(self.loc.get("menu_credit_card"))])
# Keyboard: go back to the previous menu # Keyboard: go back to the previous menu
keyboard.append([telegram.KeyboardButton(strings.menu_cancel)]) keyboard.append([telegram.KeyboardButton(self.loc.get("menu_cancel"))])
# Send the keyboard to the user # Send the keyboard to the user
self.bot.send_message(self.chat.id, strings.conversation_payment_method, self.bot.send_message(self.chat.id, self.loc.get("conversation_payment_method"),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message([strings.menu_cash, strings.menu_credit_card, strings.menu_cancel], selection = self.__wait_for_specific_message([self.loc.get("menu_cash"), self.loc.get("menu_credit_card"), self.loc.get("menu_cancel")],
cancellable=True) cancellable=True)
# If the user has selected the Cash option... # If the user has selected the Cash option...
if selection == strings.menu_cash: if selection == self.loc.get("menu_cash"):
# Go to the pay with cash function # Go to the pay with cash function
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.payment_cash.format(user_cash_id=self.user.identifiable_str())) self.loc.get("payment_cash", user_cash_id=self.user.identifiable_str()))
# If the user has selected the Credit Card option... # If the user has selected the Credit Card option...
elif selection == strings.menu_credit_card: elif selection == self.loc.get("menu_credit_card"):
# Go to the pay with credit card function # Go to the pay with credit card function
self.__add_credit_cc() self.__add_credit_cc()
# If the user has selected the Cancel option... # If the user has selected the Cancel option...
@ -652,16 +673,16 @@ class Worker(threading.Thread):
# Create a keyboard to be sent later # Create a keyboard to be sent later
presets = list(map(lambda s: s.strip(" "), configloader.config["Credit Card"]["payment_presets"].split('|'))) 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)))] for preset in presets]
keyboard.append([telegram.KeyboardButton(strings.menu_cancel)]) keyboard.append([telegram.KeyboardButton(self.loc.get("menu_cancel"))])
# Boolean variable to check if the user has cancelled the action # Boolean variable to check if the user has cancelled the action
cancelled = False cancelled = False
# Loop used to continue asking if there's an error during the input # Loop used to continue asking if there's an error during the input
while not cancelled: while not cancelled:
# Send the message and the keyboard # Send the message and the keyboard
self.bot.send_message(self.chat.id, strings.payment_cc_amount, self.bot.send_message(self.chat.id, self.loc.get("payment_cc_amount"),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait until a valid amount is sent # Wait until a valid amount is sent
selection = self.__wait_for_regex(r"([0-9]+(?:[.,][0-9]+)?|" + strings.menu_cancel + r")", cancellable=True) selection = self.__wait_for_regex(r"([0-9]+(?:[.,][0-9]+)?|" + self.loc.get("menu_cancel") + r")", cancellable=True)
# If the user cancelled the action # If the user cancelled the action
if isinstance(selection, CancelSignal): if isinstance(selection, CancelSignal):
# Exit the loop # Exit the loop
@ -672,15 +693,11 @@ class Worker(threading.Thread):
# Ensure the amount is within the range # 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.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.error_payment_amount_over_max.format( self.loc.get("error_payment_amount_over_max", max_amount=utils.Price(configloader.config["Credit Card"]["max_amount"])))
max_amount=utils.Price(configloader.config["Credit Card"]["max_amount"]))
)
continue continue
elif value < utils.Price(int(configloader.config["Credit Card"]["min_amount"])): elif value < utils.Price(int(configloader.config["Credit Card"]["min_amount"])):
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.error_payment_amount_under_min.format( self.loc.get("error_payment_amount_under_min", min_amount=utils.Price(configloader.config["Credit Card"]["min_amount"])))
min_amount=utils.Price(configloader.config["Credit Card"]["min_amount"]))
)
continue continue
break break
# If the user cancelled the action... # If the user cancelled the action...
@ -694,20 +711,20 @@ class Worker(threading.Thread):
# Set the invoice active invoice payload # Set the invoice active invoice payload
self.invoice_payload = str(uuid.uuid4()) self.invoice_payload = str(uuid.uuid4())
# Create the price array # Create the price array
prices = [telegram.LabeledPrice(label=strings.payment_invoice_label, amount=int(amount))] prices = [telegram.LabeledPrice(label=self.loc.get("payment_invoice_label"), amount=int(amount))]
# If the user has to pay a fee when using the credit card, add it to the prices list # If the user has to pay a fee when using the credit card, add it to the prices list
fee = int(self.__get_total_fee(amount)) fee = int(self.__get_total_fee(amount))
if fee > 0: if fee > 0:
prices.append(telegram.LabeledPrice(label=strings.payment_invoice_fee_label, prices.append(telegram.LabeledPrice(label=self.loc.get("payment_invoice_fee_label"),
amount=fee)) amount=fee))
# Create the invoice keyboard # Create the invoice keyboard
inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_pay, pay=True)], inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_pay"), pay=True)],
[telegram.InlineKeyboardButton(strings.menu_cancel, [telegram.InlineKeyboardButton(self.loc.get("menu_cancel"),
callback_data="cmd_cancel")]]) callback_data="cmd_cancel")]])
# The amount is valid, send the invoice # The amount is valid, send the invoice
self.bot.send_invoice(self.chat.id, self.bot.send_invoice(self.chat.id,
title=strings.payment_invoice_title, title=self.loc.get("payment_invoice_title"),
description=strings.payment_invoice_description.format(amount=str(amount)), description=self.loc.get("payment_invoice_description", amount=str(amount)),
payload=self.invoice_payload, payload=self.invoice_payload,
provider_token=configloader.config["Credit Card"]["credit_card_token"], provider_token=configloader.config["Credit Card"]["credit_card_token"],
start_parameter="tempdeeplink", start_parameter="tempdeeplink",
@ -757,7 +774,7 @@ class Worker(threading.Thread):
def __bot_info(self): def __bot_info(self):
"""Send information about the bot.""" """Send information about the bot."""
log.debug("Displaying __bot_info") log.debug("Displaying __bot_info")
self.bot.send_message(self.chat.id, strings.bot_info) self.bot.send_message(self.chat.id, self.loc.get("bot_info"))
def __admin_menu(self): def __admin_menu(self):
"""Function called from the run method when the user is an administrator. """Function called from the run method when the user is an administrator.
@ -768,52 +785,52 @@ class Worker(threading.Thread):
# Create a keyboard with the admin main menu based on the admin permissions specified in the db # Create a keyboard with the admin main menu based on the admin permissions specified in the db
keyboard = [] keyboard = []
if self.admin.edit_products: if self.admin.edit_products:
keyboard.append([strings.menu_products]) keyboard.append([self.loc.get("menu_products")])
if self.admin.receive_orders: if self.admin.receive_orders:
keyboard.append([strings.menu_orders]) keyboard.append([self.loc.get("menu_orders")])
if self.admin.create_transactions: if self.admin.create_transactions:
keyboard.append([strings.menu_edit_credit]) keyboard.append([self.loc.get("menu_edit_credit")])
keyboard.append([strings.menu_transactions, strings.menu_csv]) keyboard.append([self.loc.get("menu_transactions"), self.loc.get("menu_csv")])
if self.admin.is_owner: if self.admin.is_owner:
keyboard.append([strings.menu_edit_admins]) keyboard.append([self.loc.get("menu_edit_admins")])
keyboard.append([strings.menu_user_mode]) keyboard.append([self.loc.get("menu_user_mode")])
# Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) # 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_admin_menu, self.bot.send_message(self.chat.id, self.loc.get("conversation_open_admin_menu"),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True), reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True),
) )
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message([strings.menu_products, strings.menu_orders, selection = self.__wait_for_specific_message([self.loc.get("menu_products"), self.loc.get("menu_orders"),
strings.menu_user_mode, strings.menu_edit_credit, self.loc.get("menu_user_mode"), self.loc.get("menu_edit_credit"),
strings.menu_transactions, strings.menu_csv, self.loc.get("menu_transactions"), self.loc.get("menu_csv"),
strings.menu_edit_admins]) self.loc.get("menu_edit_admins")])
# If the user has selected the Products option... # If the user has selected the Products option...
if selection == strings.menu_products: if selection == self.loc.get("menu_products"):
# Open the products menu # Open the products menu
self.__products_menu() self.__products_menu()
# If the user has selected the Orders option... # If the user has selected the Orders option...
elif selection == strings.menu_orders: elif selection == self.loc.get("menu_orders"):
# Open the orders menu # Open the orders menu
self.__orders_menu() self.__orders_menu()
# If the user has selected the Transactions option... # If the user has selected the Transactions option...
elif selection == strings.menu_edit_credit: elif selection == self.loc.get("menu_edit_credit"):
# Open the edit credit menu # Open the edit credit menu
self.__create_transaction() self.__create_transaction()
# If the user has selected the User mode option... # If the user has selected the User mode option...
elif selection == strings.menu_user_mode: elif selection == self.loc.get("menu_user_mode"):
# Tell the user how to go back to admin menu # Tell the user how to go back to admin menu
self.bot.send_message(self.chat.id, strings.conversation_switch_to_user_mode) self.bot.send_message(self.chat.id, self.loc.get("conversation_switch_to_user_mode"))
# Start the bot in user mode # Start the bot in user mode
self.__user_menu() self.__user_menu()
# If the user has selected the Add Admin option... # If the user has selected the Add Admin option...
elif selection == strings.menu_edit_admins: elif selection == self.loc.get("menu_edit_admins"):
# Open the edit admin menu # Open the edit admin menu
self.__add_admin() self.__add_admin()
# If the user has selected the Transactions option... # If the user has selected the Transactions option...
elif selection == strings.menu_transactions: elif selection == self.loc.get("menu_transactions"):
# Open the transaction pages # Open the transaction pages
self.__transaction_pages() self.__transaction_pages()
# If the user has selected the .csv option... # If the user has selected the .csv option...
elif selection == strings.menu_csv: elif selection == self.loc.get("menu_csv"):
# Generate the .csv file # Generate the .csv file
self.__transactions_file() self.__transactions_file()
@ -825,13 +842,13 @@ class Worker(threading.Thread):
# Create a list of product names # Create a list of product names
product_names = [product.name for product in products] product_names = [product.name for product in products]
# Insert at the start of the list the add product option, the remove product option and the Cancel option # Insert at the start of the list the add product option, the remove product option and the Cancel option
product_names.insert(0, strings.menu_cancel) product_names.insert(0, self.loc.get("menu_cancel"))
product_names.insert(1, strings.menu_add_product) product_names.insert(1, self.loc.get("menu_add_product"))
product_names.insert(2, strings.menu_delete_product) product_names.insert(2, self.loc.get("menu_delete_product"))
# Create a keyboard using the product names # Create a keyboard using the product names
keyboard = [[telegram.KeyboardButton(product_name)] for product_name in product_names] keyboard = [[telegram.KeyboardButton(product_name)] for product_name in product_names]
# Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) # 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_admin_select_product, self.bot.send_message(self.chat.id, self.loc.get("conversation_admin_select_product"),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message(product_names, cancellable=True) selection = self.__wait_for_specific_message(product_names, cancellable=True)
@ -840,11 +857,11 @@ class Worker(threading.Thread):
# Exit the menu # Exit the menu
return return
# If the user has selected the Add Product option... # If the user has selected the Add Product option...
elif selection == strings.menu_add_product: elif selection == self.loc.get("menu_add_product"):
# Open the add product menu # Open the add product menu
self.__edit_product_menu() self.__edit_product_menu()
# If the user has selected the Remove Product option... # If the user has selected the Remove Product option...
elif selection == strings.menu_delete_product: elif selection == self.loc.get("menu_delete_product"):
# Open the delete product menu # Open the delete product menu
self.__delete_product_menu() self.__delete_product_menu()
# If the user has selected a product # If the user has selected a product
@ -858,15 +875,15 @@ class Worker(threading.Thread):
"""Add a product to the database or edit an existing one.""" """Add a product to the database or edit an existing one."""
log.debug("Displaying __edit_product_menu") log.debug("Displaying __edit_product_menu")
# Create an inline keyboard with a single skip button # Create an inline keyboard with a single skip button
cancel = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_skip, cancel = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_skip"),
callback_data="cmd_cancel")]]) callback_data="cmd_cancel")]])
# Ask for the product name until a valid product name is specified # Ask for the product name until a valid product name is specified
while True: while True:
# Ask the question to the user # Ask the question to the user
self.bot.send_message(self.chat.id, strings.ask_product_name) self.bot.send_message(self.chat.id, self.loc.get("ask_product_name"))
# Display the current name if you're editing an existing product # Display the current name if you're editing an existing product
if product: if product:
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=escape(product.name)), self.bot.send_message(self.chat.id, self.loc.get("edit_current_value", value=escape(product.name)),
reply_markup=cancel) reply_markup=cancel)
# Wait for an answer # Wait for an answer
name = self.__wait_for_regex(r"(.*)", cancellable=bool(product)) name = self.__wait_for_regex(r"(.*)", cancellable=bool(product))
@ -875,23 +892,23 @@ class Worker(threading.Thread):
self.session.query(db.Product).filter_by(name=name, deleted=False).one_or_none() in [None, product]: self.session.query(db.Product).filter_by(name=name, deleted=False).one_or_none() in [None, product]:
# Exit the loop # Exit the loop
break break
self.bot.send_message(self.chat.id, strings.error_duplicate_name) self.bot.send_message(self.chat.id, self.loc.get("error_duplicate_name"))
# Ask for the product description # Ask for the product description
self.bot.send_message(self.chat.id, strings.ask_product_description) self.bot.send_message(self.chat.id, self.loc.get("ask_product_description"))
# Display the current description if you're editing an existing product # Display the current description if you're editing an existing product
if product: if product:
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.edit_current_value.format(value=escape(product.description)), self.loc.get("edit_current_value", value=escape(product.description)),
reply_markup=cancel) reply_markup=cancel)
# Wait for an answer # Wait for an answer
description = self.__wait_for_regex(r"(.*)", cancellable=bool(product)) description = self.__wait_for_regex(r"(.*)", cancellable=bool(product))
# Ask for the product price # Ask for the product price
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.ask_product_price) self.loc.get("ask_product_price"))
# Display the current name if you're editing an existing product # Display the current name if you're editing an existing product
if product: if product:
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.edit_current_value.format( self.loc.get("edit_current_value",
value=(str(utils.Price(product.price)) value=(str(utils.Price(product.price))
if product.price is not None else 'Non in vendita')), if product.price is not None else 'Non in vendita')),
reply_markup=cancel) reply_markup=cancel)
@ -906,7 +923,7 @@ class Worker(threading.Thread):
else: else:
price = utils.Price(price) price = utils.Price(price)
# Ask for the product image # Ask for the product image
self.bot.send_message(self.chat.id, strings.ask_product_image, reply_markup=cancel) self.bot.send_message(self.chat.id, self.loc.get("ask_product_image"), reply_markup=cancel)
# Wait for an answer # Wait for an answer
photo_list = self.__wait_for_photo(cancellable=True) photo_list = self.__wait_for_photo(cancellable=True)
# If a new product is being added... # If a new product is being added...
@ -935,14 +952,14 @@ class Worker(threading.Thread):
# Get the file object associated with the photo # Get the file object associated with the photo
photo_file = self.bot.get_file(largest_photo.file_id) photo_file = self.bot.get_file(largest_photo.file_id)
# Notify the user that the bot is downloading the image and might be inactive for a while # Notify the user that the bot is downloading the image and might be inactive for a while
self.bot.send_message(self.chat.id, strings.downloading_image) self.bot.send_message(self.chat.id, self.loc.get("downloading_image"))
self.bot.send_chat_action(self.chat.id, action="upload_photo") self.bot.send_chat_action(self.chat.id, action="upload_photo")
# Set the image for that product # Set the image for that product
product.set_image(photo_file) product.set_image(photo_file)
# Commit the session changes # Commit the session changes
self.session.commit() self.session.commit()
# Notify the user # Notify the user
self.bot.send_message(self.chat.id, strings.success_product_edited) self.bot.send_message(self.chat.id, self.loc.get("success_product_edited"))
def __delete_product_menu(self): def __delete_product_menu(self):
log.debug("Displaying __delete_product_menu") log.debug("Displaying __delete_product_menu")
@ -951,11 +968,11 @@ class Worker(threading.Thread):
# Create a list of product names # Create a list of product names
product_names = [product.name for product in products] product_names = [product.name for product in products]
# Insert at the start of the list the Cancel button # Insert at the start of the list the Cancel button
product_names.insert(0, strings.menu_cancel) product_names.insert(0, self.loc.get("menu_cancel"))
# Create a keyboard using the product names # Create a keyboard using the product names
keyboard = [[telegram.KeyboardButton(product_name)] for product_name in product_names] keyboard = [[telegram.KeyboardButton(product_name)] for product_name in product_names]
# Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) # 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_admin_select_product_to_delete, self.bot.send_message(self.chat.id, self.loc.get("conversation_admin_select_product_to_delete"),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message(product_names, cancellable=True) selection = self.__wait_for_specific_message(product_names, cancellable=True)
@ -969,22 +986,22 @@ class Worker(threading.Thread):
product.deleted = True product.deleted = True
self.session.commit() self.session.commit()
# Notify the user # Notify the user
self.bot.send_message(self.chat.id, strings.success_product_deleted) self.bot.send_message(self.chat.id, self.loc.get("success_product_deleted"))
def __orders_menu(self): def __orders_menu(self):
"""Display a live flow of orders.""" """Display a live flow of orders."""
log.debug("Displaying __orders_menu") log.debug("Displaying __orders_menu")
# Create a cancel and a stop keyboard # Create a cancel and a stop keyboard
stop_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_stop, stop_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_stop"),
callback_data="cmd_cancel")]]) callback_data="cmd_cancel")]])
cancel_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_cancel, cancel_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_cancel"),
callback_data="cmd_cancel")]]) callback_data="cmd_cancel")]])
# Send a small intro message on the Live Orders mode # Send a small intro message on the Live Orders mode
self.bot.send_message(self.chat.id, strings.conversation_live_orders_start, reply_markup=stop_keyboard) self.bot.send_message(self.chat.id, self.loc.get("conversation_live_orders_start"), reply_markup=stop_keyboard)
# Create the order keyboard # Create the order keyboard
order_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_complete, order_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_complete"),
callback_data="order_complete")], callback_data="order_complete")],
[telegram.InlineKeyboardButton(strings.menu_refund, [telegram.InlineKeyboardButton(self.loc.get("menu_refund"),
callback_data="order_refund")]]) callback_data="order_refund")]])
# Display the past pending orders # Display the past pending orders
orders = self.session.query(db.Order) \ orders = self.session.query(db.Order) \
@ -1010,12 +1027,12 @@ class Worker(threading.Thread):
self.admin.live_mode = False self.admin.live_mode = False
break break
# Find the order # Find the order
order_id = re.search(strings.order_number.replace("{id}", "([0-9]+)"), update.message.text).group(1) order_id = re.search(self.loc.get("order_number").replace("{id}", "([0-9]+)"), update.message.text).group(1)
order = self.session.query(db.Order).filter(db.Order.order_id == order_id).one() order = self.session.query(db.Order).filter(db.Order.order_id == order_id).one()
# Check if the order hasn't been already cleared # Check if the order hasn't been already cleared
if order.delivery_date is not None or order.refund_date is not None: if order.delivery_date is not None or order.refund_date is not None:
# Notify the admin and skip that order # Notify the admin and skip that order
self.bot.edit_message_text(self.chat.id, strings.error_order_already_cleared) self.bot.edit_message_text(self.chat.id, self.loc.get("error_order_already_cleared"))
break break
# If the user pressed the complete order button, complete the order # If the user pressed the complete order button, complete the order
if update.data == "order_complete": if update.data == "order_complete":
@ -1028,12 +1045,11 @@ class Worker(threading.Thread):
message_id=update.message.message_id) message_id=update.message.message_id)
# Notify the user of the completition # Notify the user of the completition
self.bot.send_message(order.user_id, self.bot.send_message(order.user_id,
strings.notification_order_completed.format(order=order.get_text(self.session, self.loc.get("notification_order_completed", order=order.get_text(self.session, user=True)))
user=True)))
# If the user pressed the refund order button, refund the order... # If the user pressed the refund order button, refund the order...
elif update.data == "order_refund": elif update.data == "order_refund":
# Ask for a refund reason # Ask for a refund reason
reason_msg = self.bot.send_message(self.chat.id, strings.ask_refund_reason, reason_msg = self.bot.send_message(self.chat.id, self.loc.get("ask_refund_reason"),
reply_markup=cancel_keyboard) reply_markup=cancel_keyboard)
# Wait for a reply # Wait for a reply
reply = self.__wait_for_regex("(.*)", cancellable=True) reply = self.__wait_for_regex("(.*)", cancellable=True)
@ -1058,10 +1074,10 @@ class Worker(threading.Thread):
message_id=update.message.message_id) message_id=update.message.message_id)
# Notify the user of the refund # Notify the user of the refund
self.bot.send_message(order.user_id, self.bot.send_message(order.user_id,
strings.notification_order_refunded.format(order=order.get_text(self.session, self.loc.get("notification_order_refunded", order=order.get_text(self.session,
user=True))) user=True)))
# Notify the admin of the refund # Notify the admin of the refund
self.bot.send_message(self.chat.id, strings.success_order_refunded.format(order_id=order.order_id)) self.bot.send_message(self.chat.id, self.loc.get("success_order_refunded", order_id=order.order_id))
def __create_transaction(self): def __create_transaction(self):
"""Edit manually the credit of an user.""" """Edit manually the credit of an user."""
@ -1072,10 +1088,10 @@ class Worker(threading.Thread):
if isinstance(user, CancelSignal): if isinstance(user, CancelSignal):
return return
# Create an inline keyboard with a single cancel button # Create an inline keyboard with a single cancel button
cancel = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_cancel, cancel = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(self.loc.get("menu_cancel"),
callback_data="cmd_cancel")]]) callback_data="cmd_cancel")]])
# Request from the user the amount of money to be credited manually # Request from the user the amount of money to be credited manually
self.bot.send_message(self.chat.id, strings.ask_credit, reply_markup=cancel) self.bot.send_message(self.chat.id, self.loc.get("ask_credit"), reply_markup=cancel)
# Wait for an answer # Wait for an answer
reply = self.__wait_for_regex(r"(-? ?[0-9]{1,3}(?:[.,][0-9]{1,2})?)", cancellable=True) reply = self.__wait_for_regex(r"(-? ?[0-9]{1,3}(?:[.,][0-9]{1,2})?)", cancellable=True)
# Allow the cancellation of the operation # Allow the cancellation of the operation
@ -1084,7 +1100,7 @@ class Worker(threading.Thread):
# Convert the reply to a price object # Convert the reply to a price object
price = utils.Price(reply) price = utils.Price(reply)
# Ask the user for notes # Ask the user for notes
self.bot.send_message(self.chat.id, strings.ask_transaction_notes, reply_markup=cancel) self.bot.send_message(self.chat.id, self.loc.get("ask_transaction_notes"), reply_markup=cancel)
# Wait for an answer # Wait for an answer
reply = self.__wait_for_regex(r"(.*)", cancellable=True) reply = self.__wait_for_regex(r"(.*)", cancellable=True)
# Allow the cancellation of the operation # Allow the cancellation of the operation
@ -1102,36 +1118,36 @@ class Worker(threading.Thread):
self.session.commit() self.session.commit()
# Notify the user of the credit/debit # Notify the user of the credit/debit
self.bot.send_message(user.user_id, self.bot.send_message(user.user_id,
strings.notification_transaction_created.format(transaction=str(transaction))) self.loc.get("notification_transaction_created", transaction=str(transaction)))
# Notify the admin of the success # Notify the admin of the success
self.bot.send_message(self.chat.id, strings.success_transaction_created.format(transaction=str(transaction))) self.bot.send_message(self.chat.id, self.loc.get("success_transaction_created", transaction=str(transaction)))
def __help_menu(self): def __help_menu(self):
"""Help menu. Allows the user to ask for assistance, get a guide or see some info about the bot.""" """Help menu. Allows the user to ask for assistance, get a guide or see some info about the bot."""
log.debug("Displaying __help_menu") log.debug("Displaying __help_menu")
# Create a keyboard with the user help menu # Create a keyboard with the user help menu
keyboard = [[telegram.KeyboardButton(strings.menu_guide)], keyboard = [[telegram.KeyboardButton(self.loc.get("menu_guide"))],
[telegram.KeyboardButton(strings.menu_contact_shopkeeper)], [telegram.KeyboardButton(self.loc.get("menu_contact_shopkeeper"))],
[telegram.KeyboardButton(strings.menu_cancel)]] [telegram.KeyboardButton(self.loc.get("menu_cancel"))]]
# Send the previously created keyboard to the user (ensuring it can be clicked only 1 time) # Send the previously created keyboard to the user (ensuring it can be clicked only 1 time)
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.conversation_open_help_menu, self.loc.get("conversation_open_help_menu"),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message([strings.menu_guide, strings.menu_contact_shopkeeper, selection = self.__wait_for_specific_message([self.loc.get("menu_guide"), self.loc.get("menu_contact_shopkeeper"),
strings.menu_cancel]) self.loc.get("menu_cancel")])
# If the user has selected the Guide option... # If the user has selected the Guide option...
if selection == strings.menu_guide: if selection == self.loc.get("menu_guide"):
# Send them the bot guide # Send them the bot guide
self.bot.send_message(self.chat.id, strings.help_msg) self.bot.send_message(self.chat.id, self.loc.get("help_msg"))
# If the user has selected the Order Status option... # If the user has selected the Order Status option...
elif selection == strings.menu_contact_shopkeeper: elif selection == self.loc.get("menu_contact_shopkeeper"):
# Find the list of available shopkeepers # Find the list of available shopkeepers
shopkeepers = self.session.query(db.Admin).filter_by(display_on_help=True).join(db.User).all() shopkeepers = self.session.query(db.Admin).filter_by(display_on_help=True).join(db.User).all()
# Create the string # Create the string
shopkeepers_string = "\n".join([admin.user.mention() for admin in shopkeepers]) shopkeepers_string = "\n".join([admin.user.mention() for admin in shopkeepers])
# Send the message to the user # Send the message to the user
self.bot.send_message(self.chat.id, strings.contact_shopkeeper.format(shopkeepers=shopkeepers_string)) self.bot.send_message(self.chat.id, self.loc.get("contact_shopkeeper", shopkeepers=shopkeepers_string))
# If the user has selected the Cancel option the function will return immediately # If the user has selected the Cancel option the function will return immediately
def __transaction_pages(self): def __transaction_pages(self):
@ -1140,7 +1156,7 @@ class Worker(threading.Thread):
# Page number # Page number
page = 0 page = 0
# Create and send a placeholder message to be populated # Create and send a placeholder message to be populated
message = self.bot.send_message(self.chat.id, strings.loading_transactions) message = self.bot.send_message(self.chat.id, self.loc.get("loading_transactions"))
# Loop used to move between pages # Loop used to move between pages
while True: while True:
# Retrieve the 10 transactions in that page # Retrieve the 10 transactions in that page
@ -1155,22 +1171,21 @@ class Worker(threading.Thread):
if page != 0: if page != 0:
# Add a previous page button # Add a previous page button
inline_keyboard_list[0].append( inline_keyboard_list[0].append(
telegram.InlineKeyboardButton(strings.menu_previous, callback_data="cmd_previous") telegram.InlineKeyboardButton(self.loc.get("menu_previous"), callback_data="cmd_previous")
) )
# Don't add a next page button if this is the last page # Don't add a next page button if this is the last page
if len(transactions) == 10: if len(transactions) == 10:
# Add a next page button # Add a next page button
inline_keyboard_list[0].append( inline_keyboard_list[0].append(
telegram.InlineKeyboardButton(strings.menu_next, callback_data="cmd_next") telegram.InlineKeyboardButton(self.loc.get("menu_next"), callback_data="cmd_next")
) )
# Add a Done button # Add a Done button
inline_keyboard_list.append([telegram.InlineKeyboardButton(strings.menu_done, callback_data="cmd_done")]) inline_keyboard_list.append([telegram.InlineKeyboardButton(self.loc.get("menu_done"), callback_data="cmd_done")])
# Create the inline keyboard markup # Create the inline keyboard markup
inline_keyboard = telegram.InlineKeyboardMarkup(inline_keyboard_list) inline_keyboard = telegram.InlineKeyboardMarkup(inline_keyboard_list)
# Create the message text # Create the message text
transactions_string = "\n".join([str(transaction) for transaction in transactions]) transactions_string = "\n".join([str(transaction) for transaction in transactions])
text = strings.transactions_page.format(page=page + 1, text = self.loc.get("transactions_page", page=page + 1, transactions=transactions_string)
transactions=transactions_string)
# Update the previously sent message # Update the previously sent message
self.bot.edit_message_text(chat_id=self.chat.id, message_id=message.message_id, text=text, self.bot.edit_message_text(chat_id=self.chat.id, message_id=message.message_id, text=text,
reply_markup=inline_keyboard) reply_markup=inline_keyboard)
@ -1224,7 +1239,7 @@ class Worker(threading.Thread):
f"{transaction.payment_email if transaction.payment_email is not None else ''};" f"{transaction.payment_email if transaction.payment_email is not None else ''};"
f"{transaction.refunded if transaction.refunded is not None else ''}\n") f"{transaction.refunded if transaction.refunded is not None else ''}\n")
# Describe the file to the user # Describe the file to the user
self.bot.send_message(self.chat.id, strings.csv_caption) self.bot.send_message(self.chat.id, self.loc.get("csv_caption"))
# Reopen the file for reading # Reopen the file for reading
with open(f"transactions_{self.chat.id}.csv") as file: with open(f"transactions_{self.chat.id}.csv") as file:
# Send the file via a manual request to Telegram # Send the file via a manual request to Telegram
@ -1247,13 +1262,13 @@ class Worker(threading.Thread):
admin = self.session.query(db.Admin).filter_by(user_id=user.user_id).one_or_none() admin = self.session.query(db.Admin).filter_by(user_id=user.user_id).one_or_none()
if admin is None: if admin is None:
# Create the keyboard to be sent # Create the keyboard to be sent
keyboard = telegram.ReplyKeyboardMarkup([[strings.emoji_yes, strings.emoji_no]], one_time_keyboard=True) keyboard = telegram.ReplyKeyboardMarkup([[self.loc.get("emoji_yes"), self.loc.get("emoji_no")]], one_time_keyboard=True)
# Ask for confirmation # Ask for confirmation
self.bot.send_message(self.chat.id, strings.conversation_confirm_admin_promotion, reply_markup=keyboard) self.bot.send_message(self.chat.id, self.loc.get("conversation_confirm_admin_promotion"), reply_markup=keyboard)
# Wait for an answer # Wait for an answer
selection = self.__wait_for_specific_message([strings.emoji_yes, strings.emoji_no]) selection = self.__wait_for_specific_message([self.loc.get("emoji_yes"), self.loc.get("emoji_no")])
# Proceed only if the answer is yes # Proceed only if the answer is yes
if selection == strings.emoji_no: if selection == self.loc.get("emoji_no"):
return return
# Create a new admin # Create a new admin
admin = db.Admin(user=user, admin = db.Admin(user=user,
@ -1264,22 +1279,22 @@ class Worker(threading.Thread):
display_on_help=False) display_on_help=False)
self.session.add(admin) self.session.add(admin)
# Send the empty admin message and record the id # Send the empty admin message and record the id
message = self.bot.send_message(self.chat.id, strings.admin_properties.format(name=str(admin.user))) message = self.bot.send_message(self.chat.id, self.loc.get("admin_properties", name=str(admin.user)))
# Start accepting edits # Start accepting edits
while True: while True:
# Create the inline keyboard with the admin status # Create the inline keyboard with the admin status
inline_keyboard = telegram.InlineKeyboardMarkup([ inline_keyboard = telegram.InlineKeyboardMarkup([
[telegram.InlineKeyboardButton(f"{utils.boolmoji(admin.edit_products)} {strings.prop_edit_products}", [telegram.InlineKeyboardButton(f"{utils.boolmoji(admin.edit_products)} {self.loc.get('prop_edit_products')}",
callback_data="toggle_edit_products")], callback_data="toggle_edit_products")],
[telegram.InlineKeyboardButton(f"{utils.boolmoji(admin.receive_orders)} {strings.prop_receive_orders}", [telegram.InlineKeyboardButton(f"{utils.boolmoji(admin.receive_orders)} {self.loc.get('prop_receive_orders')}",
callback_data="toggle_receive_orders")], callback_data="toggle_receive_orders")],
[telegram.InlineKeyboardButton( [telegram.InlineKeyboardButton(
f"{utils.boolmoji(admin.create_transactions)} {strings.prop_create_transactions}", f"{utils.boolmoji(admin.create_transactions)} {self.loc.get('prop_create_transactions')}",
callback_data="toggle_create_transactions")], callback_data="toggle_create_transactions")],
[telegram.InlineKeyboardButton( [telegram.InlineKeyboardButton(
f"{utils.boolmoji(admin.display_on_help)} {strings.prop_display_on_help}", f"{utils.boolmoji(admin.display_on_help)} {self.loc.get('prop_display_on_help')}",
callback_data="toggle_display_on_help")], callback_data="toggle_display_on_help")],
[telegram.InlineKeyboardButton(strings.menu_done, callback_data="cmd_done")] [telegram.InlineKeyboardButton(self.loc.get('menu_done'), callback_data="cmd_done")]
]) ])
# Update the inline keyboard # Update the inline keyboard
self.bot.edit_message_reply_markup(message_id=message.message_id, self.bot.edit_message_reply_markup(message_id=message.message_id,
@ -1306,7 +1321,7 @@ class Worker(threading.Thread):
# If the session has expired... # If the session has expired...
if stop_trigger.reason == "timeout": if stop_trigger.reason == "timeout":
# Notify the user that the session has expired and remove the keyboard # Notify the user that the session has expired and remove the keyboard
self.bot.send_message(self.chat.id, strings.conversation_expired, self.bot.send_message(self.chat.id, self.loc.get('conversation_expired'),
reply_markup=telegram.ReplyKeyboardRemove()) reply_markup=telegram.ReplyKeyboardRemove())
# If a restart has been requested... # If a restart has been requested...
# Do nothing. # Do nothing.