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()