1
Fork 0
mirror of https://github.com/Steffo99/greed.git synced 2024-11-21 21:44:19 +00:00

Handle telegram exceptions

This commit is contained in:
Steffo 2018-04-05 09:57:59 +02:00
parent 410aa99018
commit 2ba2be62ff
3 changed files with 94 additions and 23 deletions

View file

@ -4,13 +4,13 @@ import time
import strings import strings
import worker import worker
import configloader import configloader
import utils
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!"""
# Create a bot instance # Create a bot instance
bot = telegram.Bot(configloader.config["Telegram"]["token"]) bot = utils.DuckBot(configloader.config["Telegram"]["token"])
# Test the specified token # Test the specified token
try: try:

View file

@ -1,6 +1,6 @@
import telegram import telegram
import telegram.error import telegram.error
import time
from configloader import config from configloader import config
from strings import currency_format_string, currency_symbol from strings import currency_format_string, currency_symbol
import typing import typing
@ -66,6 +66,7 @@ class Price:
return Price(Price(other).value - self.value) return Price(Price(other).value - self.value)
def __rmul__(self, other): def __rmul__(self, other):
return self.__mul__(other) return self.__mul__(other)
def __iadd__(self, other): def __iadd__(self, other):
@ -85,3 +86,73 @@ class Price:
self.value //= other self.value //= other
return self return self
def catch_telegram_errors(func):
"""Decorator, can be applied to any function to retry in case of Telegram errors."""
def result_func(*args, **kwargs):
while True:
try:
func(*args, **kwargs)
except telegram.error.Unauthorized:
print(f"Unauthorized to call {func.__name__}(), skipping.")
break
except telegram.error.TimedOut:
print(f"Timed out while calling {func.__name__}(), retrying in 1 sec...")
time.sleep(1)
except telegram.error.NetworkError:
print(f"Network error while calling {func.__name__}(), retrying in 5 secs...")
time.sleep(5)
else:
break
return result_func
class DuckBot:
def __init__(self, *args, **kwargs):
self.bot = telegram.Bot(*args, **kwargs)
@catch_telegram_errors
def send_message(self, *args, **kwargs):
self.bot.send_message(*args, **kwargs)
@catch_telegram_errors
def edit_message_text(self, *args, **kwargs):
self.bot.edit_message_text(*args, **kwargs)
@catch_telegram_errors
def edit_message_caption(self, *args, **kwargs):
self.bot.edit_message_caption(*args, **kwargs)
@catch_telegram_errors
def edit_message_reply_markup(self, *args, **kwargs):
self.bot.edit_message_reply_markup(*args, **kwargs)
@catch_telegram_errors
def get_updates(self, *args, **kwargs):
self.bot.get_updates(*args, **kwargs)
@catch_telegram_errors
def get_me(self, *args, **kwargs):
self.bot.get_me(*args, **kwargs)
@catch_telegram_errors
def answer_callback_query(self, *args, **kwargs):
self.bot.answer_callback_query(*args, **kwargs)
@catch_telegram_errors
def answer_pre_checkout_query(self, *args, **kwargs):
self.bot.answer_pre_checkout_query(*args, **kwargs)
@catch_telegram_errors
def send_invoice(self, *args, **kwargs):
self.bot.send_invoice(*args, **kwargs)
@catch_telegram_errors
def get_file(self, *args, **kwargs):
self.bot.get_file(*args, **kwargs)
@catch_telegram_errors
def send_chat_action(self, *args, **kwargs):
self.bot.send_chat_action(*args, **kwargs)
# TODO: add more methods

View file

@ -9,7 +9,7 @@ import sys
import queue as queuem import queue as queuem
import database as db import database as db
import re import re
from utils import Price import utils
from html import escape from html import escape
class StopSignal: class StopSignal:
@ -27,17 +27,17 @@ class CancelSignal:
class ChatWorker(threading.Thread): class ChatWorker(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: telegram.Bot, chat: telegram.Chat, *args, **kwargs): def __init__(self, bot: utils.DuckBot, chat: telegram.Chat, *args, **kwargs):
# Initialize the thread # Initialize the thread
super().__init__(name=f"ChatThread {chat.first_name}", *args, **kwargs) super().__init__(name=f"ChatThread {chat.first_name}", *args, **kwargs)
# Store the bot and chat info inside the class # Store the bot and chat info inside the class
self.bot = bot self.bot: utils.DuckBot = bot
self.chat = chat self.chat: telegram.Chat = chat
# Open a new database session # Open a new database session
self.session = db.Session() self.session = db.Session()
# Get the user db data from the users and admin tables # Get the user db data from the users and admin tables
self.user = None self.user: typing.Optional[db.User] = None
self.admin = None self.admin: typing.Optional[db.Admin] = None
# The sending pipe is stored in the ChatWorker class, allowing the forwarding of messages to the chat process # The sending pipe is stored in the ChatWorker class, allowing the forwarding of messages to the chat process
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
@ -216,7 +216,7 @@ class ChatWorker(threading.Thread):
[telegram.KeyboardButton(strings.menu_bot_info)]] [telegram.KeyboardButton(strings.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=Price(self.user.credit)), strings.conversation_open_user_menu.format(credit=utils.Price(self.user.credit)),
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True), reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True),
parse_mode="HTML") parse_mode="HTML")
# Wait for a reply from the user # Wait for a reply from the user
@ -295,7 +295,7 @@ class ChatWorker(threading.Thread):
caption=product.text(style="image", cart_qty=cart[callback.message.message_id][1]), parse_mode="HTML", reply_markup=product_inline_keyboard) caption=product.text(style="image", cart_qty=cart[callback.message.message_id][1]), parse_mode="HTML", reply_markup=product_inline_keyboard)
# Create the cart summary # Create the cart summary
product_list = "" product_list = ""
total_cost = Price(0) total_cost = utils.Price(0)
for product_id in cart: for product_id in cart:
if cart[product_id][1] > 0: 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(style="short", cart_qty=cart[product_id][1]) + "\n"
@ -332,7 +332,7 @@ class ChatWorker(threading.Thread):
caption=product.text(style="image", cart_qty=cart[callback.message.message_id][1]), parse_mode="HTML", reply_markup=product_inline_keyboard) caption=product.text(style="image", cart_qty=cart[callback.message.message_id][1]), parse_mode="HTML", reply_markup=product_inline_keyboard)
# Create the cart summary # Create the cart summary
product_list = "" product_list = ""
total_cost = Price(0) total_cost = utils.Price(0)
for product_id in cart: for product_id in cart:
if cart[product_id][1] > 0: 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(style="short", cart_qty=cart[product_id][1]) + "\n"
@ -447,10 +447,10 @@ class ChatWorker(threading.Thread):
def __add_credit_cc(self): def __add_credit_cc(self):
"""Add money to the wallet through a credit card payment.""" """Add money to the wallet through a credit card payment."""
# Create a keyboard to be sent later # Create a keyboard to be sent later
keyboard = [[telegram.KeyboardButton(str(Price("10.00")))], keyboard = [[telegram.KeyboardButton(str(utils.Price("10.00")))],
[telegram.KeyboardButton(str(Price("25.00")))], [telegram.KeyboardButton(str(utils.Price("25.00")))],
[telegram.KeyboardButton(str(Price("50.00")))], [telegram.KeyboardButton(str(utils.Price("50.00")))],
[telegram.KeyboardButton(str(Price("100.00")))], [telegram.KeyboardButton(str(utils.Price("100.00")))],
[telegram.KeyboardButton(strings.menu_cancel)]] [telegram.KeyboardButton(strings.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
@ -468,13 +468,13 @@ class ChatWorker(threading.Thread):
cancelled = True cancelled = True
continue continue
# Convert the amount to an integer # Convert the amount to an integer
value = Price(selection) value = utils.Price(selection)
# Ensure the amount is within the range # Ensure the amount is within the range
if value > 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, strings.error_payment_amount_over_max.format(max_amount=Price(configloader.config["Payments"]["max_amount"]))) self.bot.send_message(self.chat.id, strings.error_payment_amount_over_max.format(max_amount=utils.Price(configloader.config["Payments"]["max_amount"])))
continue continue
elif value < 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, strings.error_payment_amount_under_min.format(min_amount=Price(configloader.config["Payments"]["min_amount"]))) self.bot.send_message(self.chat.id, strings.error_payment_amount_under_min.format(min_amount=utils.Price(configloader.config["Payments"]["min_amount"])))
continue continue
break break
# If the user cancelled the action... # If the user cancelled the action...
@ -637,7 +637,7 @@ class ChatWorker(threading.Thread):
self.bot.send_message(self.chat.id, strings.ask_product_price, parse_mode="HTML") self.bot.send_message(self.chat.id, strings.ask_product_price, parse_mode="HTML")
# 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=(str(Price(product.price)) if product.price is not None else 'Non in vendita')), parse_mode="HTML", reply_markup=cancel) self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=(str(utils.Price(product.price)) if product.price is not None else 'Non in vendita')), parse_mode="HTML", reply_markup=cancel)
# Wait for an answer # Wait for an answer
price = self.__wait_for_regex(r"([0-9]{1,3}(?:[.,][0-9]{1,2})?|[Xx])", cancellable=True) price = self.__wait_for_regex(r"([0-9]{1,3}(?:[.,][0-9]{1,2})?|[Xx])", cancellable=True)
# If the price is skipped # If the price is skipped
@ -646,7 +646,7 @@ class ChatWorker(threading.Thread):
elif price.lower() == "x": elif price.lower() == "x":
price = None price = None
else: else:
price = 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, strings.ask_product_image, reply_markup=cancel)
# Wait for an answer # Wait for an answer