Files
homelabs-raffle/app/keyboards.py
2026-02-22 12:56:42 +01:00

188 lines
8.6 KiB
Python
Raw Permalink 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.
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)