import logging import sqlite3 import os import time import re import gspread from oauth2client.service_account import ServiceAccountCredentials from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Application, CommandHandler, MessageHandler, filters, ConversationHandler, CallbackContext, CallbackQueryHandler from telegram.constants import ParseMode PRODUCT_NAME, PRODUCT_DESCRIPTION, PRICE_MEMBER, PRICE, PRODUCT_IMAGE, LIMIT, LIMIT_PER_USER, UNLIMITED, LIMITED = range(9) # 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) # Cargar los identificadores de administradores desde la variable de entorno admin_ids = [int(admin_id) for admin_id in os.environ.get("ADMIN_IDS", "").split(",")] general_group_chat_id = os.environ.get("GENERAL_GROUP_CHAT_ID") vip_group_chat_id = os.environ.get("VIP_GROUP_CHAT_ID") bot_token = os.environ.get("TELEGRAM_TOKEN") spreadsheet_id = os.environ.get("SPREADSHEET_ID") # Configura la base de datos SQLite conn = sqlite3.connect('/app/data/conjuntas.db') cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS conjuntas ( id INTEGER PRIMARY KEY AUTOINCREMENT, message_id_general INTEGER, message_id_vip INTEGER, product_name TEXT, product_description TEXT, limite INTEGER, limit_per_user INTEGER, price INTEGER, price_member INTEGER, closed INTEGER, photo_id TEXT, message_date INTEGER )''') cursor.execute('''CREATE TABLE IF NOT EXISTS conjunta_users ( id INTEGER PRIMARY KEY AUTOINCREMENT, conjunta_id INTEGER, user_id INTEGER, user_name TEXT, quantity INTEGER, FOREIGN KEY(conjunta_id) REFERENCES conjuntas(id) )''') conn.commit() # Configuramos API de Google Sheets json_keyfile = '/app/data/creds.json' scopes = [ 'https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive' ] creds = ServiceAccountCredentials.from_json_keyfile_name(json_keyfile, scopes) client = gspread.authorize(creds) 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_general, message_id_vip, 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): if not context.user_data['edit']: product_name = update.message.text context.user_data['product_name'] = product_name 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): if not context.user_data['edit']: product_description = update.message.text context.user_data['product_description'] = product_description 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): 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 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): 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 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): 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)") 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): 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 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: 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 try: limit = int(limit) if limit <= 0: raise ValueError context.user_data['limit_per_user'] = limit await update.message.reply_text(f"Esta conjunta tiene un límite de {limit} productos por usuario. Escribe OK.") return LIMITED except ValueError: await update.message.reply_text("Por favor, introduce un número válido como límite.") return LIMIT_PER_USER # Función para enviar mensaje al grupo vip y añadir conjunta a la base de datos async def add_and_send(update: Update, context: CallbackContext, message): product_name = context.user_data['product_name'] limit = context.user_data['limit'] limit_per_user = context.user_data['limit_per_user'] photo_id = context.user_data['photo_id'] product_description = context.user_data['product_description'] price = context.user_data['price'] price_member = context.user_data['price_member'] if not context.user_data['edit']: sent_message = await context.bot.send_photo(vip_group_chat_id, photo=photo_id, caption=message) cursor.execute("INSERT INTO conjuntas (message_id_general, message_id_vip, product_name, product_description, price, price_member, limite, limit_per_user, closed, photo_id, message_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (0, 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 # Creamos una nueva hoja en el documento worksheet = spreadsheet.add_worksheet(title=f"{product_name} - {conjunta_id}", rows=1, cols=3) worksheet.append_row(["Usuario", "Cantidad", "Socio"]) # Anclar el mensaje en el grupo await context.bot.pin_chat_message(vip_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}") await update_conjunta(update, context, conjunta_id) # Función para gestionar la conjunta (opción ilimitada) async def unlimited(update: Update, context: CallbackContext): product_name = context.user_data['product_name'] # Enviar un mensaje al grupo con la información de la conjunta message = f"Nueva conjunta para '{product_name}' sin límite de cantidad. Puedes unirte respondiendo a este mensaje con la cantidad que quieras, en números." await add_and_send(update, context, message) 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 # Función para gestionar la conjunta (opción limitada) async def limited(update: Update, context: CallbackContext): product_name = context.user_data['product_name'] limit = context.user_data['limit'] limit_per_user = context.user_data['limit_per_user'] # Enviar un mensaje al grupo con la información de la conjunta message = f"Nueva conjunta para '{product_name}' con un límite de {limit} productos y {limit_per_user} por usuario. Puedes unirte respondiendo a este mensaje con la cantidad que quieras, en números." await add_and_send(update, context, message) 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 # Función para saber cuántas unidades quedan de una conjunta def quantity_left(conjunta_id): cursor.execute("SELECT limite FROM conjuntas WHERE id = ?", (conjunta_id,)) limit = cursor.fetchone()[0] if limit: cursor.execute("SELECT quantity FROM conjunta_users WHERE conjunta_id = ?", (conjunta_id,)) total_quantity = 0 for quantity in cursor.fetchall(): total_quantity += quantity[0] return limit - total_quantity else: return 1000 # Función para actualizar la cantidad el anclado async def update_conjunta(update: Update, context: CallbackContext, conjunta_id): cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,)) conjunta = cursor.fetchone() if conjunta: conjunta_id, message_id_general, message_id_vip, 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: message += f"#️⃣ Límite de {limit} productos.\n" message += f"#️⃣ Límite de {limit_per_user} por usuario.\n" left = quantity_left(conjunta_id) if left == 0: message += f"\n❌ Ya no quedan productos disponibles.\n" 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"\n🗒️ {product_description}\n" message += f"\n💰 Precio para socios: {price_member}€\n" message += f"💰 Precio para NO socios: {price}€\n" message += f"\nPuedes unirte respondiendo a este mensaje con me apunto {{cantidad}}, en números.\n" message += f"Para borrarte responde a este mensaje y di me borro.\n" cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id=?", (conjunta_id,)) users = cursor.fetchall() #if users: # message += f"\n🧍 Lista de apuntados:\n\n" # for user in users: # id, conjunta_id, user_id, user_name, quantity = user # quantity_string = "unidades" if quantity > 1 else "unidad" # message += f"@{user_name} - {quantity} {quantity_string}\n" if message_id_vip != 0: if (int(time.time()) - int(message_date)) < (24 * 3600 * 2): # borramos el mensaje si tiene más de 1 día logging.info(f"Updating message for {product_name} - {conjunta_id}") try: await context.bot.edit_message_caption(chat_id=vip_group_chat_id, message_id=message_id_vip, caption=message, parse_mode=ParseMode.HTML) except Exception as e: logging.error(f"Error editing message: {e}") else: logging.info(f"Original message for {product_name} - {conjunta_id} in vip chat is old, unpinning and sending new message") try: await context.bot.unpin_chat_message(chat_id=vip_group_chat_id, message_id=message_id_vip) except Exception as e: logging.error(f"Error unpinning message: {e}") sent_message = await context.bot.send_photo(chat_id=vip_group_chat_id, photo=photo_id, caption=message, parse_mode=ParseMode.HTML) await context.bot.pin_chat_message(chat_id=vip_group_chat_id, message_id=sent_message.message_id, disable_notification=True) cursor.execute("UPDATE conjuntas SET message_id_vip = ?, message_date = ? WHERE id = ?", (sent_message.message_id, int(time.time()), conjunta_id)) conn.commit() logging.info("New message sent and pinned to vip chat") else: # si por algún motivo no existe el mensaje en el grupo vip, lo enviamos logging.info("Message didn't exist in VIP group, sending it right now") sent_message = await context.bot.send_photo(vip_group_chat_id, photo=photo_id, caption='') # Anclar el mensaje en el grupo await context.bot.pin_chat_message(vip_group_chat_id, sent_message.message_id) cursor.execute("UPDATE conjuntas SET message_id_general = ? WHERE id=?", (sent_message.message_id, conjunta_id)) conn.commit() await update_conjunta(update, context, conjunta_id) if message_id_general != 0: # si existe un mensaje en el chat general, comprobamos y actualizamos if (int(time.time()) - int(message_date)) < (24 * 3600 * 2): # borramos el mensaje si tiene más de 1 día logging.info(f"Updating message for {product_name} - {conjunta_id}") try: await context.bot.edit_message_caption(chat_id=general_group_chat_id, message_id=message_id_general, caption=message, parse_mode=ParseMode.HTML) except Exception as e: logging.error(f"Error editing message: {e}") else: logging.info(f"Original message for {product_name} - {conjunta_id} in general chat is old, unpinning and sending new message") try: await context.bot.unpin_chat_message(chat_id=general_group_chat_id, message_id=message_id_general) except Exception as e: logging.error(f"Error unpinning message: {e}") sent_message = await context.bot.send_photo(chat_id=general_group_chat_id, photo=photo_id, caption=message, parse_mode=ParseMode.HTML) await context.bot.pin_chat_message(chat_id=general_group_chat_id, message_id=sent_message.message_id, disable_notification=True) cursor.execute("UPDATE conjuntas SET message_id_general = ?, message_date = ? WHERE id = ?", (sent_message.message_id, int(time.time()), conjunta_id)) conn.commit() logging.info("New message sent and pinned to general chat") # Función para unirse a una conjunta async def handle_conjunta(update: Update, context: CallbackContext): try: user_id = update.effective_user.id user_name = update.effective_user.username message = update.message.text group_id = update.message.chat_id reply_message_id = update.message.reply_to_message.message_id logging.info(f"{user_name} replied to message {reply_message_id} in group {group_id}") if group_id == int(general_group_chat_id): logging.info(f"Retrieving data for general group") cursor.execute("SELECT * FROM conjuntas WHERE message_id_general=?", (reply_message_id,)) elif group_id == int(vip_group_chat_id): logging.info(f"Retrieving data for vip group") cursor.execute("SELECT * FROM conjuntas WHERE message_id_vip=?", (reply_message_id,)) conjunta = cursor.fetchone() if conjunta: conjunta_id, message_id_general, message_id_vip, 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' # Si el usuario se borra... if re.findall(regex_borrar, message, re.IGNORECASE): if closed == 0: cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id=? AND user_id=?", (conjunta_id, user_id)) user_conjunta = cursor.fetchone() if user_conjunta: cursor.execute("DELETE FROM conjunta_users WHERE id = ?", (user_conjunta[0],)) conn.commit() worksheet = spreadsheet.worksheet(f"{product_name} - {conjunta_id}") found_cell = worksheet.find(user_name) if found_cell: worksheet.delete_rows(found_cell.row) await update.message.reply_text("¡Desapuntado de la conjunta!") await send_message_to_admins(update, context, f"@{user_name} se ha desapuntado de la conjunta {product_name} - {conjunta_id}") try: await update_conjunta(update, context, conjunta_id) except Exception as e: logging.error(f"Error updating conjunta: {e}") else: await update.message.reply_text("La conjunta ya está cerrada y no puedes borrarte.") # Si el usuario se apunta... if re.findall(regex_apuntar, message, re.IGNORECASE): if closed == 0: cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id=? AND user_id=?", (conjunta_id, user_id)) user_conjunta = cursor.fetchone() if user_conjunta: await update.message.reply_text("Ya te has unido a esta conjunta.") else: regex_numeros = r'\d+' search_quantity = re.search(regex_numeros, message) if search_quantity: quantity = search_quantity.group() else: quantity = 1 try: quantity = int(quantity) if quantity <= 0: raise ValueError if limit is not None and quantity > limit_per_user or limit is not None and quantity > quantity_left(conjunta_id): await update.message.reply_text("La cantidad deseada excede el límite por usuario de la conjunta o no quedan suficientes.") else: if user_name: cursor.execute("INSERT INTO conjunta_users (conjunta_id, user_id, user_name, quantity) VALUES (?, ?, ?, ?)", (conjunta_id, user_id, user_name, quantity)) conn.commit() socios_worksheet = spreadsheet.worksheet("Socios") socio = socios_worksheet.find(user_name) worksheet = spreadsheet.worksheet(f"{product_name} - {conjunta_id}") worksheet.append_row([user_name, quantity, "SÍ" if socio else "NO"]) my_quantity_left = quantity_left(conjunta_id) quantity_string = "unidades" if my_quantity_left > 1 else "unidad" message = f"Te has unido a la conjunta para '{product_name}' con {quantity} {quantity_string}." if not limit: message += f"\n\n¡Todavía quedan unidades disponibles!" elif my_quantity_left > 0: message += f"\n\n¡Todavía quedan {my_quantity_left} {quantity_string} disponibles!" else: message += f"\n\n¡Ya no quedan unidades disponibles!" await update.message.reply_text(message) await send_message_to_admins(update, context, f"@{user_name} se ha apuntado a la conjunta {product_name} - {conjunta_id} con {quantity} unidades") try: await update_conjunta(update, context, conjunta_id) except Exception: pass else: await update.message.reply_text("Por favor, tienes que ponerte un nick en Telegram para poder participar.") except ValueError: await update.message.reply_text("Por favor, introduce una cantidad válida.") else: await update.message.reply_text("La conjunta ya está cerrada y no puedes unirte.") except Exception as e: logging.error(f"Error handling message: {e}") async def close_conjunta(update: Update, context: CallbackContext): user_id = update.effective_user.id query = update.callback_query await query.answer(text="Cerrando conjunta") await query.edit_message_reply_markup(None) conjunta_id = int(query.data.split("close ")[1]) cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,)) conjunta = cursor.fetchone() if conjunta: conjunta_id, message_id_general, message_id_vip, 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,)) conn.commit() message = f"La conjunta para {product_name} ha sido cerrada.\n" message += f"La lista de apuntados es la siguiente:\n\n" cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id = ?", (conjunta_id,)) for user in cursor.fetchall(): id, conjunta_id, user_id, user_name, quantity = user message += f"@{user_name},{quantity}\n" message += "" message += f"\n\n🗒️ {product_description}\n" message += f"\n💰 Precio para socios: {price_member}€\n" message += f"💰 Precio para NO socios: {price}€\n" await context.bot.send_message(chat_id=vip_group_chat_id, text=message, parse_mode=ParseMode.HTML) # Desanclamos el mensaje del grupo vip await context.bot.unpin_chat_message(vip_group_chat_id, message_id_vip) if message_id_general != 0: await context.bot.send_message(chat_id=general_group_chat_id, text=message, parse_mode=ParseMode.HTML) # Desanclamos el mensaje del grupo general await context.bot.unpin_chat_message(general_group_chat_id, message_id_general) await context.bot.send_message(chat_id=user_id, text=f"La conjunta para {product_name} ha sido cerrada correctamente.") else: 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=user_id, text="La conjunta no existe.") async def general_conjunta(update: Update, context: CallbackContext): user_id = update.effective_user.id query = update.callback_query await query.answer(text="Abriendo conjunta al chat general") await query.edit_message_reply_markup(None) conjunta_id = int(query.data.split("general ")[1]) cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,)) conjunta = cursor.fetchone() if conjunta: conjunta_id, message_id_general, message_id_vip, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id, message_date = conjunta sent_message = await context.bot.send_photo(general_group_chat_id, photo=photo_id, caption='') # Anclar el mensaje en el grupo await context.bot.pin_chat_message(general_group_chat_id, sent_message.message_id) cursor.execute("UPDATE conjuntas SET message_id_general = ? WHERE id=?", (sent_message.message_id, conjunta_id)) conn.commit() await update_conjunta(update, context, conjunta_id) # Función para obtener un resumen de las conjuntas activas async def admin_summary(update: Update, context: CallbackContext): user_id = update.effective_user.id if user_id in admin_ids: cursor.execute("SELECT * FROM conjuntas WHERE closed=0") conjuntas = cursor.fetchall() if conjuntas: summary_text = "Resumen de conjuntas activas:\n\n" for conjunta in conjuntas: conjunta_id, message_id_general, message_id_vip, 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,)) total_quantity = 0 if num_users: for quantity in cursor.fetchall(): total_quantity += quantity[0] summary_text += f"🛒 {product_name} ({conjunta_id})\n" summary_text += f"🧍 Usuarios apuntados: {num_users}\n" if limit: summary_text += f"#️⃣ Límite: {limit}\n" summary_text += f"#️⃣ Límite por usuario: {limit_per_user}\n" summary_text += f"🔢 Cantidad total de productos pedidos: {total_quantity}\n" url = spreadsheet.worksheet(f"{product_name} - {conjunta_id}").url summary_text += f"💻 URL en Google Sheets\n" summary_text += f"\n--------------------------------------\n\n" await update.message.reply_text(summary_text, parse_mode=ParseMode.HTML) context.user_data["conjuntas"] = conjuntas # Agregar botones para seleccionar una conjunta keyboard = [] conjuntas_line = [] count = 0 for conjunta in conjuntas: conjunta_id, message_id_general, message_id_vip, 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)}")) if message_id_general == 0: conjuntas_line.append(InlineKeyboardButton(f"🔓 {product_name} ({str(conjunta_id)})", callback_data=f"general {str(conjunta_id)}")) keyboard.append(conjuntas_line) conjuntas_line = [] reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text("Selecciona una conjunta para ver más detalles.", reply_markup=reply_markup) else: await update.message.reply_text("No hay conjuntas activas en este grupo.") else: await update.message.reply_text("Solo los administradores pueden acceder a esta función.") # Función para mostrar detalles de una conjunta seleccionada async def show_conjunta_details(update: Update, context: CallbackContext): query = update.callback_query await query.answer(text="Mostrando detalles") await query.edit_message_reply_markup(None) selected_conjunta_idx = int(query.data.split("info ")[1]) cursor.execute("SELECT * FROM conjuntas WHERE id = ?", (selected_conjunta_idx,)) conjunta = cursor.fetchone() conjunta_id, message_id_general, message_id_vip, 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" selected_conjunta_details += f"🗒️ {product_description}\n" if limit: selected_conjunta_details += f"#️⃣ Límite: {limit}\n" selected_conjunta_details += f"#️⃣ Límite por usuario: {limit_per_user}\n" selected_conjunta_details += f"💰 Precio para socios: {price_member}€ (envío incluido)\n" selected_conjunta_details += f"💰 Precio para NO socios: {price}€ (envío a parte)\n" cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id = ?", (conjunta_id,)) selected_conjunta_details += f"\n🧍 Usuarios apuntados:\n\n-----------------------\n" for user in cursor.fetchall(): id, conjunta_id, user_id, user_name, quantity = user selected_conjunta_details += f"@{user_name},{quantity}\n" selected_conjunta_details += "-----------------------" url = spreadsheet.worksheet(f"{product_name} - {conjunta_id}").url selected_conjunta_details += f"\n💻 URL en Google Sheets\n" await context.bot.send_photo(chat_id=update.effective_chat.id, photo=photo_id, caption=selected_conjunta_details, parse_mode=ParseMode.HTML) # Función para enviar un mensaje a todos los administradores async def send_message_to_admins(update: Update, context: CallbackContext, message): for admin_id in admin_ids: try: await context.bot.send_message(chat_id=admin_id, text=message, parse_mode=ParseMode.HTML) except Exception as e: logging.error(f"Error sending message to {admin_id}: {e}") async def help(update: Update, context: CallbackContext): user_id = update.effective_user.id # Verifica si el usuario es administrador if user_id in admin_ids: message = "Comandos disponibles:\n\n" message += "/admin_summary - Muestra las conjuntas activas, permite modificarlas y cerrarlas\n" message += "/start_conjunta - Inicia una nueva conjunta\n" await update.message.reply_text(text=message, parse_mode=ParseMode.HTML) 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), 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 | filters.TEXT, product_image)], LIMIT: [MessageHandler(filters.TEXT, limit)], LIMIT_PER_USER: [MessageHandler(filters.TEXT, limit_per_user)], UNLIMITED: [MessageHandler(filters.TEXT, unlimited)], LIMITED: [MessageHandler(filters.TEXT, limited)], }, fallbacks=[], ) application.add_handler(conv_handler) application.add_handler(CommandHandler('admin_summary', admin_summary)) application.add_handler(CallbackQueryHandler(show_conjunta_details, pattern="info \d")) application.add_handler(CallbackQueryHandler(close_conjunta, pattern="close \d")) application.add_handler(CallbackQueryHandler(general_conjunta, pattern="general \d")) application.add_handler(MessageHandler(filters.REPLY, handle_conjunta)) application.add_handler(CommandHandler('help', help)) application.run_polling() conn.close() if __name__ == '__main__': main()