Files
wallamanta/wallamanta/wallamanta.py
2024-04-08 11:48:14 +02:00

540 lines
28 KiB
Python

import threading
import logging
import helpers
import walladb
import constants
import account_checker
import time
import search_manager
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ForceReply
from telegram.constants import ParseMode
from telegram.ext import (
ApplicationBuilder,
CallbackQueryHandler,
CallbackContext,
CommandHandler,
ContextTypes,
ConversationHandler,
MessageHandler,
filters
)
SEARCH_THREADS_LIST = []
ACTION, ADD_PRODUCT_NAME, ADD_PRODUCT_MIN_PRICE, ADD_PRODUCT_MAX_PRICE, \
ADD_PRODUCT_CATEGORY, ADD_PRODUCT_TITLE_EXCLUDE, ADD_PRODUCT_DESCRIPTION_EXCLUDE, \
ADD_PRODUCT_COORDS, ADD_PRODUCT_DISTANCE, REMOVE_PRODUCT, LIST, FINISH, CONTINUE_OR_FINISH = range(13)
MAX_PRODUCTS_TESTING = 5
MAX_PRODUCTS_PREMIUM = 5
MAX_PRODUCTS_HOMELABS = 5
# Enable logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)
httpx_logger = logging.getLogger('httpx')
httpx_logger.setLevel(logging.WARNING)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if walladb.is_user_active(helpers.get_telegram_user_id(update)):
message = "Utiliza el menú de la conversación para añadir un producto y sigue los pasos indicados. Si tienes cualquier duda contacta con @jocarduck para más información."
else:
message = "No tienes permitido usar este bot."
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def main_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
context.user_data.clear()
keyboard = [
[
InlineKeyboardButton("Añadir", callback_data='add'),
InlineKeyboardButton("Listar", callback_data='list'),
InlineKeyboardButton("Borrar", callback_data='remove')]
]
markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text('Selecciona una acción a realizar', reply_markup=markup)
return ACTION
async def menu_click_handler(update: Update, context: CallbackContext):
await update.callback_query.edit_message_reply_markup(None)
telegram_user_id = helpers.get_telegram_user_id(update)
if walladb.is_user_active(telegram_user_id):
context.user_data['telegram_user_id'] = telegram_user_id
query = update.callback_query
number_of_products = walladb.count_user_products(telegram_user_id)
if query.data == 'add':
valid = False
number_of_products = walladb.count_user_products(telegram_user_id)
if walladb.is_user_testing(telegram_user_id):
valid = True
if number_of_products >= MAX_PRODUCTS_TESTING:
message = f"Ya tienes {MAX_PRODUCTS_TESTING} productos en seguimiento. Borra algunos para añadir más."
valid = False
elif walladb.is_user_premium(telegram_user_id):
valid = True
if number_of_products >= MAX_PRODUCTS_PREMIUM:
message = f"Ya tienes {MAX_PRODUCTS_PREMIUM} productos en seguimiento. Borra algunos para añadir más."
valid = False
elif walladb.is_user_homelabs(telegram_user_id):
valid = True
if number_of_products >= MAX_PRODUCTS_HOMELABS:
message = f"Ya tienes {MAX_PRODUCTS_HOMELABS} productos en seguimiento. Borra algunos para añadir más."
valid = False
if valid:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el nombre del producto a buscar', reply_markup=ForceReply())
return ADD_PRODUCT_NAME
else:
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'{message}')
return ConversationHandler.END
if query.data == 'remove':
if number_of_products != 0:
await send_list(update, context)
return REMOVE_PRODUCT
message = "No tienes ninguna búsqueda activa"
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'{message}')
return ConversationHandler.END
if query.data == 'list':
if number_of_products != 0:
await send_list(update, context)
return LIST
message = "No tienes ninguna búsqueda activa"
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'{message}')
return ConversationHandler.END
else:
await context.bot.send_message(chat_id=update.effective_chat.id,
text=helpers.telegram_escape_characters('No tienes permiso para usar este bot.'), parse_mode=ParseMode.MARKDOWN_V2)
return ConversationHandler.END
async def add_product_name(update: Update, context: CallbackContext):
if context.user_data.get('product_name', '') == '':
product_name = update.message.text
if len(product_name) > 200:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='El nombre del producto a buscar es muy largo, pon otro nombre', reply_markup=ForceReply())
return ADD_PRODUCT_NAME
else:
context.user_data['product_name'] = product_name
if not walladb.get_product(context.user_data):
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el precio mínimo', reply_markup=ForceReply())
return ADD_PRODUCT_MIN_PRICE
else:
await context.bot.send_message(chat_id=update.effective_chat.id,
text=f"¡{walladb.get_product(context.user_data)['product_name']} ya está en seguimiento!")
return ConversationHandler.END
async def add_product_min_price(update: Update, context: CallbackContext):
if context.user_data.get('min_price', '') == '':
answer = update.message.text
try:
min_price = int(answer)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio mínimo en números', reply_markup=ForceReply())
return ADD_PRODUCT_MIN_PRICE
if min_price < 0 or min_price > 999999:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio mínimo entre 0 y 999999', reply_markup=ForceReply())
return ADD_PRODUCT_MIN_PRICE
context.user_data['min_price'] = min_price
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el precio máximo', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
async def add_product_max_price(update: Update, context: CallbackContext):
if context.user_data.get('max_price', '') == '':
answer = update.message.text
try:
max_price = int(answer)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio máximo en números', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
if max_price < 0 or max_price > 999999:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio máximo entre 0 y 999999', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
if max_price < context.user_data['min_price']:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio máximo menor al precio mínimo', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
context.user_data['max_price'] = max_price
keyboard = [
[
InlineKeyboardButton("Añadir", callback_data='add'),
InlineKeyboardButton("Descartar", callback_data='remove')
], [InlineKeyboardButton("Escoge una categoría", callback_data='category'),],
]
markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f'{context.user_data["product_name"]} de {context.user_data["min_price"]}€ a {context.user_data["max_price"]}', reply_markup=markup)
return ADD_PRODUCT_CATEGORY
async def add_product_category(update: Update, context: CallbackContext):
await update.callback_query.edit_message_reply_markup(None)
query = update.callback_query
if query.data == 'category':
markup = InlineKeyboardMarkup(helpers.create_category_keyboard())
context.user_data['last_step'] = 'category'
await context.bot.send_message(chat_id=update.effective_chat.id, text='Escoge la categoría', reply_markup=markup)
return CONTINUE_OR_FINISH
elif query.data == 'add':
await add_to_db_and_send(update, context)
return ConversationHandler.END
elif query.data == 'remove':
context.user_data.clear()
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Borrado')
return ConversationHandler.END
async def continue_or_finish(update: Update, context: CallbackContext):
if update.callback_query != None:
await update.callback_query.edit_message_reply_markup(None)
markup = InlineKeyboardMarkup(helpers.create_continue_keyboard())
last_step = context.user_data.get('last_step', '')
query = update.callback_query
try:
qd = query.data
logging.info(f"Query Data: {qd}")
except:
qd = ''
if qd == 'add':
await add_to_db_and_send(update, context)
return ConversationHandler.END
if qd == 'remove':
context.user_data.clear()
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Borrado')
return ConversationHandler.END
if qd == 'add_subcategory':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escoge la categoría', reply_markup=InlineKeyboardMarkup(helpers.create_categories_keyboard(context.user_data['category'])))
context.user_data['last_step'] = 'choose_subcategory'
return CONTINUE_OR_FINISH
if qd == 'add_category':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Categoría', reply_markup=InlineKeyboardMarkup(helpers.create_category_keyboard()))
context.user_data['last_step'] = 'category'
return CONTINUE_OR_FINISH
if last_step == "choose_subcategory":
category = query.data
if category != 'finish':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escoge la subcategoría', reply_markup=InlineKeyboardMarkup(helpers.create_subcategory_keyboard(category)))
context.user_data['last_step'] = 'subcategory'
return CONTINUE_OR_FINISH
else:
context.user_data['last_step'] = ''
if last_step == 'subcategory':
subcategory = query.data
if subcategory != 'finish':
text = f"✅Subcategoría escogida: {helpers.get_subcategory_name(subcategory)}"
if context.user_data.get('subcategory'):
if subcategory not in context.user_data['subcategory'].split(','):
context.user_data['subcategory'] = context.user_data['subcategory'] + ',' + subcategory
else:
text = f"❌La subcategoría {helpers.get_subcategory_name(subcategory)} ya estaba añadida❌"
else:
context.user_data['subcategory'] = subcategory
await context.bot.send_message(chat_id=update.effective_chat.id,
text=text)
logging.info(f"{context.user_data['subcategory']}")
context.user_data['last_step'] = ''
if last_step == 'category':
category = query.data
if category != 'finish':
text = f"✅Categoría escogida: {helpers.get_category_name(category)}"
if context.user_data.get('category'):
if category not in context.user_data['category'].split(','):
context.user_data['category'] = context.user_data['category'] + ',' + category
else:
text = f"❌La categoría {helpers.get_category_name(category)} ya estaba añadida❌"
else:
context.user_data['category'] = category
await context.bot.send_message(chat_id=update.effective_chat.id,
text=text)
logging.info(f"{context.user_data['category']}")
context.user_data['last_step'] = ''
if qd == 'title_exclude':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Exclusión de título', reply_markup=ForceReply())
context.user_data['last_step'] = 'title_exclude'
return CONTINUE_OR_FINISH
if last_step == 'title_exclude':
context.user_data['title_exclude'] = update.message.text
context.user_data['last_step'] = ''
if qd == 'title_description_exclude':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Exclusión de descripción', reply_markup=ForceReply())
context.user_data['last_step'] = 'title_description_exclude'
return CONTINUE_OR_FINISH
if last_step == 'title_description_exclude':
context.user_data['title_description_exclude'] = update.message.text
context.user_data['last_step'] = ''
if qd == 'coords':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Coordenadas', reply_markup=ForceReply())
context.user_data['last_step'] = 'coords'
return CONTINUE_OR_FINISH
if last_step == 'coords':
answer = update.message.text
try:
latitude = round(float(answer.split(',')[0]), 4)
longitude = round(float(answer.split(',')[1]), 4)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon las coordenadas siguiendo el ejemplo: 41.34, 0.65', reply_markup=ForceReply())
return CONTINUE_OR_FINISH
context.user_data['latitude'] = str(latitude)
context.user_data['longitude'] = str(longitude)
context.user_data['last_step'] = ''
if qd == 'distance':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Distancia', reply_markup=ForceReply())
context.user_data['last_step'] = 'distance'
return CONTINUE_OR_FINISH
if last_step == 'distance':
answer = update.message.text
try:
latitude = int(answer)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon la distancia en km, por ejemplo: 100', reply_markup=ForceReply())
return CONTINUE_OR_FINISH
context.user_data['distance'] = answer
context.user_data['last_step'] = ''
if context.user_data['last_step'] == '':
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Añadir?', reply_markup=markup)
return CONTINUE_OR_FINISH
async def add_subcategory(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
pass
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
logging.info('Conversation cancelled')
context.user_data.clear()
await context.bot.send_message(chat_id=update.effective_chat.id, text="Cancelado.")
return ConversationHandler.END
async def remove_product(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
global SEARCH_THREADS_LIST
await update.callback_query.edit_message_reply_markup(None)
query = update.callback_query
product_name = query.data
telegram_user_id = helpers.get_telegram_user_id(update)
message = f"¡{product_name} no está en seguimiento!"
if walladb.remove_product({'product_name' : product_name, \
'telegram_user_id' : telegram_user_id}):
message = f"¡{product_name} borrado de la lista de seguimiento!"
await context.bot.send_message(chat_id=update.effective_chat.id, text=message)
return ConversationHandler.END
async def product_details(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.callback_query.edit_message_reply_markup(None)
query = update.callback_query
product_name = query.data
telegram_user_id = helpers.get_telegram_user_id(update)
product = walladb.get_product({'product_name':product_name,'telegram_user_id':telegram_user_id})
if product:
categories = helpers.generate_categories_string(product['category'], product['subcategory'])
text = f"*Nombre del producto:* {helpers.telegram_escape_characters(product['product_name'])}\n\
*Precio desde *{helpers.telegram_escape_characters(str(product['min_price']))}€ *hasta *{helpers.telegram_escape_characters(str(product['max_price']))}\n\
*En las coordenadas *{helpers.telegram_escape_characters(str(product['latitude']))}, {helpers.telegram_escape_characters(str(product['longitude']))} *y a *{product['distance']}km *de estas*\n\
*En las categorías: *{helpers.telegram_escape_characters(categories)}\n\
*Palabras excluídas del título: *`{helpers.telegram_escape_characters(product['title_exclude'])}`\n\
*Palabras excluídas del título y la descripción: *`{helpers.telegram_escape_characters(product['title_description_exclude'])}`"
await context.bot.send_message(chat_id=update.effective_chat.id, text=text, parse_mode=ParseMode.MARKDOWN_V2)
return ConversationHandler.END
async def send_list(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
telegram_user_id = helpers.get_telegram_user_id(update)
if walladb.is_user_active(telegram_user_id):
if walladb.is_user_testing(telegram_user_id) or walladb.is_user_premium(telegram_user_id):
query = update.callback_query
if query.data == 'remove':
text = "Escoge un producto para borrar"
if query.data == 'list':
text = "Escoge un producto para ver los detalles"
keyboard = helpers.create_products_keyboard(telegram_user_id)
markup = InlineKeyboardMarkup(keyboard)
await context.bot.send_message(chat_id=update.effective_chat.id, text=text, reply_markup=markup)
async def admin_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if helpers.is_user_admin(update.message.chat_id):
await update.message.reply_markdown_v2(helpers.telegram_escape_characters("¡Eres admin!"))
async def add_premium_user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if helpers.is_user_admin(update.message.chat_id):
telegram_user_id = update.message.text.split('/add_premium_user ')[1].split(' ')[0]
days = update.message.text.split('/add_premium_user ')[1].split(' ')[1]
until = helpers.get_date_ahead(int(days))
if not walladb.add_premium_user(telegram_user_id, until):
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"{telegram_user_id} re-activado hasta {until}. Re-activando productos."))
else:
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"{telegram_user_id} añadido hasta {until}."))
async def remove_user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if helpers.is_user_admin(update.message.chat_id):
telegram_user_id = update.message.text.split('/remove_user ')[1]
walladb.remove_valid_user(telegram_user_id)
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"{telegram_user_id} desactivado."))
async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
telegram_user_id = helpers.get_telegram_user_id(update)
if walladb.is_user_active(telegram_user_id):
type = walladb.get_user_type(telegram_user_id)
until = walladb.get_user_until(telegram_user_id)
message = f"Tu cuenta es tipo: {type} y caduca el {helpers.get_spanish_date(until)}."
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
telegram_user_id = helpers.get_telegram_user_id(update)
telegram_user_name = helpers.get_telegram_user_name(update)
#if not walladb.is_user_valid(telegram_user_id):
message = f"Bienvenido a @wallamanta_homelabs_vip, usa el comando /help para más información."
if telegram_user_id < 0:
message = "Este bot no se puede usar en grupos."
elif walladb.is_user_premium(telegram_user_id):
message = f"Bienvenido a @wallamanta_homelabs_vip, usa el comando /help para más información."
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def add_to_db_and_send(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
logging.info(f"Adding product with context: {context.user_data}")
walladb.add_product(context.user_data)
await context.bot.send_message(chat_id=update.effective_chat.id, text=f"¡*{helpers.telegram_escape_characters(context.user_data['product_name'])}* añadido correctamente\!", parse_mode=ParseMode.MARKDOWN_V2)
def error(update, context):
logging.error(f'Update ---{update}--- caused error ---{context.error}---')
async def conv_timeout(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await context.bot.send_message(chat_id=update.effective_chat.id, text="Se ha superado el tiempo de espera, vuelve a usar el menú si quieres añadir otro producto.")
async def conv_finish(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await context.bot.send_message(chat_id=update.effective_chat.id, text="Vuelve a usar el menú si quieres añadir otro producto.")
async def list_threads(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if helpers.is_user_admin(update.message.chat_id):
global SEARCH_THREADS_LIST
tmp_search_threads_list = []
threads_string = ""
for thread in SEARCH_THREADS_LIST:
if thread[1].is_alive():
tmp_search_threads_list.append((thread[0], thread[1]))
threads_string = threads_string + f"{thread[0]['product_name']} - {thread[0]['telegram_user_id']}\n"
SEARCH_THREADS_LIST = tmp_search_threads_list
if len(threads_string) > 2000:
my_strings = [(threads_string[i:i+2000]) for i in range(0, len(threads_string), 2000)]
for my_string in my_strings:
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"{my_string}"))
else:
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"{threads_string}"))
async def message_to_all(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if helpers.is_user_admin(update.message.chat_id):
message = update.message.text.split('/message_to_all ')[1]
helpers.send_message_to_all(message)
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"Messages sent to all users"))
async def code(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
code = update.message.text.split('/code ')[1]
telegram_user_id = update.message.chat_id
if len(code) != 12:
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"El código no tiene el formato correcto"))
else:
code = walladb.get_code(code)
if code:
until = helpers.get_date_ahead(code['days'])
try:
if code['type'] == 'premium':
walladb.add_premium_user(telegram_user_id, until)
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"Activado periodo premium hasta el {until}"))
elif code['type'] == 'testing':
walladb.add_test_user(telegram_user_id, until)
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"Activado periodo testing hasta el {until}"))
walladb.use_code(code)
except Exception as e:
error_code = helpers.get_random_string(8)
logging.info(f"Error trying to checkout code {code}: {e}. {error_code}")
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"Ha habido un error, contacta con @jocarduck y dale el siguiente código de error: `{error_code}`"))
else:
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(f"El código no es válido o ya ha sido usado"))
def count_threads():
time.sleep(10)
while True:
logging.info(f"=== There are: {threading.active_count()} threads. ===")
time.sleep(60)
def main()->None:
walladb.setup_db()
"""Start the bot."""
# Create the Application and pass it your bot's token.
if constants.telegram_proxy:
logging.info("Creating application with socks5 proxy")
application = ApplicationBuilder().get_updates_http_version('1.1').http_version('1.1').token(constants.TELEGRAM_TOKEN).proxy(constants.proxy_url).get_updates_proxy(constants.proxy_url).build()
else:
logging.info("Creating application without socks5 proxy")
application = ApplicationBuilder().get_updates_http_version('1.1').http_version('1.1').token(constants.TELEGRAM_TOKEN).build()
p = threading.Thread(target=search_manager.work)
p.start()
p = threading.Thread(target=account_checker.work, args=(3600, ))
p.start()
#p = threading.Thread(target=count_threads)
#p.start()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start_command))
application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("admin", admin_command))
application.add_handler(CommandHandler("add_premium_user", add_premium_user_command))
application.add_handler(CommandHandler("remove_user", remove_user_command))
application.add_handler(CommandHandler("status", status_command))
application.add_handler(CommandHandler("list_threads", list_threads))
application.add_handler(CommandHandler("message_to_all", message_to_all))
application.add_handler(CommandHandler("code", code))
#application.add_handler(CallbackQueryHandler("list", send_list()))
#application.add_handler(CallbackQueryHandler(pattern="list", callback=send_list()))
conv_handler = ConversationHandler(
conversation_timeout=120,
entry_points=[CommandHandler("menu", main_menu)],
states={
ACTION: [CallbackQueryHandler(menu_click_handler)],
ADD_PRODUCT_NAME: [MessageHandler(filters.TEXT, add_product_name),
CallbackQueryHandler(add_product_name)],
ADD_PRODUCT_MIN_PRICE: [MessageHandler(filters.TEXT, add_product_min_price)],
ADD_PRODUCT_MAX_PRICE: [MessageHandler(filters.TEXT, add_product_max_price)],
ADD_PRODUCT_CATEGORY: [CallbackQueryHandler(add_product_category)],
CONTINUE_OR_FINISH: [CallbackQueryHandler(continue_or_finish),
MessageHandler(filters.TEXT, continue_or_finish)],
REMOVE_PRODUCT: [CallbackQueryHandler(remove_product)],
LIST: [CallbackQueryHandler(product_details)],
ConversationHandler.TIMEOUT: [CallbackQueryHandler(conv_timeout),
MessageHandler(filters.TEXT, conv_timeout)],
ConversationHandler.END: [MessageHandler(filters.TEXT, conv_finish)],
},
fallbacks=[CommandHandler("cancel", cancel)],
)
application.add_handler(conv_handler)
application.add_error_handler(error)
application.run_polling()
if __name__ == "__main__":
main()