Modified categories. Remove buttons when used. Added timeout to conversation. Check if user is expired. Added telegram name. Fixed bug with lowercase.

This commit is contained in:
Joan
2023-03-10 23:52:25 +01:00
parent 5841038d55
commit f4446ed6fb
6 changed files with 110 additions and 86 deletions

View File

@@ -10,28 +10,26 @@ LATITUDE = os.getenv("LATITUDE")
LONGITUDE = os.getenv("LONGITUDE")
SLEEP_TIME = int(os.getenv("SLEEP_TIME"))
CATEGORIES = {'coches': 100,
'motos': 14000,
'motor y accesorios': 12800,
'moda y accesorios': 12465,
'inmobiliaria': 200,
'tv audio y foto': 12545,
'móviles y telefonía': 16000,
'informática y electrónica': 15000,
'informática': 15000,
'electrónica': 15000,
'deporte y ocio': 12579,
'bicicletas': 17000,
'consolas y videojuegos': 12900,
'consolas': 12900,
'videojuegos': 12900,
'hogar y jardín': 12467,
'electrodomésticos': 13100,
'cine libros y música': 12463,
'niños y bebés': 12461,
'coleccionismo': 18000,
'construcción y reformas': 19000,
'industria y agricultura': 20000,
'empleo': 21000,
'servicios': 13200,
'otros': 12485}
CATEGORIES = {'🚗 Coches 🚗': 100,
'🏍️ Motos 🏍️': 14000,
'🚙 Motor 🚙': 12800,
'👗 Moda 👗': 12465,
'🏘️ Inmobiliaria 🏘️': 200,
'📺 TV, audio y foto 📺': 12545,
'📱 Móviles 📱': 16000,
'🖥 Informática 🖥️': 15000,
'🏅 Deportes y ocio 🏅': 12579,
'🚴 Bicicletas 🚴': 17000,
'🎮 Consolas y videojuegos 🎮': 12900,
'🏡 Hogar y jardín 🏡': 12467,
'Electrodomésticos': 13100,
'🎥 Cine, libros y música 🎥': 12463,
'🧒 Niños y bebés 🧒': 12461,
'Coleccionismo': 18000,
'Construcción y reformas': 19000,
'Industria y agricultura': 20000,
'Empleo': 21000,
'Servicios': 13200,
'Otros': 12485,
'Todas': 0}

View File

@@ -35,10 +35,18 @@ def get_telegram_user_id(update):
#return update.message.chat_id
return update.effective_chat.id
def get_telegram_user_name(update):
return update.message.from_user
def get_date_ahead(add_days):
date_ahead = datetime.today() + timedelta(days=add_days)
return date_ahead.strftime("%d/%m/%Y")
def is_date_expired(until):
until_date = datetime.strptime(until, "%d/%m/%Y")
difference = until_date - datetime.today()
return difference.days < 0
def random_wait():
time.sleep(random.random())
@@ -114,32 +122,40 @@ def get_category_id(category):
ret = constants.CATEGORIES.get(category, '')
return ret
def get_category_name(category):
category_name = ''
for i_category in constants.CATEGORIES:
if constants.CATEGORIES[i_category] == category:
category_name = i_category
break
return category_name
def create_category_keyboard():
keyboard = [
[
InlineKeyboardButton("🖥 Informática", callback_data='15000'),
InlineKeyboardButton("Motor", callback_data='12800'),
InlineKeyboardButton("Coches", callback_data='100'),
InlineKeyboardButton("🖥 Informática 🖥️ ", callback_data='15000'),
InlineKeyboardButton("🚙 Motor 🚙", callback_data='12800'),
InlineKeyboardButton("🚗 Coches 🚗", callback_data='100'),
],
[
InlineKeyboardButton("Motos", callback_data='14000'),
InlineKeyboardButton("Moda", callback_data='12465'),
InlineKeyboardButton("Inmobiliaria", callback_data='200'),
InlineKeyboardButton("🏍️ Motos 🏍️", callback_data='14000'),
InlineKeyboardButton("👗 Moda 👗", callback_data='12465'),
InlineKeyboardButton("🏘️ Inmobiliaria 🏘️", callback_data='200'),
],
[
InlineKeyboardButton("TV, audio y foto", callback_data='12545'),
InlineKeyboardButton("Móviles", callback_data='16000'),
InlineKeyboardButton("Deportes y ocio", callback_data='12579'),
InlineKeyboardButton("📺 TV, audio y foto 📺", callback_data='12545'),
InlineKeyboardButton("📱 Móviles 📱", callback_data='16000'),
InlineKeyboardButton("🏅 Deportes y ocio 🏅", callback_data='12579'),
],
[
InlineKeyboardButton("Bicicletas", callback_data='17000'),
InlineKeyboardButton("Consolas y videojuegos", callback_data='12900'),
InlineKeyboardButton("Hogar y jardín", callback_data='12467'),
InlineKeyboardButton("🚴 Bicicletas 🚴", callback_data='17000'),
InlineKeyboardButton("🎮 Consolas y videojuegos 🎮", callback_data='12900'),
InlineKeyboardButton("🏡 Hogar y jardín 🏡", callback_data='12467'),
],
[
InlineKeyboardButton("Electrodomésticos", callback_data='13100'),
InlineKeyboardButton("Cine, libros y música", callback_data='12463'),
InlineKeyboardButton("Niños y bebés", callback_data='12461'),
InlineKeyboardButton("🎥 Cine, libros y música 🎥", callback_data='12463'),
InlineKeyboardButton("🧒 Niños y bebés 🧒", callback_data='12461'),
],
[
InlineKeyboardButton("Coleccionismo", callback_data='18000'),
@@ -164,7 +180,7 @@ def create_continue_keyboard():
InlineKeyboardButton("Descartar", callback_data='remove')
],[
InlineKeyboardButton("Exclusión de título", callback_data='title_exclude'),
InlineKeyboardButton("Exclusión de descripción", callback_data='description_exclude')
InlineKeyboardButton("Exclusión de descripción", callback_data='title_description_exclude')
],[
InlineKeyboardButton("Coordenadas", callback_data='coords'),
InlineKeyboardButton("Distancia", callback_data='distance')

View File

@@ -1,4 +1,5 @@
python-telegram-bot==20.1
python-telegram-bot[job-queue]==20.1
requests==2.28.1
prettytable==3.6.0
Pillow==9.4.0

View File

@@ -1,7 +1,7 @@
import sqlite3
import os
import logging
import constants
import helpers
# Enable logging
logging.basicConfig(
@@ -19,7 +19,7 @@ def dict_factory(cursor, row):
def setup_db():
con = sqlite3.connect(constants.DB)
cur = con.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS users(telegram_user_id, active, type, until)")
cur.execute("CREATE TABLE IF NOT EXISTS users(telegram_user_id, active, type, until, telegram_name)")
cur.execute("CREATE TABLE IF NOT EXISTS products(product_name, distance, \
latitude, longitude, condition, min_price, max_price, category, \
title_exclude, title_description_exclude, telegram_user_id)")
@@ -33,6 +33,18 @@ def is_user_valid(telegram_user_id):
con.close()
return ret
def is_user_expired(telegram_user_id):
con = sqlite3.connect(constants.DB)
cur = con.cursor()
res = cur.execute(f"SELECT until FROM users WHERE telegram_user_id={telegram_user_id}")
q_res = res.fetchone()
ret = True
if q_res != None:
if not helpers.is_date_expired(q_res[0]):
ret = False
con.close()
return ret
def is_user_premium(telegram_user_id):
con = sqlite3.connect(constants.DB)
cur = con.cursor()
@@ -49,13 +61,14 @@ def is_user_testing(telegram_user_id):
con.close()
return ret
def add_premium_user(telegram_user_id, until):
def add_premium_user(telegram_user_id, telegram_name, until):
found = False
con = sqlite3.connect(constants.DB)
cur = con.cursor()
res = cur.execute(f"SELECT * FROM users WHERE telegram_user_id={telegram_user_id}")
if res.fetchone() is None:
cur.execute(f"INSERT INTO users VALUES ({telegram_user_id}, True, 'premium', '{until}')")
params = (telegram_user_id, True, 'premium', until, telegram_name.first_name)
cur.execute("INSERT INTO users VALUES (?, ?, ?, ?, ?)", params)
con.commit()
else:
cur.execute(f"UPDATE users SET active = True, type = 'premium', until = '{until}' WHERE telegram_user_id={telegram_user_id}")
@@ -65,13 +78,14 @@ def add_premium_user(telegram_user_id, until):
logging.info(f"Added premium user {telegram_user_id} until {until}")
return found
def add_test_user(telegram_user_id, until):
def add_test_user(telegram_user_id, telegram_name, until):
found = False
con = sqlite3.connect(constants.DB)
cur = con.cursor()
res = cur.execute(f"SELECT * FROM users WHERE telegram_user_id={telegram_user_id}")
if res.fetchone() is None:
cur.execute(f"INSERT INTO users VALUES ({telegram_user_id}, True, 'testing', '{until}')")
params = (telegram_user_id, True, 'testing', until, telegram_name.first_name)
cur.execute("INSERT INTO users VALUES (?, ?, ?, ?, ?)", params)
con.commit()
else:
found = True
@@ -114,7 +128,7 @@ def get_user_until(telegram_user_id):
return ret[0]
def get_product(product):
product_name = product.get('product_name')
product_name = product.get('product_name').lower()
telegram_user_id = product.get('telegram_user_id')
con = sqlite3.connect(constants.DB)
con.row_factory = dict_factory

View File

@@ -31,28 +31,11 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
def parse_json_file():
f = open("/app/data/products.json")
return json.load(f)
def save_json_file(products):
with open('/app/data/products.json', 'w') as outfile:
json.dump(products, outfile, indent=2)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if walladb.is_user_valid(helpers.get_telegram_user_id(update)):
message = """Añade un producto con `/add producto;precio_mínimo;precio_máximo,categoría,excluir_título(opcional, separado por comas);
excluir_descripción_y_título(opciona, separado por comas);latitud(opcional);longitud(opcional),distancia(opcional)`\n
Ejemplo: `/add placa base itx;0;150`\n
Ejemplo 2: `/add cpu;10;30;;;intel,core 2 duo,celeron;;;100`\n
Ejemplo 3: `/add tiny;0;100;informática`\n
Los campos opcionales que se dejen vacíos tomarán el valor configurado en el archivo `.env`\n
Lista los productos con `/list` o obtén la información de uno en concreto con `/list nombre del producto`\n
Borra un producto con `/remove nombre del producto`\n
`/status` muestra tu tipo de membresía y fecha de caducidad\n
`/categories` muestra las categorías disponibles"""
message = "Utiliza el menú de la conversación para añadir un producto y sigue los pasos indicados"
else:
message = """Activa tu periodo de prueba de 7 días con `/test` o contacta con @jocarduck para más información."""
message = "Activa tu periodo de prueba de 7 días con `/test` o contacta con @jocarduck para más información."
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def main_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -68,6 +51,7 @@ async def main_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
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_valid(telegram_user_id):
context.user_data['telegram_user_id'] = telegram_user_id
@@ -99,11 +83,10 @@ async def menu_click_handler(update: Update, context: CallbackContext):
return REMOVE_PRODUCT
if query.data == 'list':
await send_list(update, context)
#await context.bot.send_message(chat_id=update.effective_chat.id, text='Send your name', reply_markup=ForceReply())
return ConversationHandler.END
else:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Activa tu periodo de prueba de 7 días con `/test` o contacta con @jocarduck para más información.', parse_mode=ParseMode.MARKDOWN_V2)
text=helpers.telegram_escape_characters('Activa tu periodo de prueba de 7 días con `/test` o contacta con @jocarduck para más información.'), parse_mode=ParseMode.MARKDOWN_V2)
return ConversationHandler.END
async def add_product_name(update: Update, context: CallbackContext):
@@ -162,6 +145,7 @@ async def add_product_max_price(update: Update, context: CallbackContext):
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())
@@ -177,6 +161,8 @@ async def add_product_category(update: Update, context: CallbackContext):
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
@@ -195,6 +181,8 @@ async def continue_or_finish(update: Update, context: CallbackContext):
if last_step == 'category':
category = int(query.data)
await context.bot.send_message(chat_id=update.effective_chat.id,
text=f"Categoría escogida: {helpers.get_category_name(category)}")
context.user_data['category'] = category
context.user_data['last_step'] = ''
@@ -207,13 +195,13 @@ async def continue_or_finish(update: Update, context: CallbackContext):
context.user_data['title_exclude'] = update.message.text
context.user_data['last_step'] = ''
if qd == 'description_exclude':
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'] = 'description_exclude'
context.user_data['last_step'] = 'title_description_exclude'
return CONTINUE_OR_FINISH
if last_step == 'description_exclude':
context.user_data['description_exclude'] = update.message.text
if last_step == 'title_description_exclude':
context.user_data['title_description_exclude'] = update.message.text
context.user_data['last_step'] = ''
if qd == 'coords':
@@ -255,6 +243,7 @@ async def continue_or_finish(update: Update, context: CallbackContext):
return CONTINUE_OR_FINISH
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
@@ -306,7 +295,7 @@ async def add_premium_user_command(update: Update, context: ContextTypes.DEFAULT
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):
if not walladb.add_premium_user(telegram_user_id, '', until):
products = walladb.get_products_from_user(telegram_user_id)
for product in products:
@@ -333,9 +322,10 @@ async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
async def test_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):
until = helpers.get_date_ahead(7)
walladb.add_test_user(telegram_user_id, until)
walladb.add_test_user(telegram_user_id, telegram_user_name, until)
message = f"Periodo de prueba activado hasta el {until}."
else:
message = "Ya has utilizado el periodo de prueba."
@@ -345,7 +335,7 @@ async def test_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
message = "Ya eres premium. No puedes volver al periodo de prueba."
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def add_to_db_and_send(update, context):
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)
p = threading.Thread(target=Worker.run, args=(walladb.get_product(context.user_data), ))
@@ -355,6 +345,12 @@ async def add_to_db_and_send(update, context):
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.")
def main()->None:
walladb.setup_db()
products = walladb.get_all_products()
@@ -366,7 +362,7 @@ def main()->None:
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token(constants.TELEGRAM_TOKEN).build()
application = Application.builder().get_updates_http_version('1.1').http_version('1.1').token(constants.TELEGRAM_TOKEN).build()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("help", help_command))
@@ -377,6 +373,7 @@ def main()->None:
application.add_handler(CommandHandler("test", test_command))
conv_handler = ConversationHandler(
conversation_timeout=60,
entry_points=[CommandHandler("menu", main_menu)],
states={
ACTION: [CallbackQueryHandler(menu_click_handler)],
@@ -388,20 +385,16 @@ def main()->None:
CONTINUE_OR_FINISH: [CallbackQueryHandler(continue_or_finish),
MessageHandler(filters.TEXT, continue_or_finish)],
REMOVE_PRODUCT: [MessageHandler(filters.TEXT, remove_product)],
ConversationHandler.TIMEOUT: [CallbackQueryHandler(conv_timeout),
MessageHandler(filters.TEXT, conv_timeout)],
ConversationHandler.END: [MessageHandler(filters.TEXT, conv_finish)],
},
fallbacks=[CommandHandler("cancel", cancel)],
)
# Add ConversationHandler to application that will be used for handling updates
application.add_handler(conv_handler)
#application.bot.set_chat_menu_button('Menú')
application.add_error_handler(error)
# on non command i.e message - echo the message on Telegram
#application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# Run the bot until the user presses Ctrl-C
application.run_polling()
if __name__ == "__main__":

View File

@@ -17,8 +17,10 @@ class Worker:
def is_valid_request(self, product):
is_valid = False
if walladb.get_product(product):
if not walladb.is_user_expired(product['telegram_user_id']):
if walladb.is_user_valid(product['telegram_user_id']):
if walladb.is_user_premium(product['telegram_user_id']) or walladb.is_user_testing(product['telegram_user_id']):
if walladb.is_user_premium(product['telegram_user_id']) or \
walladb.is_user_testing(product['telegram_user_id']):
is_valid = True
return is_valid