Files
conjuntasbot/conjuntasbot/conjuntasbot.py
2024-03-14 15:47:39 +01:00

793 lines
42 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date 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 <b>{product_name} - {conjunta_id}</b>.\n\nSi quieres editar el título, escríbelo, si no, escribe un punto: <code>.</code>", 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: <code>.</code>", 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 <b>{context.user_data['price_member']}€</b>. Si quieres editarlo, envía el nuevo precio para socios, si no, escribe un punto: <code>.</code>", 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 <b>{context.user_data['price']}€</b>. Si quieres editarlo, envía el nuevo precio para NO socios, si no, escribe un punto: <code>.</code>", 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: <code>.</code>", 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: <code>.</code>", 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):
logging.info("Updating conjunta...")
cursor.execute("SELECT id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date 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'<a href="https://t.me/homelabsclub/556703">📖 Normas de conjuntas 📖</a>\n\n'
message += f"Conjunta para 🛒 <b>{product_name}</b>\n\n"
if limit:
message += f"#️⃣ Límite de <b>{limit}</b> productos.\n"
message += f"#️⃣ Límite de <b>{limit_per_user}</b> 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 <b>{left}</b> 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: <b>{price_member}€</b>\n"
message += f"💰 Precio para NO socios: <b>{price}€</b>\n"
message += f"\nPuedes unirte respondiendo a este mensaje con <code>me apunto {{cantidad}}</code>, en números.\n"
message += f"Para borrarte responde a este mensaje y di <code>me borro</code>.\n\n"
message += f'<a href="https://t.me/homelabsclub/556703">📖 Normas de conjuntas 📖</a>\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 int(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} in vip group")
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_vip = ?, message_date = ? WHERE id=?", (sent_message.message_id, int(time.time()), conjunta_id))
conn.commit()
await update_conjunta(update, context, conjunta_id)
if int(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} in general group")
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):
cursor.execute("SELECT id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date FROM conjuntas WHERE message_id_general=?", (reply_message_id,))
elif group_id == int(vip_group_chat_id):
cursor.execute("SELECT id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date FROM conjuntas WHERE message_id_vip=?", (reply_message_id,))
conjunta = cursor.fetchone()
if conjunta:
logging.info("Handling message...")
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, case_sensitive=False)
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, case_sensitive=False)
worksheet = spreadsheet.worksheet(f"{product_name} - {conjunta_id}")
worksheet.append_row([user_name, quantity, "" 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):
message_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 id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date 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 message_user_id in admin_ids:
cursor.execute("UPDATE conjuntas SET closed=1 WHERE id=?", (conjunta_id,))
conn.commit()
message = f"La conjunta para <b>{product_name}</b> ha sido cerrada.\n"
message_public = f"La conjunta para <b>{product_name}</b> ha sido cerrada.\n"
message += f"La lista de apuntados es la siguiente:\n\n<code>"
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 += "</code>"
message += f"\n\n🗒️ <b>{product_description}</b>\n"
message += f"\n💰 Precio para socios: <b>{price_member}€</b>\n"
message += f"💰 Precio para NO socios: <b>{price}€</b>\n"
await context.bot.send_message(chat_id=vip_group_chat_id, text=message_public, 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_public, 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=message_user_id, text=f"La conjunta para {product_name} ha sido cerrada correctamente.")
send_message_to_admins(message)
else:
await context.bot.send_message(chat_id=message_user_id, text="Solo el administrador puede cerrar la conjunta.")
else:
await context.bot.send_message(chat_id=message_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 id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date 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 id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date 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"🛒 <b>{product_name} ({conjunta_id})</b>\n"
summary_text += f"🧍 Usuarios apuntados: <b>{num_users}</b>\n"
if limit:
summary_text += f"#️⃣ Límite: <b>{limit}</b>\n"
summary_text += f"#️⃣ Límite por usuario: <b>{limit_per_user}</b>\n"
summary_text += f"🔢 Cantidad total de productos pedidos: <b>{total_quantity}</b>\n"
url = spreadsheet.worksheet(f"{product_name} - {conjunta_id}").url
summary_text += f"<a href='{url}'>💻 URL en Google Sheets</a>\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 id, message_id_general, message_id_vip, product_name, product_description, limite, limit_per_user, price, price_member, closed, photo_id, message_date 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"🛒 <b>{product_name}</b>\n"
selected_conjunta_details += f"🗒️ <b>{product_description}</b>\n"
if limit:
selected_conjunta_details += f"#️⃣ Límite: <b>{limit}</b>\n"
selected_conjunta_details += f"#️⃣ Límite por usuario: <b>{limit_per_user}</b>\n"
selected_conjunta_details += f"💰 Precio para socios: <b>{price_member}€</b> (envío incluido)\n"
selected_conjunta_details += f"💰 Precio para NO socios: <b>{price}€</b> (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<a href='{url}'>💻 URL en Google Sheets</a>\n"
await context.bot.send_photo(chat_id=update.effective_chat.id, photo=photo_id, caption=product_name, parse_mode=ParseMode.HTML)
await context.bot.send_message(chat_id=update.effective_chat.id, text=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()