From cb1950ee054b9c3b5362117fd11cb4f483a7f36e Mon Sep 17 00:00:00 2001 From: Joan Date: Sun, 12 Nov 2023 19:25:04 +0100 Subject: [PATCH] Added editing and re-creation of old messages --- conjuntasbot/conjuntasbot.py | 295 ++++++++++++++++++++++++++--------- 1 file changed, 220 insertions(+), 75 deletions(-) diff --git a/conjuntasbot/conjuntasbot.py b/conjuntasbot/conjuntasbot.py index 9507f0a..c73a062 100644 --- a/conjuntasbot/conjuntasbot.py +++ b/conjuntasbot/conjuntasbot.py @@ -41,7 +41,8 @@ cursor.execute('''CREATE TABLE IF NOT EXISTS conjuntas ( price INTEGER, price_member INTEGER, closed INTEGER, - photo_id TEXT + photo_id TEXT, + message_date INTEGER )''') cursor.execute('''CREATE TABLE IF NOT EXISTS conjunta_users ( @@ -67,102 +68,215 @@ spreadsheet = client.open_by_key(spreadsheet_id) # Función para iniciar una nueva conjunta async def start_conjunta(update: Update, context: CallbackContext): + context.user_data.clear() user_id = update.effective_user.id chat_id = update.message.chat_id # Verifica si el usuario es administrador if user_id in admin_ids: context.user_data['chat_id'] = chat_id + context.user_data['edit'] = False await update.message.reply_text("Empezando nueva conjunta, dime ¿qué título quieres ponerle?") return PRODUCT_NAME else: await update.message.reply_text("Solo los administradores pueden iniciar conjuntas.") return ConversationHandler.END +# Función para editar una conjunta +async def edit_conjunta(update: Update, context: CallbackContext): + context.user_data.clear() + user_id = update.effective_user.id + + # Verifica si el usuario es administrador + if user_id in admin_ids: + query = update.callback_query + await query.answer(text="Editando conjunta") + await query.edit_message_reply_markup(None) + + conjunta_id = int(query.data.split("edit ")[1]) + + cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,)) + conjunta = cursor.fetchone() + + if conjunta: + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta + + context.user_data['product_name'] = product_name + context.user_data['original_product_name'] = product_name + context.user_data['limit'] = limit + context.user_data['limit_per_user'] = limit_per_user + context.user_data['photo_id'] = photo_id + context.user_data['product_description'] = product_description + context.user_data['price'] = price + context.user_data['price_member'] = price_member + context.user_data['conjunta_id'] = conjunta_id + + context.user_data['edit'] = True + await context.bot.send_message(chat_id=user_id, text=f"Vas a editar la conjunta {product_name} - {conjunta_id}.\n\nSi quieres editar el título, escríbelo, si no, escribe un punto: .", parse_mode=ParseMode.HTML) + return PRODUCT_NAME + else: + await context.bot.send_message(chat_id=user_id, text="Solo los administradores pueden iniciar conjuntas.") + return ConversationHandler.END + # Función para manejar el nombre del producto async def product_name(update: Update, context: CallbackContext): - product_name = update.message.text + if not context.user_data['edit']: + product_name = update.message.text - context.user_data['product_name'] = product_name + context.user_data['product_name'] = product_name - await update.message.reply_text("Envía una descripción para esta conjunta.") + await update.message.reply_text("Envía una descripción para esta conjunta.") + else: + if update.message.text != '.': + product_name = update.message.text + context.user_data['product_name'] = product_name + + await update.message.reply_text(f"La descripción actual es:\n\n{context.user_data['product_description']}\n\nSi quieres editarla escribe la nueva descripción, si no, escribe un punto: .", parse_mode=ParseMode.HTML) return PRODUCT_DESCRIPTION # Función para manejar la descripción del producto async def product_description(update: Update, context: CallbackContext): - product_description = update.message.text + if not context.user_data['edit']: + product_description = update.message.text - context.user_data['product_description'] = product_description + context.user_data['product_description'] = product_description - await update.message.reply_text("Envía el precio para SOCIOS.") + await update.message.reply_text("Envía el precio para SOCIOS.") + else: + if update.message.text != '.': + product_description = update.message.text + + context.user_data['product_description'] = product_description + + await update.message.reply_text(f"El precio actual para socios es de {context.user_data['price_member']}€. Si quieres editarlo, envía el nuevo precio para socios, si no, escribe un punto: .", parse_mode=ParseMode.HTML) return PRICE_MEMBER # Función para manejar el precio para socios del producto async def product_price_member(update: Update, context: CallbackContext): - price_member = update.message.text + if not context.user_data['edit']: + price_member = update.message.text - try: - context.user_data['price_member'] = int(price_member) - except ValueError: - await update.message.reply_text("Envía un número, por favor.") - return PRICE_MEMBER + try: + context.user_data['price_member'] = int(price_member) + except ValueError: + await update.message.reply_text("Envía un número, por favor.") + return PRICE_MEMBER - await update.message.reply_text("Envía el precio para NO socios.") + await update.message.reply_text("Envía el precio para NO socios.") + else: + if update.message.text != '.': + price_member = update.message.text + + try: + context.user_data['price_member'] = int(price_member) + except ValueError: + await update.message.reply_text("Envía un número, por favor.") + return PRICE_MEMBER + await update.message.reply_text(f"El precio actual para NO socios es de {context.user_data['price']}€. Si quieres editarlo, envía el nuevo precio para NO socios, si no, escribe un punto: .", parse_mode=ParseMode.HTML) return PRICE # Función para manejar el precio para no socios del producto async def product_price(update: Update, context: CallbackContext): - price = update.message.text + if not context.user_data['edit']: + price = update.message.text - try: - context.user_data['price'] = int(price) - except ValueError: - await update.message.reply_text("Envía un número, por favor.") - return PRICE + try: + context.user_data['price'] = int(price) + except ValueError: + await update.message.reply_text("Envía un número, por favor.") + return PRICE - await update.message.reply_text("Envía una foto para esta conjunta.") + await update.message.reply_text("Envía una foto para esta conjunta.") + else: + if update.message.text != '.': + price = update.message.text + + try: + context.user_data['price'] = int(price) + except ValueError: + await update.message.reply_text("Envía un número, por favor.") + return PRICE + + await update.message.reply_text("Si quieres editar la foto para la conjunta, envíala ahora. Si no, escribe un punto: .", parse_mode=ParseMode.HTML) return PRODUCT_IMAGE # Función para manejar la foto del producto async def product_image(update: Update, context: CallbackContext): - photo_id = None - if update.message.photo: - # Si se adjunta una foto, obtenemos el ID de la primera foto - photo_id = update.message.photo[-1].file_id - context.user_data['photo_id'] = photo_id - else: - await update.message.reply_text("No has enviado una foto. Vuelve a enviarla.") - return PRODUCT_IMAGE + if not context.user_data['edit']: + photo_id = None + if update.message.photo: + # Si se adjunta una foto, obtenemos el ID de la primera foto + photo_id = update.message.photo[-1].file_id + context.user_data['photo_id'] = photo_id + else: + await update.message.reply_text("No has enviado una foto. Vuelve a enviarla.") + return PRODUCT_IMAGE - await update.message.reply_text("¿Cuál es el límite de productos para esta conjunta? (Envía /unlimited si no hay límite)") + await update.message.reply_text("¿Cuál es el límite de productos para esta conjunta? (Envía /unlimited si no hay límite)") + else: + if update.message.text != '.': + photo_id = None + if update.message.photo: + # Si se adjunta una foto, obtenemos el ID de la primera foto + photo_id = update.message.photo[-1].file_id + context.user_data['photo_id'] = photo_id + else: + await update.message.reply_text("No has enviado una foto. Vuelve a enviarla.") + return PRODUCT_IMAGE + + await update.message.reply_text(f"El límite actual para esta conjunta es de {context.user_data['limit']}, si quieres editar, envía el nuevo límite ahora (Envía /unlimited si no hay límite). Si no, envía un punto: .", parse_mode=ParseMode.HTML) return LIMIT # Función para manejar el límite de productos async def limit(update: Update, context: CallbackContext): - limit = update.message.text + if not context.user_data['edit']: + limit = update.message.text - if limit.lower() == "/unlimited": - await update.message.reply_text("Esta conjunta no tiene límite de productos. Escribe OK.") - context.user_data['limit'] = None - context.user_data['limit_per_user'] = None - return UNLIMITED + if limit.lower() == "/unlimited": + await update.message.reply_text("Esta conjunta no tiene límite de productos. Escribe OK.") + context.user_data['limit'] = None + context.user_data['limit_per_user'] = None + return UNLIMITED + else: + try: + limit = int(limit) + if limit <= 0: + raise ValueError + context.user_data['limit'] = limit + await update.message.reply_text(f"Esta conjunta tiene un límite de {limit} productos. ¿Cuál es el límite individual?") + return LIMIT_PER_USER + except ValueError: + await update.message.reply_text("Por favor, introduce un número válido como límite.") + return LIMIT else: - try: - limit = int(limit) - if limit <= 0: - raise ValueError - context.user_data['limit'] = limit - await update.message.reply_text(f"Esta conjunta tiene un límite de {limit} productos. ¿Cuál es el límite individual?") - return LIMIT_PER_USER - except ValueError: - await update.message.reply_text("Por favor, introduce un número válido como límite.") - return LIMIT - + if update.message.text != '.': + limit = update.message.text + + if limit.lower() == "/unlimited": + await update.message.reply_text("Esta conjunta no tiene límite de productos. Escribe OK.") + context.user_data['limit'] = None + context.user_data['limit_per_user'] = None + return UNLIMITED + else: + try: + limit = int(limit) + if limit <= 0: + raise ValueError + context.user_data['limit'] = limit + await update.message.reply_text(f"Esta conjunta tiene un límite de {limit} productos. ¿Cuál es el límite individual?") + return LIMIT_PER_USER + except ValueError: + await update.message.reply_text("Por favor, introduce un número válido como límite.") + return LIMIT + await update.message.reply_text("Límite no editado. Producto modificado.") + await add_and_send(update, context, '.') + return ConversationHandler.END + # Función para manejar el límite por usuario async def limit_per_user(update: Update, context: CallbackContext): limit = update.message.text @@ -188,21 +302,30 @@ async def add_and_send(update: Update, context: CallbackContext, message): price = context.user_data['price'] price_member = context.user_data['price_member'] - sent_message = await context.bot.send_photo(group_chat_id, photo=photo_id, caption=message) + if not context.user_data['edit']: + sent_message = await context.bot.send_photo(group_chat_id, photo=photo_id, caption=message) - cursor.execute("INSERT INTO conjuntas (message_id, product_name, product_description, price, price_member, limite, limit_per_user, closed, photo_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", - (sent_message.message_id, product_name, product_description, price, price_member, limit, limit_per_user, 0, photo_id)) - conn.commit() + cursor.execute("INSERT INTO conjuntas (message_id, product_name, product_description, price, price_member, limite, limit_per_user, closed, photo_id, message_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + (sent_message.message_id, product_name, product_description, price, price_member, limit, limit_per_user, 0, photo_id, int(time.time()))) + conn.commit() - conjunta_id = cursor.lastrowid + conjunta_id = cursor.lastrowid + + # Creamos una nueva hoja en el documento + worksheet = spreadsheet.add_worksheet(title=f"{product_name} - {conjunta_id}", rows=1, cols=3) + #worksheet.update(values = [2, 5, 3], range_name = "A1:C1") + worksheet.append_row(["Usuario", "Cantidad", "Socio"]) + + # Anclar el mensaje en el grupo + await context.bot.pin_chat_message(group_chat_id, sent_message.message_id) + else: + conjunta_id = context.user_data['conjunta_id'] + cursor.execute("UPDATE conjuntas SET product_name = ?, product_description = ?, price = ?, price_member = ?, limite = ?, limit_per_user = ?, photo_id = ? WHERE id=?", (product_name, product_description, price, price_member, limit, limit_per_user, photo_id, conjunta_id)) + conn.commit() + original_product_name = context.user_data['original_product_name'] + worksheet = spreadsheet.worksheet(f"{original_product_name} - {conjunta_id}") + worksheet.update_title(f"{product_name} - {conjunta_id}") - # Creamos una nueva hoja en el documento - worksheet = spreadsheet.add_worksheet(title=f"{product_name} - {conjunta_id}", rows=1, cols=3) - #worksheet.update(values = [2, 5, 3], range_name = "A1:C1") - worksheet.append_row(["Usuario", "Cantidad", "Socio"]) - - # Anclar el mensaje en el grupo - await context.bot.pin_chat_message(group_chat_id, sent_message.message_id) await update_conjunta(update, context, conjunta_id) # Función para gestionar la conjunta (opción ilimitada) @@ -214,7 +337,10 @@ async def unlimited(update: Update, context: CallbackContext): await add_and_send(update, context, message) - await update.message.reply_text(f"La conjunta para '{product_name}' ha sido creada.") + if not context.user_data['edit']: + await update.message.reply_text(f"La conjunta para '{product_name}' ha sido creada.") + else: + await update.message.reply_text(f"La conjunta para '{product_name}' ha sido editada.") return ConversationHandler.END @@ -229,7 +355,10 @@ async def limited(update: Update, context: CallbackContext): await add_and_send(update, context, message) - await update.message.reply_text(f"La conjunta para '{product_name}' ha sido creada con un límite de {limit} productos.") + if not context.user_data['edit']: + await update.message.reply_text(f"La conjunta para '{product_name}' ha sido creada con un límite de {limit} productos.") + else: + await update.message.reply_text(f"La conjunta para '{product_name}' ha sido editada.") return ConversationHandler.END @@ -251,7 +380,7 @@ async def update_conjunta(update: Update, context: CallbackContext, conjunta_id) conjunta = cursor.fetchone() if conjunta: - conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta message = f"Conjunta para 🛒 {product_name}\n\n" if limit: @@ -263,7 +392,7 @@ async def update_conjunta(update: Update, context: CallbackContext, conjunta_id) else: message += f"\n✅ Todavía quedan {left} productos disponibles.\n" else: - message = f"✅ No hay límite de productos por usuario.\n" + message += f"✅ No hay límite de productos por usuario.\n" message += f"\n🗒️ {product_description}\n" message += f"\n💰 Precio para socios: {price_member}€ (envío incluido)\n" message += f"💰 Precio para NO socios: {price}€ (envío a parte)\n" @@ -277,10 +406,24 @@ async def update_conjunta(update: Update, context: CallbackContext, conjunta_id) message += f"\n🧍 Lista de apuntados:\n\n" for user in users: id, conjunta_id, user_id, user_name, quantity = user - message += f"@{user_name} - {quantity} unidades\n" - - await context.bot.edit_message_caption(chat_id=group_chat_id, message_id=message_id, caption=message, parse_mode=ParseMode.HTML) + quantity_string = "unidades" if quantity > 1 else "unidad" + message += f"@{user_name} - {quantity} {quantity_string}\n" + if (int(time.time()) - int(message_date)) < (24 * 3600 * 2): # borramos el mensaje si tiene más de 1 día + try: + await context.bot.edit_message_caption(chat_id=group_chat_id, message_id=message_id, caption=message, parse_mode=ParseMode.HTML) + except Exception as e: + logging.error(f"Error editing message: {e}") + else: + try: + await context.bot.delete_message(chat_id=group_chat_id, message_id=message_id) + except Exception as e: + logging.error(f"Message for {product_name} - {conjunta_id} couldn't be deleted, unpinning anyways") + await context.bot.unpin_chat_message(group_chat_id, message_id=message_id) + sent_message = await context.bot.send_photo(group_chat_id, photo=photo_id, caption=message, parse_mode=ParseMode.HTML) + await context.bot.pin_chat_message(chat_id=group_chat_id, message_id=sent_message.message_id) + cursor.execute("UPDATE conjuntas SET message_id = ?, message_date = ? WHERE conjunta_id = ?", (sent_message.message_id, int(time.time()), conjunta_id)) + conn.commit() # Función para unirse a una conjunta async def handle_conjunta(update: Update, context: CallbackContext): @@ -294,7 +437,7 @@ async def handle_conjunta(update: Update, context: CallbackContext): conjunta = cursor.fetchone() if conjunta: - conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta regex_borrar = r'\b(?!apunto)(desapunto|borro|desapuntar|borrar)\b' regex_apuntar = r'\b(apunto|me uno)\b' @@ -385,7 +528,7 @@ async def close_conjunta(update: Update, context: CallbackContext): conjunta = cursor.fetchone() if conjunta: - conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta if user_id in admin_ids: cursor.execute("UPDATE conjuntas SET closed=1 WHERE id=?", (conjunta_id,)) @@ -410,9 +553,9 @@ async def close_conjunta(update: Update, context: CallbackContext): # Desanclamos el mensaje await context.bot.unpin_chat_message(group_chat_id, message_id) else: - await context.bot.send_message(chat_id=group_chat_id, text="Solo el administrador puede cerrar la conjunta.") + await context.bot.send_message(chat_id=user_id, text="Solo el administrador puede cerrar la conjunta.") else: - await context.bot.send_message(chat_id=group_chat_id, text="La conjunta no existe.") + await context.bot.send_message(chat_id=user_id, text="La conjunta no existe.") # Función para obtener un resumen de las conjuntas activas async def admin_summary(update: Update, context: CallbackContext): @@ -425,7 +568,7 @@ async def admin_summary(update: Update, context: CallbackContext): if conjuntas: summary_text = "Resumen de conjuntas activas:\n\n" for conjunta in conjuntas: - conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta cursor.execute("SELECT COUNT(*) FROM conjunta_users WHERE conjunta_id=?", (conjunta_id,)) num_users = cursor.fetchone()[0] cursor.execute("SELECT quantity FROM conjunta_users WHERE conjunta_id=?", (conjunta_id,)) @@ -451,8 +594,9 @@ async def admin_summary(update: Update, context: CallbackContext): conjuntas_line = [] count = 0 for conjunta in conjuntas: - conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta conjuntas_line.append(InlineKeyboardButton(f"ℹ️ {product_name} ({str(conjunta_id)})", callback_data=f"info {str(conjunta_id)}")) + conjuntas_line.append(InlineKeyboardButton(f"📝 {product_name} ({str(conjunta_id)})", callback_data=f"edit {str(conjunta_id)}")) conjuntas_line.append(InlineKeyboardButton(f"❌ {product_name} ({str(conjunta_id)})", callback_data=f"close {str(conjunta_id)}")) keyboard.append(conjuntas_line) conjuntas_line = [] @@ -473,7 +617,7 @@ async def show_conjunta_details(update: Update, context: CallbackContext): cursor.execute("SELECT * FROM conjuntas WHERE id = ?", (selected_conjunta_idx,)) conjunta = cursor.fetchone() - conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta + conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta selected_conjunta_details = f"Detalles de la conjunta seleccionada:\n" selected_conjunta_details += f"🛒 {product_name}\n" @@ -509,13 +653,14 @@ def main()->None: application = Application.builder().get_updates_http_version('1.1').http_version('1.1').token(bot_token).build() conv_handler = ConversationHandler( - entry_points=[CommandHandler('start_conjunta', start_conjunta)], + entry_points=[CommandHandler('start_conjunta', start_conjunta), + CallbackQueryHandler(edit_conjunta, pattern="edit \d")], states={ PRODUCT_NAME: [MessageHandler(filters.TEXT, product_name)], PRODUCT_DESCRIPTION: [MessageHandler(filters.TEXT, product_description)], PRICE_MEMBER: [MessageHandler(filters.TEXT, product_price_member)], PRICE: [MessageHandler(filters.TEXT, product_price)], - PRODUCT_IMAGE: [MessageHandler(filters.PHOTO, product_image)], + PRODUCT_IMAGE: [MessageHandler(filters.PHOTO | filters.TEXT, product_image)], LIMIT: [MessageHandler(filters.TEXT, limit)], LIMIT_PER_USER: [MessageHandler(filters.TEXT, limit_per_user)], UNLIMITED: [MessageHandler(filters.TEXT, unlimited)],