Initial commit

This commit is contained in:
Joan
2026-02-22 12:56:42 +01:00
commit 50eb4f7eca
11 changed files with 3455 additions and 0 deletions

188
app/keyboards.py Normal file
View File

@@ -0,0 +1,188 @@
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from database import get_participants, get_active_raffles_in_channel, get_active_raffles, get_raffle_name
from config import *
def generate_raffle_selection_keyboard(channel_id):
"""Generates keyboard for users to select an active raffle in a channel."""
raffles = get_active_raffles_in_channel(channel_id)
keyboard = [[InlineKeyboardButton(r["name"], callback_data=f"join:{r['id']}")] for r in raffles]
return InlineKeyboardMarkup(keyboard)
def generate_numbers_keyboard(raffle_id, user_id, page=0):
"""Generates the 10x10 number selection keyboard with status icons and paging."""
participants = get_participants(raffle_id)
taken_numbers = {}
user_reserved = set()
user_taken = set()
# Process participants to determine number statuses
if participants:
for participant in participants:
participant_id = participant['user_id']
numbers = participant['numbers']
step = participant['step']
if numbers: # Ensure numbers is not None or empty
try:
numbers_set = {int(n) for n in numbers.split(',') if n.isdigit()} # Safer conversion
if participant_id == user_id:
if step == "waiting_for_payment":
user_reserved.update(numbers_set)
elif step == "completed":
user_taken.update(numbers_set)
for num in numbers_set:
# Store only if not already taken by the current user (avoids overwriting user status)
if num not in user_taken and num not in user_reserved:
taken_numbers[num] = participant_id # Track who took it generally
except ValueError:
logging.warning(f"Invalid number format in participant data for raffle {raffle_id}: {numbers}")
continue # Skip this participant's numbers if format is wrong
# Build the keyboard grid
keyboard = []
rows = 10
cols = 5
start = page * rows * cols
end = start + rows * cols
for i in range(rows):
row_buttons = []
for j in range(cols):
num = start + i * cols + j
if num >= 100: # Ensure we don't go beyond 99
break
num_str = f"{num:02}"
# Determine icon based on status
if num in user_taken:
icon = "🟢" # Taken (Paid) by this user
elif num in user_reserved:
icon = "🔒" # Reserved (Not paid) by this user
elif num in taken_numbers: # Check general taken status *after* specific user status
icon = "" # Taken by someone else
else:
icon = "☑️" # Free
row_buttons.append(InlineKeyboardButton(f"{icon} {num_str}", callback_data=f"number:{raffle_id}:{num}"))
if row_buttons: # Only add row if it contains buttons
keyboard.append(row_buttons)
# Add Paging Buttons
paging_buttons = []
if page > 0:
paging_buttons.append(InlineKeyboardButton("⬅️ Anterior", callback_data=f"number:{raffle_id}:prev"))
if end < 100: # Only show next if there are more numbers
paging_buttons.append(InlineKeyboardButton("Siguiente ➡️", callback_data=f"number:{raffle_id}:next"))
if paging_buttons:
keyboard.append(paging_buttons)
action_buttons_row = [
InlineKeyboardButton("✨ Número Aleatorio ✨", callback_data=f"random_num:{raffle_id}")
]
keyboard.append(action_buttons_row)
# Add Confirm/Cancel Buttons
confirm_cancel_row = [
InlineKeyboardButton("👍 Confirmar Selección", callback_data=f"confirm:{raffle_id}"),
InlineKeyboardButton("❌ Cancelar Selección", callback_data=f"cancel:{raffle_id}")
]
keyboard.append(confirm_cancel_row)
return InlineKeyboardMarkup(keyboard)
# --- Admin Menu Keyboards ---
def generate_admin_main_menu_keyboard():
keyboard = [
[InlineKeyboardButton(" Crear Nuevo Sorteo", callback_data=ADMIN_MENU_CREATE)],
[InlineKeyboardButton("📋 Listar/Gestionar Sorteos", callback_data=ADMIN_MENU_LIST)],
[InlineKeyboardButton("✏️ Editar Sorteo (Añadir Canales)", callback_data=ADMIN_EDIT_RAFFLE_SELECT)], # New option
]
return InlineKeyboardMarkup(keyboard)
def generate_channel_selection_keyboard_for_edit(existing_channel_ids, selected_new_channels_ids=None):
"""Generates channel selection keyboard for editing, excluding existing ones."""
if selected_new_channels_ids is None:
selected_new_channels_ids = set()
buttons = []
# CHANNELS is { 'alias': 'id' }
for alias, channel_id_str in CHANNELS.items():
if channel_id_str not in existing_channel_ids: # Only show channels NOT already in the raffle
text = f"{alias}" if channel_id_str in selected_new_channels_ids else f"⬜️ {alias}"
buttons.append([InlineKeyboardButton(text, callback_data=f"{SELECT_CHANNEL_PREFIX}{channel_id_str}")])
if selected_new_channels_ids:
buttons.append([InlineKeyboardButton("➡️ Continuar con precios", callback_data=f"{SELECT_CHANNEL_PREFIX}done")])
buttons.append([InlineKeyboardButton("❌ Cancelar Edición", callback_data=f"{SELECT_CHANNEL_PREFIX}cancel_edit")])
return InlineKeyboardMarkup(buttons)
def generate_admin_list_raffles_keyboard():
"""Generates keyboard listing active raffles with management buttons."""
active_raffles = get_active_raffles()
keyboard = []
if not active_raffles:
keyboard.append([InlineKeyboardButton("No hay sorteos activos.", callback_data=ADMIN_NO_OP)])
else:
for raffle in active_raffles:
raffle_id = raffle['id']
raffle_name = raffle['name']
# Row for each raffle: Name Button (View Details), Announce Button, End Button
keyboard.append([
InlineKeyboardButton(f" {raffle_name}", callback_data=f"{ADMIN_VIEW_RAFFLE_PREFIX}{raffle_id}"),
InlineKeyboardButton("📢 Anunciar", callback_data=f"{ADMIN_ANNOUNCE_RAFFLE_PREFIX}{raffle_id}"),
InlineKeyboardButton("🏁 Terminar", callback_data=f"{ADMIN_END_RAFFLE_PROMPT_PREFIX}{raffle_id}")
])
keyboard.append([InlineKeyboardButton("⬅️ Volver al Menú Principal", callback_data=ADMIN_MENU_BACK_MAIN)])
return InlineKeyboardMarkup(keyboard)
def generate_admin_raffle_details_keyboard(raffle_id):
"""Generates keyboard for the raffle detail view."""
keyboard = [
# Add relevant actions here if needed later, e.g., edit description?
[InlineKeyboardButton("📢 Anunciar de Nuevo", callback_data=f"{ADMIN_ANNOUNCE_RAFFLE_PREFIX}{raffle_id}")],
[InlineKeyboardButton("🏁 Terminar Sorteo", callback_data=f"{ADMIN_END_RAFFLE_PROMPT_PREFIX}{raffle_id}")],
[InlineKeyboardButton("⬅️ Volver a la Lista", callback_data=ADMIN_MENU_LIST)] # Back to list view
]
return InlineKeyboardMarkup(keyboard)
def generate_admin_cancel_end_keyboard():
"""Generates a simple cancel button during the end process."""
keyboard = [
[InlineKeyboardButton("❌ Cancelar Finalización", callback_data=ADMIN_CANCEL_END_PROCESS)]
]
return InlineKeyboardMarkup(keyboard)
# --- Keyboards for Raffle Creation Conversation ---
def generate_channel_selection_keyboard(selected_channels_ids=None):
"""Generates keyboard for admin to select target channels."""
if selected_channels_ids is None:
selected_channels_ids = set()
buttons = []
for alias, channel_id in CHANNELS.items():
# Mark selected channels
text = f"{alias}" if channel_id in selected_channels_ids else f"⬜️ {alias}"
buttons.append([InlineKeyboardButton(text, callback_data=f"{SELECT_CHANNEL_PREFIX}{channel_id}")])
# Add Done button only if at least one channel is selected
if selected_channels_ids:
buttons.append([InlineKeyboardButton("➡️ Continuar", callback_data=f"{SELECT_CHANNEL_PREFIX}done")])
buttons.append([InlineKeyboardButton("❌ Cancelar Creación", callback_data=f"{SELECT_CHANNEL_PREFIX}cancel")])
return InlineKeyboardMarkup(buttons)
def generate_confirmation_keyboard():
"""Generates Yes/No keyboard for final confirmation."""
keyboard = [
[
InlineKeyboardButton("✅ Sí, crear sorteo", callback_data=CONFIRM_CREATION_CALLBACK),
InlineKeyboardButton("❌ No, cancelar", callback_data=CANCEL_CREATION_CALLBACK),
]
]
return InlineKeyboardMarkup(keyboard)