First commit

This commit is contained in:
Joan
2023-10-30 19:50:11 +01:00
parent 2b958a51f8
commit f818609259
7 changed files with 502 additions and 89 deletions

View File

@@ -0,0 +1,457 @@
import logging
import sqlite3
import os
import time
import re
from telegram import Update, ReplyKeyboardMarkup
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ConversationHandler, CallbackContext
from telegram.constants import ParseMode
PRODUCT_NAME, PRODUCT_DESCRIPTION, PRODUCT_IMAGE, LIMIT, LIMIT_PER_USER, UNLIMITED, LIMITED = range(7)
# 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(",")]
group_chat_id = os.environ.get("GROUP_CHAT_ID")
bot_token = os.environ.get("TELEGRAM_TOKEN")
# Configura la base de datos SQLite
conn = sqlite3.connect('/app/data/db/conjuntas.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS conjuntas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER,
product_name TEXT,
product_description TEXT,
limite INTEGER,
limit_per_user INTEGER,
closed INTEGER,
photo_id TEXT
)''')
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()
async def list_conjuntas(update: Update, context: CallbackContext):
chat_id = update.message.chat_id
cursor.execute("SELECT * FROM conjuntas WHERE closed=0")
conjuntas = cursor.fetchall()
if conjuntas:
for conjunta in conjuntas:
time.sleep(0.5)
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, closed, photo_id = conjunta
caption = f"Conjunta para '{product_name}'\n"
cursor.execute("SELECT COUNT(*) FROM conjunta_users WHERE conjunta_id = ?", (conjunta[0],))
users = cursor.fetchone()
if users:
caption += f"Participantes: {users[0]}\n"
if limit is not None:
caption += f"Límite de productos: {limit} ({limit_per_user} por usuario)\n"
await context.bot.send_photo(chat_id, photo=photo_id, caption=caption)
else:
await update.message.reply_text("No hay conjuntas activas.")
# Función para iniciar una nueva conjunta
async def start_conjunta(update: Update, context: CallbackContext):
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
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 manejar el nombre del producto
async def product_name(update: Update, context: CallbackContext):
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.")
return PRODUCT_DESCRIPTION
# Función para manejar la descripción del producto
async def product_description(update: Update, context: CallbackContext):
product_description = update.message.text
context.user_data['product_description'] = product_description
await update.message.reply_text("Envía una foto para esta conjunta.")
return PRODUCT_IMAGE
# Función para manejar la foto del producto
async def product_image(update: Update, context: CallbackContext):
photo_id = None
if update.message.photo:
# Si se adjunta una foto, obtenemos el ID de la primera foto
photo_id = update.message.photo[-1].file_id
context.user_data['photo_id'] = photo_id
else:
await update.message.reply_text("No has enviado una foto. Vuelve a enviarla.")
return PRODUCT_IMAGE
await update.message.reply_text("¿Cuál es el límite de productos para esta conjunta? (Envía /unlimited si no hay límite)")
return LIMIT
# Función para manejar el límite de productos
async def limit(update: Update, context: CallbackContext):
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
# 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 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']
sent_message = await context.bot.send_photo(group_chat_id, photo=photo_id, caption=message)
cursor.execute("INSERT INTO conjuntas (message_id, product_name, product_description, limite, limit_per_user, closed, photo_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
(sent_message.message_id, product_name, product_description, limit, limit_per_user, 0, photo_id))
conn.commit()
conjunta_id = cursor.lastrowid
# Anclar el mensaje en el grupo
await context.bot.pin_chat_message(group_chat_id, sent_message.message_id)
await update_conjunta(update, context, conjunta_id)
# Función para gestionar la conjunta (opción ilimitada)
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)
await update.message.reply_text(f"La conjunta para '{product_name}' ha sido creada.")
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)
await update.message.reply_text(f"La conjunta para '{product_name}' ha sido creada con un límite de {limit} productos.")
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]
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
# 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, product_name, product_description, limit, limit_per_user, closed, photo_id = conjunta
if limit:
message = f"Conjunta para <b>{product_name}</b> con un límite de <b>{limit}</b> productos y <b>{limit_per_user}</b> por usuario.\n"
left = quantity_left(conjunta_id)
if left == 0:
message += f"\nYa no quedan productos disponibles.\n"
else:
message += f"\nTodavía quedan <b>{left}</b> productos disponibles.\n"
else:
message = f"Conjunta para <b>{product_name}</b> sin límite de cantidad.\n"
message += f"\n{product_description}\n\n"
message += f"Puedes unirte respondiendo a este mensaje con <code>me apunto {{cantidad}}</code>, en números.\nPara borrarte responde a este mensaje y di <code>me borro</code>."
await context.bot.edit_message_caption(chat_id=group_chat_id, message_id=message_id, caption=message, parse_mode=ParseMode.HTML)
# Función para unirse a una conjunta
async def handle_conjunta(update: Update, context: CallbackContext):
user_id = update.effective_user.id
user_name = update.effective_user.username
reply_message_id = update.message.reply_to_message.message_id
message = update.message.text
cursor.execute("SELECT * FROM conjuntas WHERE message_id=?", (reply_message_id,))
conjunta = cursor.fetchone()
if conjunta:
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, closed, photo_id = 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()
await update.message.reply_text("¡Desapuntado de la conjunta!")
try:
await update_conjunta(update, context, conjunta_id)
except Exception:
pass
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:
cursor.execute("INSERT INTO conjunta_users (conjunta_id, user_id, user_name, quantity) VALUES (?, ?, ?, ?)",
(conjunta_id, user_id, user_name, quantity))
conn.commit()
await update.message.reply_text(f"Te has unido a la conjunta para '{product_name}' con {quantity} unidades.")
try:
await update_conjunta(update, context, conjunta_id)
except Exception:
pass
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.")
# Función para consultar el estado de una conjunta
async def status_conjunta(update: Update, context: CallbackContext):
user_id = update.effective_user.id
chat_id = update.message.chat_id
conjunta_id = context.args[0]
cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,))
conjunta = cursor.fetchone()
if conjunta:
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, closed, photo_id = conjunta
cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id=?", (conjunta_id,))
users_data = cursor.fetchall()
status_text = f"Conjunta para '{product_name}':\n"
if users_data:
status_text += "Participantes y cantidades:\n"
for user_data in users_data:
user_id, user_quantity = user_data[2], user_data[3]
status_text += f"Usuario {user_id}: {user_quantity} unidades\n"
if limit is not None:
status_text += f"Límite de productos: {limit}\n"
status_text += f"Estado: {'Abierta' if closed == 0 else 'Cerrada'}"
await update.message.reply_text(status_text)
else:
await update.message.reply_text("La conjunta no existe.")
async def close_conjunta(update: Update, context: CallbackContext):
user_id = update.effective_user.id
conjunta_id = context.args[0]
cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,))
conjunta = cursor.fetchone()
if conjunta:
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, closed, photo_id = conjunta
if user_id in admin_ids:
cursor.execute("UPDATE conjuntas SET closed=1 WHERE id=?", (conjunta_id,))
conn.commit()
await update.message.reply_text(f"La conjunta para '{product_name}' ha sido cerrada.")
# Desanclamos el mensaje
#context.bot.unpin_chat_message(chat_id)
else:
await update.message.reply_text("Solo el administrador puede cerrar la conjunta.")
else:
await update.message.reply_text("La conjunta no existe.")
# 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, product_name, product_description, limit, limit_per_user, closed, photo_id = 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"ID: <b>{conjunta_id}</b>\n"
summary_text += f"Producto: <b>{product_name}</b>\n"
summary_text += f"Usuarios apuntados: <b>{num_users}</b>\n"
summary_text += f"Cantidad total de productos pedidos: <b>{total_quantity}</b>\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 = []
for conjunta in conjuntas:
keyboard.append([str(conjunta[0])])
reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)
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):
if "conjuntas" in context.user_data:
selected_conjunta_idx = int(update.message.text)
cursor.execute("SELECT * FROM conjuntas WHERE id = ?", (selected_conjunta_idx,))
conjunta = cursor.fetchone()
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, closed, photo_id = conjunta
selected_conjunta_details = f"Detalles de la conjunta seleccionada:\n"
selected_conjunta_details += f"Producto: <b>{product_name}</b>\n"
selected_conjunta_details += f"Descripción: <b>{product_description}</b>\n"
if limit:
selected_conjunta_details += f"Límite total: <b>{limit}</b>\n"
selected_conjunta_details += f"Límite por usuario: <b>{limit_per_user}</b>\n"
cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id = ?", (conjunta_id,))
selected_conjunta_details += f"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 += "-----------------------"
await update.message.reply_photo(photo=photo_id, caption=selected_conjunta_details, 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)],
states={
PRODUCT_NAME: [MessageHandler(filters.TEXT, product_name)],
PRODUCT_DESCRIPTION: [MessageHandler(filters.TEXT, product_description)],
PRODUCT_IMAGE: [MessageHandler(filters.PHOTO, 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('status', status_conjunta))
application.add_handler(CommandHandler('close', close_conjunta))
application.add_handler(CommandHandler('list', list_conjuntas))
application.add_handler(CommandHandler('admin_summary', admin_summary))
application.add_handler(MessageHandler(filters.TEXT & filters.Regex(r'^\d+$'), show_conjunta_details))
application.add_handler(MessageHandler(filters.REPLY, handle_conjunta))
application.run_polling()
if __name__ == '__main__':
main()