787 lines
40 KiB
Python
787 lines
40 KiB
Python
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 <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):
|
||
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 🛒 <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"
|
||
|
||
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 <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, 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"🛒 <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 * 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=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()
|