Did some modifications...
This commit is contained in:
42
app/app.py
42
app/app.py
@@ -144,6 +144,41 @@ async def check_winner_numbers(context: ContextTypes.DEFAULT_TYPE):
|
|||||||
logger.info(f"Winner number for {raffle['name']} is {winner_num}")
|
logger.info(f"Winner number for {raffle['name']} is {winner_num}")
|
||||||
await end_raffle_logic(context, raffle['id'], [int(winner_num)%100], ADMIN_IDS[0])
|
await end_raffle_logic(context, raffle['id'], [int(winner_num)%100], ADMIN_IDS[0])
|
||||||
|
|
||||||
|
async def announce_reminder_active_raffles(context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Job callback to announce active raffles in announce channels."""
|
||||||
|
# context is automatically provided by PTB's Job Queue
|
||||||
|
# No need to get app from context explicitly here for bot access,
|
||||||
|
# context.bot can be used directly.
|
||||||
|
logger.info(f"Running announce_reminder_active_raffles")
|
||||||
|
|
||||||
|
raffles = get_active_raffles()
|
||||||
|
|
||||||
|
if not raffles:
|
||||||
|
logger.info("No active raffles to announce.")
|
||||||
|
return
|
||||||
|
|
||||||
|
message_lines = ["🎉 <b>Sorteos Activos</b> 🎉\n"]
|
||||||
|
for raffle in raffles:
|
||||||
|
remaining = get_remaining_numbers_amount(raffle['id'])
|
||||||
|
message_lines.append(
|
||||||
|
f"• <b>{raffle['name']}</b>\n"
|
||||||
|
f" Donación mínima: {raffle['price']}€\n"
|
||||||
|
f" Participaciones restantes: {remaining}\n"
|
||||||
|
f" https://t.me/{REVERSE_CHANNELS.get(raffle['channel_id'])}/{get_main_message_id(raffle['id'])}\n"
|
||||||
|
)
|
||||||
|
message_text = "\n".join(message_lines)
|
||||||
|
|
||||||
|
for alias, channel_id in ANNOUNCE_CHANNELS.items():
|
||||||
|
try:
|
||||||
|
await context.bot.send_message(chat_id=channel_id, text=message_text, parse_mode=ParseMode.HTML)
|
||||||
|
logger.info(f"Announced active raffles in channel {alias} ({channel_id}).")
|
||||||
|
except Forbidden:
|
||||||
|
logger.warning(f"Cannot send announcement to channel {alias} ({channel_id}) (Forbidden).")
|
||||||
|
except BadRequest as e:
|
||||||
|
logger.error(f"BadRequest sending announcement to channel {alias} ({channel_id}): {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send announcement to channel {alias} ({channel_id}): {e}")
|
||||||
|
|
||||||
# --- Main Function ---
|
# --- Main Function ---
|
||||||
def main():
|
def main():
|
||||||
init_db()
|
init_db()
|
||||||
@@ -171,6 +206,13 @@ def main():
|
|||||||
)
|
)
|
||||||
logger.info("Scheduled winner check job every day at 21:45 Madrid time.")
|
logger.info("Scheduled winner check job every day at 21:45 Madrid time.")
|
||||||
|
|
||||||
|
job_queue.run_daily(
|
||||||
|
callback=announce_reminder_active_raffles,
|
||||||
|
time=dtime(hour=13, minute=0, tzinfo=madrid_tz),
|
||||||
|
name="announce_active_raffles_job"
|
||||||
|
)
|
||||||
|
logger.info("Scheduled announce active raffles job every day at 13:00 Madrid time.")
|
||||||
|
|
||||||
# --- Handlers (Remain the same) ---
|
# --- Handlers (Remain the same) ---
|
||||||
# 1. Raffle Creation Conversation Handler
|
# 1. Raffle Creation Conversation Handler
|
||||||
raffle_creation_conv = ConversationHandler(
|
raffle_creation_conv = ConversationHandler(
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ ADMIN_IDS = list(map(int, os.getenv("ADMIN_IDS", "1").split(','))) # Comma-sepa
|
|||||||
CHANNELS_IDS = list(os.getenv("CHANNEL_IDS", "1/test").split(',')) # Comma-separated channel IDs
|
CHANNELS_IDS = list(os.getenv("CHANNEL_IDS", "1/test").split(',')) # Comma-separated channel IDs
|
||||||
# Create a dictionary { 'channel_alias': 'channel_id' }
|
# Create a dictionary { 'channel_alias': 'channel_id' }
|
||||||
CHANNELS = {channel.split('/')[1]: channel.split('/')[0] for channel in CHANNELS_IDS}
|
CHANNELS = {channel.split('/')[1]: channel.split('/')[0] for channel in CHANNELS_IDS}
|
||||||
|
ANNOUNCE_CHANNEL_IDS = list(os.getenv("ANNOUNCE_CHANNEL_IDS", "").split(',')) # Comma-separated announce channel IDs
|
||||||
|
ANNOUNCE_CHANNELS = {channel.split('/')[1]: channel.split('/')[0] for channel in ANNOUNCE_CHANNEL_IDS if '/' in channel}
|
||||||
# Create a reverse dictionary { 'channel_id': 'channel_alias' } for display/lookup
|
# Create a reverse dictionary { 'channel_id': 'channel_alias' } for display/lookup
|
||||||
REVERSE_CHANNELS = {v: k for k, v in CHANNELS.items()}
|
REVERSE_CHANNELS = {v: k for k, v in CHANNELS.items()}
|
||||||
DATABASE_PATH = "/app/data/raffles.db"
|
DATABASE_PATH = "/app/data/raffles.db"
|
||||||
@@ -45,6 +47,7 @@ ADMIN_END_RAFFLE_PROMPT_PREFIX = "admin_end_prompt:" # + raffle_id
|
|||||||
ADMIN_CANCEL_END_PROCESS = "admin_cancel_end"
|
ADMIN_CANCEL_END_PROCESS = "admin_cancel_end"
|
||||||
ADMIN_VIEW_RAFFLE_PREFIX = "admin_view_raffle:" # + raffle_id (NEW)
|
ADMIN_VIEW_RAFFLE_PREFIX = "admin_view_raffle:" # + raffle_id (NEW)
|
||||||
ADMIN_ANNOUNCE_RAFFLE_PREFIX = "admin_announce_raffle:" # + raffle_id (NEW)
|
ADMIN_ANNOUNCE_RAFFLE_PREFIX = "admin_announce_raffle:" # + raffle_id (NEW)
|
||||||
|
ADMIN_UPDATE_IMAGE_PREFIX = "admin_update_image:" # + raffle_id (NEW)
|
||||||
ADMIN_NO_OP = "admin_no_op" # Placeholder for buttons that do nothing on click
|
ADMIN_NO_OP = "admin_no_op" # Placeholder for buttons that do nothing on click
|
||||||
# --- End Admin Menu ---
|
# --- End Admin Menu ---
|
||||||
|
|
||||||
@@ -53,4 +56,10 @@ DRAW_MAPPING = {
|
|||||||
'friday': 'VIE', # Fri
|
'friday': 'VIE', # Fri
|
||||||
'weekend': 'DOM' # Sat–Sun
|
'weekend': 'DOM' # Sat–Sun
|
||||||
}
|
}
|
||||||
JUEGOS_ONCE_URL = "https://www.juegosonce.es/resultados-ultimos-sorteos-once"
|
JUEGOS_ONCE_URL = "https://www.juegosonce.es/resultados-ultimos-sorteos-once"
|
||||||
|
|
||||||
|
# Homelabs API Configuration
|
||||||
|
HOMELABS_API_TOKEN = os.getenv("HOMELABS_API_TOKEN")
|
||||||
|
HOMELABS_API_URL = os.getenv("HOMELABS_API_URL", "http://user_membership_api:8000")
|
||||||
|
|
||||||
|
VIP_DISCOUNT_PER_NUMBER = float(os.getenv("VIP_DISCOUNT_PER_NUMBER", "1.0")) # Default 1 EUR discount per number for VIP members
|
||||||
@@ -131,8 +131,7 @@ def get_active_raffles():
|
|||||||
conn = connect_db()
|
conn = connect_db()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
try:
|
try:
|
||||||
# No price here, as it's per-channel
|
cur.execute("SELECT * FROM raffles WHERE active=1")
|
||||||
cur.execute("SELECT id, name, description, image_file_id FROM raffles WHERE active=1")
|
|
||||||
raffles = cur.fetchall()
|
raffles = cur.fetchall()
|
||||||
return raffles
|
return raffles
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
raffle = get_raffle(raffle_id) # Get raffle details
|
raffle = get_raffle(raffle_id) # Get raffle details
|
||||||
remaining_numbers_amount = get_remaining_numbers_amount(raffle_id)
|
remaining_numbers_amount = get_remaining_numbers_amount(raffle_id)
|
||||||
if raffle and raffle['active'] and remaining_numbers_amount > 0:
|
if raffle and raffle['active'] and remaining_numbers_amount > 0:
|
||||||
# Check what time is it, if it's between 20:55 and 22:00, users can't join
|
# Check what time is it, if it's between 21:15 and 22:00, users can't join
|
||||||
madrid_tz = pytz.timezone("Europe/Madrid")
|
madrid_tz = pytz.timezone("Europe/Madrid")
|
||||||
current_time = datetime.now(madrid_tz)
|
current_time = datetime.now(madrid_tz)
|
||||||
if current_time.time() >= dtime(20, 55) and current_time.time() <= dtime(22, 0):
|
if current_time.time() >= dtime(21, 15) and current_time.time() <= dtime(22, 0):
|
||||||
await update.message.reply_text("Lo siento, no puedes unirte a sorteos entre las 20:55 y las 22:00 (hora de España) para evitar conflictos con el sorteo en directo. Inténtalo de nuevo más tarde.")
|
await update.message.reply_text("Lo siento, no puedes unirte a sorteos entre las 21:15 y las 22:00 (hora de España) para evitar conflictos con el sorteo en directo. Inténtalo de nuevo más tarde.")
|
||||||
return
|
return
|
||||||
if len(get_reserved_numbers(user.id, raffle_id)) > 0:
|
if len(get_reserved_numbers(user.id, raffle_id)) > 0:
|
||||||
await update.message.reply_text("Ya tienes participaciones reservadas para este sorteo. Por favor, completa la donación o espera a que caduquen antes de unirte de nuevo.")
|
await update.message.reply_text("Ya tienes participaciones reservadas para este sorteo. Por favor, completa la donación o espera a que caduquen antes de unirte de nuevo.")
|
||||||
@@ -48,13 +48,23 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
logger.info(f"User {user.id} started bot with join link for raffle {raffle_id}")
|
logger.info(f"User {user.id} started bot with join link for raffle {raffle_id}")
|
||||||
context.user_data['joining_raffle_id'] = raffle_id
|
context.user_data['joining_raffle_id'] = raffle_id
|
||||||
keyboard = generate_numbers_keyboard(raffle_id, user.id)
|
keyboard = generate_numbers_keyboard(raffle_id, user.id)
|
||||||
await update.message.reply_text(
|
is_vip = is_vip_member_of_homelabs(user.id)
|
||||||
f"¡Hola! Vamos a unirnos al sorteo '{raffle['name']}'.\n\n"
|
if is_vip:
|
||||||
f"🌍 Envío internacional: {'Sí ✅' if raffle['international_shipping'] else 'No ❌'}\n"
|
await update.message.reply_text(
|
||||||
f"La donación mínima es de {raffle['price']}€.\n\n"
|
f"¡Hola usuario VIP! Vamos a unirnos al sorteo '{raffle['name']}'.\n\n"
|
||||||
"Por favor, selecciona tus números:",
|
f"🌍 Envío internacional: {'Sí ✅' if raffle['international_shipping'] else 'No ❌'}\n"
|
||||||
reply_markup=keyboard
|
f"La donación mínima es de {raffle['price'] - VIP_DISCOUNT_PER_NUMBER}€.\n\n"
|
||||||
)
|
"Por favor, selecciona tus números:",
|
||||||
|
reply_markup=keyboard
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await update.message.reply_text(
|
||||||
|
f"¡Hola! Vamos a unirnos al sorteo '{raffle['name']}'.\n\n"
|
||||||
|
f"🌍 Envío internacional: {'Sí ✅' if raffle['international_shipping'] else 'No ❌'}\n"
|
||||||
|
f"La donación mínima es de {raffle['price']}€.\n\n"
|
||||||
|
"Por favor, selecciona tus números:",
|
||||||
|
reply_markup=keyboard
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await update.message.reply_text("Este sorteo ya no está activo o no tiene números disponibles.")
|
await update.message.reply_text("Este sorteo ya no está activo o no tiene números disponibles.")
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
@@ -225,8 +235,24 @@ async def confirm_creation(update: Update, context: ContextTypes.DEFAULT_TYPE) -
|
|||||||
channel_id = user_data.get('channel')
|
channel_id = user_data.get('channel')
|
||||||
international_shipping = user_data.get('international_shipping', 0)
|
international_shipping = user_data.get('international_shipping', 0)
|
||||||
|
|
||||||
if not all([name, description, image_file_id, price, international_shipping]):
|
exists_international_shipping = False
|
||||||
await context.bot.send_message(query.from_user.id, "Faltan datos. Creación cancelada.")
|
if international_shipping != None:
|
||||||
|
exists_international_shipping = True
|
||||||
|
|
||||||
|
if not all([name, description, image_file_id, price, exists_international_shipping]):
|
||||||
|
missing_fields = []
|
||||||
|
if not name:
|
||||||
|
missing_fields.append("título")
|
||||||
|
if not description:
|
||||||
|
missing_fields.append("descripción")
|
||||||
|
if not image_file_id:
|
||||||
|
missing_fields.append("imagen")
|
||||||
|
if not price:
|
||||||
|
missing_fields.append("precio")
|
||||||
|
if not exists_international_shipping:
|
||||||
|
missing_fields.append("envío internacional")
|
||||||
|
|
||||||
|
await context.bot.send_message(query.from_user.id, f"Faltan datos: {', '.join(missing_fields)}. Creación cancelada.")
|
||||||
context.user_data.pop('new_raffle', None)
|
context.user_data.pop('new_raffle', None)
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
@@ -528,6 +554,12 @@ async def confirm_callback(update: Update, context: CallbackContext):
|
|||||||
return
|
return
|
||||||
|
|
||||||
user_name = participant['user_name']
|
user_name = participant['user_name']
|
||||||
|
|
||||||
|
is_vip = is_vip_member_of_homelabs(user_id)
|
||||||
|
if is_vip:
|
||||||
|
price_per_number -= VIP_DISCOUNT_PER_NUMBER
|
||||||
|
if price_per_number < 1:
|
||||||
|
price_per_number = 1 # Minimum price
|
||||||
total_price = len(reserved_numbers) * price_per_number
|
total_price = len(reserved_numbers) * price_per_number
|
||||||
|
|
||||||
paypal_link, invoice_id = create_paypal_order(get_paypal_access_token(), total_price, raffle_info['id'], reserved_numbers, user_name)
|
paypal_link, invoice_id = create_paypal_order(get_paypal_access_token(), total_price, raffle_info['id'], reserved_numbers, user_name)
|
||||||
@@ -680,16 +712,19 @@ async def end_raffle_logic(context: ContextTypes.DEFAULT_TYPE, raffle_id: int, w
|
|||||||
|
|
||||||
# Notify admin of success
|
# Notify admin of success
|
||||||
try:
|
try:
|
||||||
|
number_of_participations = get_total_participations(raffle_id)
|
||||||
|
price_per_number = raffle_details['price']
|
||||||
|
total_gross = 0
|
||||||
|
total_net = 0
|
||||||
|
total_fees = 0
|
||||||
|
invoice_ids = get_all_invoice_ids(raffle_id)
|
||||||
|
for inv_id in invoice_ids:
|
||||||
|
gross, net, fees = get_paypal_amounts_for_invoice(inv_id)
|
||||||
|
total_gross += gross
|
||||||
|
total_net += net
|
||||||
|
total_fees += fees
|
||||||
for admin_user_id in ADMIN_IDS:
|
for admin_user_id in ADMIN_IDS:
|
||||||
await context.bot.send_message(admin_user_id, f"Sorteo '{raffle_name}' terminado y ganadores anunciados.")
|
await context.bot.send_message(admin_user_id, f"Sorteo '{raffle_name}' terminado y ganadores anunciados.")
|
||||||
number_of_participations = get_total_participations(raffle_id)
|
|
||||||
price_per_number = raffle_details['price']
|
|
||||||
invoice_ids = get_all_invoice_ids(raffle_id)
|
|
||||||
for inv_id in invoice_ids:
|
|
||||||
gross, net, fees = get_paypal_amounts_for_invoice(inv_id)
|
|
||||||
total_gross += gross
|
|
||||||
total_net += net
|
|
||||||
total_fees += fees
|
|
||||||
await context.bot.send_message(
|
await context.bot.send_message(
|
||||||
admin_user_id,
|
admin_user_id,
|
||||||
f"Resumen del sorteo '{raffle_name}':\n"
|
f"Resumen del sorteo '{raffle_name}':\n"
|
||||||
@@ -788,6 +823,26 @@ async def admin_menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE
|
|||||||
logger.error(f"Invalid callback data for announce raffle: {data}")
|
logger.error(f"Invalid callback data for announce raffle: {data}")
|
||||||
await query.edit_message_text("Error: ID de sorteo inválido.", reply_markup=generate_admin_list_raffles_keyboard())
|
await query.edit_message_text("Error: ID de sorteo inválido.", reply_markup=generate_admin_list_raffles_keyboard())
|
||||||
|
|
||||||
|
elif data.startswith(ADMIN_UPDATE_IMAGE_PREFIX):
|
||||||
|
try:
|
||||||
|
raffle_id = int(data[len(ADMIN_UPDATE_IMAGE_PREFIX):])
|
||||||
|
logger.info(f"Admin {user_id} requested image update for raffle {raffle_id}")
|
||||||
|
await query.edit_message_text(f"🔄 Actualizando imagen del sorteo {get_raffle_name(raffle_id)}...", reply_markup=None)
|
||||||
|
|
||||||
|
# Use the extracted function to send/update the raffle image
|
||||||
|
if send_raffle_update_image(raffle_id, bot_token=context.bot.token):
|
||||||
|
await context.bot.send_message(user_id, f"✅ Imagen actualizada correctamente para el sorteo {get_raffle_name(raffle_id)}.")
|
||||||
|
else:
|
||||||
|
await context.bot.send_message(user_id, f"❌ Error al actualizar la imagen para el sorteo {get_raffle_name(raffle_id)}.")
|
||||||
|
|
||||||
|
# Go back to raffle details
|
||||||
|
details_text = format_raffle_details(raffle_id)
|
||||||
|
details_keyboard = generate_admin_raffle_details_keyboard(raffle_id)
|
||||||
|
await context.bot.send_message(user_id, details_text, reply_markup=details_keyboard, parse_mode=ParseMode.HTML)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logger.error(f"Invalid callback data for update image: {data}")
|
||||||
|
await query.edit_message_text("Error: ID de sorteo inválido.", reply_markup=generate_admin_list_raffles_keyboard())
|
||||||
|
|
||||||
elif data.startswith(ADMIN_END_RAFFLE_PROMPT_PREFIX):
|
elif data.startswith(ADMIN_END_RAFFLE_PROMPT_PREFIX):
|
||||||
try:
|
try:
|
||||||
raffle_id = int(data[len(ADMIN_END_RAFFLE_PROMPT_PREFIX):])
|
raffle_id = int(data[len(ADMIN_END_RAFFLE_PROMPT_PREFIX):])
|
||||||
@@ -902,7 +957,7 @@ async def _announce_raffle_in_channels(context: ContextTypes.DEFAULT_TYPE, raffl
|
|||||||
|
|
||||||
raffle_name = raffle['name']
|
raffle_name = raffle['name']
|
||||||
image_file_id = raffle['image_file_id']
|
image_file_id = raffle['image_file_id']
|
||||||
raffle_description = raffle['description'] # Get description for caption
|
raffle_description = raffle['description'][:350] # Get description for caption
|
||||||
price = raffle['price']
|
price = raffle['price']
|
||||||
channel_id_str = raffle['channel_id']
|
channel_id_str = raffle['channel_id']
|
||||||
|
|
||||||
|
|||||||
247
app/helpers.py
247
app/helpers.py
@@ -1,3 +1,4 @@
|
|||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
from database import * # Import all DB functions
|
from database import * # Import all DB functions
|
||||||
@@ -389,9 +390,9 @@ def format_last_participants_list(participants_list: list) -> str:
|
|||||||
if numbers_str:
|
if numbers_str:
|
||||||
num_list = numbers_str.split(',')
|
num_list = numbers_str.split(',')
|
||||||
if len(num_list) == 1:
|
if len(num_list) == 1:
|
||||||
line = f" - {user_name}, con la papeleta: {num_list[0]}"
|
line = f" - {user_name}, con la participación: {num_list[0]}"
|
||||||
else:
|
else:
|
||||||
line = f" - {user_name}, con las papeletas: {', '.join(num_list)}"
|
line = f" - {user_name}, con las participaciones: {', '.join(num_list)}"
|
||||||
formatted_lines.append(line)
|
formatted_lines.append(line)
|
||||||
|
|
||||||
return "\n".join(formatted_lines) # Add a trailing newline
|
return "\n".join(formatted_lines) # Add a trailing newline
|
||||||
@@ -482,3 +483,245 @@ def get_paypal_amounts_for_invoice(invoice_id):
|
|||||||
net_amount = float(breakdown.get("net_amount", {}).get("value", gross_amount))
|
net_amount = float(breakdown.get("net_amount", {}).get("value", gross_amount))
|
||||||
|
|
||||||
return gross_amount, net_amount, fee_amount
|
return gross_amount, net_amount, fee_amount
|
||||||
|
|
||||||
|
def is_vip_member_of_homelabs(user_id):
|
||||||
|
"""Checks if a Telegram user ID is a VIP member of HomeLabs via the Homelabs API."""
|
||||||
|
if not HOMELABS_API_TOKEN or not HOMELABS_API_URL:
|
||||||
|
logger.warning("Homelabs API configuration is missing.")
|
||||||
|
return False # If not configured, treat as non-member
|
||||||
|
|
||||||
|
url = f"{HOMELABS_API_URL}/users/{user_id}"
|
||||||
|
headers = {
|
||||||
|
"X-API-Key": HOMELABS_API_TOKEN,
|
||||||
|
"Accept": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers, timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get("is_lifetime") == True:
|
||||||
|
return True
|
||||||
|
if data.get("member_until") >= datetime.datetime.now(datetime.timezone.utc).date().isoformat():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
elif response.status_code == 404:
|
||||||
|
return False # User not found, hence not a member
|
||||||
|
else:
|
||||||
|
logger.error(f"Unexpected response from Homelabs API: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Error connecting to Homelabs API: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_raffle_update_image(raffle_id, current_user_name=None, numbers=None, bot_token=None):
|
||||||
|
"""
|
||||||
|
Sends or updates the raffle table image to the appropriate channel.
|
||||||
|
Can be used for payment confirmations or manual updates from admin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
raffle_id: The ID of the raffle
|
||||||
|
current_user_name: Name of the user who just joined (optional, for payment updates)
|
||||||
|
numbers: List of numbers the user selected (optional, for payment updates)
|
||||||
|
bot_token: Telegram bot token for API calls
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
import json
|
||||||
|
|
||||||
|
if not bot_token:
|
||||||
|
bot_token = BOT_TOKEN
|
||||||
|
|
||||||
|
TELEGRAM_API_URL = f"https://api.telegram.org/bot{bot_token}"
|
||||||
|
|
||||||
|
# Generate table image
|
||||||
|
if not generate_table_image(raffle_id):
|
||||||
|
logger.error(f"Failed to generate raffle table image for {raffle_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
image_path = f"/app/data/raffles/raffle_table_{raffle_id}.png"
|
||||||
|
|
||||||
|
# Get raffle details
|
||||||
|
raffle_details = get_raffle(raffle_id)
|
||||||
|
if not raffle_details:
|
||||||
|
logger.error(f"Could not fetch raffle details for ID {raffle_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
raffle_name = raffle_details['name']
|
||||||
|
raffle_description_for_announce = raffle_details['description'][:350]
|
||||||
|
channel_id_to_announce = raffle_details['channel_id']
|
||||||
|
original_price_per_number = raffle_details['price']
|
||||||
|
remaining_numbers_amount = get_remaining_numbers_amount(raffle_id)
|
||||||
|
|
||||||
|
# Determine keyboard and caption based on remaining numbers
|
||||||
|
keyboard = None
|
||||||
|
if remaining_numbers_amount == 0:
|
||||||
|
# Raffle is complete
|
||||||
|
main_announcement = f"🎯🏆🎯 <b>Sorteo '{raffle_name}' terminado</b> 🎯🏆🎯\n\n"
|
||||||
|
main_announcement += f"{raffle_description_for_announce}\n\n"
|
||||||
|
main_announcement += f"🌍 Envío internacional: {'Sí ✅' if raffle_details['international_shipping'] else 'No ❌'}\n"
|
||||||
|
main_announcement += f"💵 <b>Donación mínima:</b> {original_price_per_number}€\n"
|
||||||
|
main_announcement += f"📜 <b>Normas y condiciones:</b> {TYC_DOCUMENT_URL}"
|
||||||
|
|
||||||
|
# Update main message to remove participate button
|
||||||
|
main_message_id = get_main_message_id(raffle_id)
|
||||||
|
if main_message_id:
|
||||||
|
requests.post(f"{TELEGRAM_API_URL}/editMessageCaption", json={
|
||||||
|
"chat_id": channel_id_to_announce,
|
||||||
|
"message_id": main_message_id,
|
||||||
|
"caption": main_announcement,
|
||||||
|
"reply_markup": None,
|
||||||
|
"parse_mode": "HTML"
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Raffle still in progress
|
||||||
|
url = f"https://t.me/{BOT_NAME}?start=join_{raffle_id}"
|
||||||
|
keyboard = {
|
||||||
|
"inline_keyboard": [
|
||||||
|
[
|
||||||
|
{"text": "✅ ¡Participar Ahora! ✅", "url": url}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
main_announcement = f"🏆 Sorteo '{raffle_name}' en progreso 🏆\n\n"
|
||||||
|
main_announcement += f"{raffle_description_for_announce}\n\n"
|
||||||
|
main_announcement += f"🌍 Envío internacional: {'Sí ✅' if raffle_details['international_shipping'] else 'No ❌'}\n"
|
||||||
|
main_announcement += f"💵 <b>Donación mínima:</b> {original_price_per_number}€\n"
|
||||||
|
main_announcement += f"🗒️ Quedan {remaining_numbers_amount} participaciones disponibles. ¡Date prisa! 🗒️\n\n"
|
||||||
|
main_announcement += f"📜 <b>Normas y condiciones:</b> {TYC_DOCUMENT_URL}"
|
||||||
|
|
||||||
|
# Update main message
|
||||||
|
main_message_id = get_main_message_id(raffle_id)
|
||||||
|
if main_message_id:
|
||||||
|
requests.post(f"{TELEGRAM_API_URL}/editMessageCaption", json={
|
||||||
|
"chat_id": channel_id_to_announce,
|
||||||
|
"message_id": main_message_id,
|
||||||
|
"caption": main_announcement,
|
||||||
|
"reply_markup": keyboard,
|
||||||
|
"parse_mode": "HTML"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Build caption for update message
|
||||||
|
caption = ""
|
||||||
|
|
||||||
|
# Add participant info if provided (for payment confirmations)
|
||||||
|
if current_user_name and numbers:
|
||||||
|
escaped_current_user_name = current_user_name
|
||||||
|
numbers_text = ""
|
||||||
|
if len(numbers) > 1:
|
||||||
|
numbers_text = f"con las participaciones: {', '.join(numbers)}"
|
||||||
|
else:
|
||||||
|
numbers_text = f"con la participación: {', '.join(numbers)}"
|
||||||
|
|
||||||
|
new_participant_line = f"🗳️ @{escaped_current_user_name} se ha unido al sorteo {numbers_text}. ¡Mucha suerte! 🗳️"
|
||||||
|
caption += f"{new_participant_line}\n\n"
|
||||||
|
|
||||||
|
# Add last participants
|
||||||
|
last_other_participants = get_last_n_other_participants(raffle_id, n=4)
|
||||||
|
last_participants_text = format_last_participants_list(last_other_participants)
|
||||||
|
caption += f"{last_participants_text}\n\n"
|
||||||
|
|
||||||
|
# Add remaining numbers info
|
||||||
|
remaining_numbers_text = ""
|
||||||
|
if remaining_numbers_amount > 10:
|
||||||
|
remaining_numbers_text = f"🗒️ Todavía hay {remaining_numbers_amount} participaciones disponibles. 🗒️"
|
||||||
|
elif remaining_numbers_amount == 1:
|
||||||
|
remaining_numbers = get_remaining_numbers(raffle_id)
|
||||||
|
remaining_numbers_text = f"⏰⏰⏰ ¡Última participación! ⏰⏰⏰\n\n"
|
||||||
|
remaining_numbers_text += f"Queda la participación: {remaining_numbers[0]}"
|
||||||
|
elif remaining_numbers_amount == 0:
|
||||||
|
remaining_numbers_text = "⌛ ¡Ya no hay participaciones! ⌛\n\n"
|
||||||
|
remaining_numbers_text += "¡El resultado del sorteo se dará a conocer a las 21:45h!"
|
||||||
|
else:
|
||||||
|
remaining_numbers = get_remaining_numbers(raffle_id)
|
||||||
|
remaining_numbers_text = f"🔔🔔🔔 ¡Últimas {remaining_numbers_amount} participaciones disponibles! 🔔🔔🔔\n\n"
|
||||||
|
remaining_numbers_text += f"Quedan las participaciones: {', '.join(remaining_numbers)}"
|
||||||
|
|
||||||
|
caption += f"{remaining_numbers_text}\n\n"
|
||||||
|
|
||||||
|
# Add raffle description and details
|
||||||
|
caption += (
|
||||||
|
f"{raffle_description_for_announce}\n\n"
|
||||||
|
f"🔎 Ver detalles: https://t.me/{REVERSE_CHANNELS.get(channel_id_to_announce)}/{get_main_message_id(raffle_id)}\n\n"
|
||||||
|
f"🌍 Envío internacional: {'Sí ✅' if raffle_details['international_shipping'] else 'No ❌'}\n"
|
||||||
|
f"💵 Donación mínima: {original_price_per_number}€\n\n"
|
||||||
|
f"📜 Normas y condiciones: {TYC_DOCUMENT_URL} \n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send or update the image message
|
||||||
|
update_message_id = get_update_message_id(raffle_id)
|
||||||
|
sent_or_edited_message_id = None
|
||||||
|
|
||||||
|
if update_message_id:
|
||||||
|
logger.info(f"Attempting to delete old message {update_message_id} and send new one in channel {channel_id_to_announce}")
|
||||||
|
|
||||||
|
# Try deleting old message first
|
||||||
|
try:
|
||||||
|
delete_payload = {'chat_id': channel_id_to_announce, 'message_id': update_message_id}
|
||||||
|
delete_response = requests.post(f"{TELEGRAM_API_URL}/deleteMessage", data=delete_payload)
|
||||||
|
if delete_response.status_code == 200:
|
||||||
|
logger.info(f"Successfully deleted old message {update_message_id} in channel {channel_id_to_announce}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Failed to delete old message {update_message_id} in channel {channel_id_to_announce}: {delete_response.text}")
|
||||||
|
except Exception as e_del:
|
||||||
|
logger.warning(f"Error deleting old message {update_message_id}: {e_del}")
|
||||||
|
|
||||||
|
# Always send new photo after delete attempt
|
||||||
|
files = {'photo': open(image_path, 'rb')}
|
||||||
|
data = {
|
||||||
|
'chat_id': channel_id_to_announce,
|
||||||
|
'caption': caption,
|
||||||
|
'parse_mode': 'HTML'
|
||||||
|
}
|
||||||
|
if keyboard:
|
||||||
|
data['reply_markup'] = json.dumps(keyboard)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{TELEGRAM_API_URL}/sendPhoto", data=data, files=files)
|
||||||
|
response.raise_for_status()
|
||||||
|
logger.info(f"Sent new photo to channel {channel_id_to_announce}")
|
||||||
|
message_data = response.json().get('result')
|
||||||
|
if message_data and 'message_id' in message_data:
|
||||||
|
sent_or_edited_message_id = message_data['message_id']
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"Error sending new photo to channel {channel_id_to_announce}: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
files['photo'].close()
|
||||||
|
else:
|
||||||
|
# No previous message, send new
|
||||||
|
logger.info(f"No previous message found for raffle {raffle_id} in channel {channel_id_to_announce}. Sending new.")
|
||||||
|
files = {'photo': open(image_path, 'rb')}
|
||||||
|
data = {
|
||||||
|
'chat_id': channel_id_to_announce,
|
||||||
|
'caption': caption,
|
||||||
|
'parse_mode': 'HTML'
|
||||||
|
}
|
||||||
|
if keyboard:
|
||||||
|
data['reply_markup'] = json.dumps(keyboard)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{TELEGRAM_API_URL}/sendPhoto", data=data, files=files)
|
||||||
|
response.raise_for_status()
|
||||||
|
logger.info(f"Sent photo to channel {channel_id_to_announce}")
|
||||||
|
message_data = response.json().get('result')
|
||||||
|
if message_data and 'message_id' in message_data:
|
||||||
|
sent_or_edited_message_id = message_data['message_id']
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error(f"Error sending photo to channel {channel_id_to_announce}: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
files['photo'].close()
|
||||||
|
|
||||||
|
# Store the new message ID for future updates
|
||||||
|
if sent_or_edited_message_id:
|
||||||
|
store_update_message_id(raffle_id, sent_or_edited_message_id)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in send_raffle_update_image for raffle {raffle_id}: {e}")
|
||||||
|
return False
|
||||||
@@ -122,6 +122,7 @@ def generate_admin_raffle_details_keyboard(raffle_id):
|
|||||||
keyboard = [
|
keyboard = [
|
||||||
# Add relevant actions here if needed later, e.g., edit description?
|
# 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("📢 Anunciar de Nuevo", callback_data=f"{ADMIN_ANNOUNCE_RAFFLE_PREFIX}{raffle_id}")],
|
||||||
|
[InlineKeyboardButton("🔄 Actualizar Imagen", callback_data=f"{ADMIN_UPDATE_IMAGE_PREFIX}{raffle_id}")],
|
||||||
[InlineKeyboardButton("🏁 Terminar Sorteo", callback_data=f"{ADMIN_END_RAFFLE_PROMPT_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
|
[InlineKeyboardButton("⬅️ Volver a la Lista", callback_data=ADMIN_MENU_LIST)] # Back to list view
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import os # Import os to get BOT_TOKEN
|
|||||||
# Import necessary functions from your project structure
|
# Import necessary functions from your project structure
|
||||||
# Adjust the path if paypal_processor.py is not in the root 'app' directory
|
# Adjust the path if paypal_processor.py is not in the root 'app' directory
|
||||||
# Assuming it can access the other modules directly as in the docker setup:
|
# Assuming it can access the other modules directly as in the docker setup:
|
||||||
from helpers import generate_table_image, format_last_participants_list, get_paypal_access_token
|
from helpers import generate_table_image, format_last_participants_list, get_paypal_access_token, is_vip_member_of_homelabs, send_raffle_update_image
|
||||||
from database import (
|
from database import (
|
||||||
get_user_by_invoice_id, confirm_reserved_numbers,
|
get_user_by_invoice_id, confirm_reserved_numbers,
|
||||||
get_raffle_name, get_raffle,
|
get_raffle_name, get_raffle,
|
||||||
@@ -19,7 +19,7 @@ from database import (
|
|||||||
get_last_n_other_participants, get_remaining_numbers,
|
get_last_n_other_participants, get_remaining_numbers,
|
||||||
delete_reservation_timestamp, cancel_reservation_by_id
|
delete_reservation_timestamp, cancel_reservation_by_id
|
||||||
)
|
)
|
||||||
from config import BOT_TOKEN, BOT_NAME, WEBHOOK_ID, TYC_DOCUMENT_URL, REVERSE_CHANNELS, NEWRELIC_API_KEY, PAYPAL_URL, ADMIN_IDS
|
from config import BOT_TOKEN, BOT_NAME, WEBHOOK_ID, TYC_DOCUMENT_URL, REVERSE_CHANNELS, NEWRELIC_API_KEY, PAYPAL_URL, ADMIN_IDS, VIP_DISCOUNT_PER_NUMBER
|
||||||
from newrelic_telemetry_sdk import Log, LogClient
|
from newrelic_telemetry_sdk import Log, LogClient
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -126,7 +126,6 @@ def send_telegram_photo(chat_id, photo_path, caption=None, keyboard=None, parse_
|
|||||||
if 'photo' in files and files['photo']:
|
if 'photo' in files and files['photo']:
|
||||||
files['photo'].close()
|
files['photo'].close()
|
||||||
|
|
||||||
# --- CORRECTED FUNCTION ---
|
|
||||||
def receive_paypal_payment(invoice_id, payment_status, payment_amount_str):
|
def receive_paypal_payment(invoice_id, payment_status, payment_amount_str):
|
||||||
"""Processes verified PayPal payment data."""
|
"""Processes verified PayPal payment data."""
|
||||||
logger.info(f"Processing payment for Invoice: {invoice_id}, Status: {payment_status}, Amount: {payment_amount_str}")
|
logger.info(f"Processing payment for Invoice: {invoice_id}, Status: {payment_status}, Amount: {payment_amount_str}")
|
||||||
@@ -147,7 +146,13 @@ def receive_paypal_payment(invoice_id, payment_status, payment_amount_str):
|
|||||||
raffle_id = participant_data['raffle_id']
|
raffle_id = participant_data['raffle_id']
|
||||||
numbers_str = participant_data['numbers']
|
numbers_str = participant_data['numbers']
|
||||||
raffle_details = get_raffle(raffle_id)
|
raffle_details = get_raffle(raffle_id)
|
||||||
price_per_number = raffle_details['price']
|
original_price_per_number = raffle_details['price']
|
||||||
|
is_vip = is_vip_member_of_homelabs(user_id)
|
||||||
|
if is_vip:
|
||||||
|
logger.info(f"User {user_id} ({current_user_name}) is a VIP member. Applying discount.")
|
||||||
|
price_per_number = max(1, raffle_details['price'] - VIP_DISCOUNT_PER_NUMBER) # Ensure price doesn't go negative
|
||||||
|
else:
|
||||||
|
price_per_number = raffle_details['price']
|
||||||
channel_id_to_announce = raffle_details['channel_id']
|
channel_id_to_announce = raffle_details['channel_id']
|
||||||
update_message_id = raffle_details['update_message_id']
|
update_message_id = raffle_details['update_message_id']
|
||||||
|
|
||||||
@@ -206,144 +211,15 @@ def receive_paypal_payment(invoice_id, payment_status, payment_amount_str):
|
|||||||
# Raffle name can be added here if desired, requires one more DB call or adding it to get_user_by_invoice_id
|
# Raffle name can be added here if desired, requires one more DB call or adding it to get_user_by_invoice_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate table image
|
# Send raffle update image with participant info
|
||||||
if generate_table_image(raffle_id):
|
if send_raffle_update_image(raffle_id, current_user_name, numbers, BOT_TOKEN):
|
||||||
image_path = f"/app/data/raffles/raffle_table_{raffle_id}.png"
|
# Send image confirmation to user
|
||||||
|
if generate_table_image(raffle_id):
|
||||||
# Get general raffle details (like description) for announcement caption
|
image_path = f"/app/data/raffles/raffle_table_{raffle_id}.png"
|
||||||
raffle_details_general = get_raffle(raffle_id) # Fetches name, description, image_id
|
user_caption = f"¡Apuntado satisfactoriamente al sorteo '{raffle_name}'! Tus participaciones son: {', '.join(numbers)}"
|
||||||
if not raffle_details_general:
|
send_telegram_photo(user_id, image_path, caption=user_caption)
|
||||||
logger.error(f"Could not fetch general raffle details for ID {raffle_id} after payment confirmation.")
|
|
||||||
return # Or handle more gracefully
|
|
||||||
|
|
||||||
raffle_description_for_announce = raffle_details_general['description']
|
|
||||||
remaining_numbers_amount = get_remaining_numbers_amount(raffle_id)
|
|
||||||
|
|
||||||
# If it's the last number, update the main message and delete the participate button
|
|
||||||
if remaining_numbers_amount == 0:
|
|
||||||
keyboard = None
|
|
||||||
main_announcement = f"🎯🏆🎯 <b>Sorteo '{raffle_name}' terminado</b> 🎯🏆🎯\n\n"
|
|
||||||
main_announcement += f"{raffle_details['description']}\n\n"
|
|
||||||
main_announcement += f"🌍 Envío internacional: {'Sí ✅' if raffle_details['international_shipping'] else 'No ❌'}\n"
|
|
||||||
main_announcement += f"💵 <b>Donación mínima:</b> {raffle_details['price']}€\n"
|
|
||||||
main_announcement += f"📜 <b>Normas y condiciones:</b> {TYC_DOCUMENT_URL}"
|
|
||||||
main_message_id = get_main_message_id(raffle_id)
|
|
||||||
requests.post(f"{TELEGRAM_API_URL}/editMessageCaption", json={
|
|
||||||
"chat_id": channel_id_to_announce,
|
|
||||||
"message_id": main_message_id,
|
|
||||||
"caption": main_announcement,
|
|
||||||
"reply_markup": keyboard,
|
|
||||||
"parse_mode": "HTML"
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
url = f"https://t.me/{BOT_NAME}?start=join_{raffle_id}"
|
|
||||||
keyboard = {
|
|
||||||
"inline_keyboard": [
|
|
||||||
[
|
|
||||||
{"text": "✅ ¡Participar Ahora! ✅", "url": url}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
main_announcement = f"🏆 Sorteo '{raffle_name}' en progreso 🏆\n\n"
|
|
||||||
main_announcement += f"{raffle_details['description']}\n\n"
|
|
||||||
main_announcement += f"🌍 Envío internacional: {'Sí ✅' if raffle_details['international_shipping'] else 'No ❌'}\n"
|
|
||||||
main_announcement += f"💵 <b>Donación mínima:</b> {raffle_details['price']}€\n"
|
|
||||||
main_announcement += f"🗒️ Quedan {remaining_numbers_amount} participaciones disponibles. ¡Date prisa! 🗒️\n\n"
|
|
||||||
main_announcement += f"📜 <b>Normas y condiciones:</b> {TYC_DOCUMENT_URL}"
|
|
||||||
main_message_id = get_main_message_id(raffle_id)
|
|
||||||
requests.post(f"{TELEGRAM_API_URL}/editMessageCaption", json={
|
|
||||||
"chat_id": channel_id_to_announce,
|
|
||||||
"message_id": main_message_id,
|
|
||||||
"caption": main_announcement,
|
|
||||||
"reply_markup": keyboard,
|
|
||||||
"parse_mode": "HTML"
|
|
||||||
})
|
|
||||||
|
|
||||||
last_other_participants = get_last_n_other_participants(raffle_id, n=4)
|
|
||||||
last_participants_text = format_last_participants_list(last_other_participants)
|
|
||||||
|
|
||||||
escaped_current_user_name = current_user_name
|
|
||||||
|
|
||||||
numbers_text = ""
|
|
||||||
if len(numbers) > 1:
|
|
||||||
numbers_text = f"con las participaciones: {', '.join(numbers)}"
|
|
||||||
else:
|
|
||||||
numbers_text = f"con la participación: {', '.join(numbers)}"
|
|
||||||
|
|
||||||
new_participant_line = f"🗳️ @{escaped_current_user_name} se ha unido al sorteo {numbers_text}. ¡Mucha suerte! 🗳️"
|
|
||||||
|
|
||||||
remaining_numbers_text = ""
|
|
||||||
if remaining_numbers_amount > 10:
|
|
||||||
remaining_numbers_text = f"🗒️ Todavía hay {remaining_numbers_amount} participaciones disponibles. 🗒️"
|
|
||||||
elif remaining_numbers_amount == 1:
|
|
||||||
remaining_numbers = get_remaining_numbers(raffle_id)
|
|
||||||
remaining_numbers_text = f"⏰⏰⏰ ¡Última participación! ⏰⏰⏰\n\n"
|
|
||||||
remaining_numbers_text += f"Queda la participación: {remaining_numbers[0]}"
|
|
||||||
elif remaining_numbers_amount == 0:
|
|
||||||
remaining_numbers_text = "⌛ ¡Ya no hay participaciones! ⌛\n\n"
|
|
||||||
remaining_numbers_text += "¡El resultado del sorteo se dará a conocer a las 21:45h!"
|
|
||||||
else:
|
|
||||||
remaining_numbers = get_remaining_numbers(raffle_id)
|
|
||||||
remaining_numbers_text = f"🔔🔔🔔 ¡Últimas {remaining_numbers_amount} participaciones disponibles! 🔔🔔🔔\n\n"
|
|
||||||
remaining_numbers_text += f"Quedan las participaciones: {', '.join(remaining_numbers)}"
|
|
||||||
|
|
||||||
caption = (
|
|
||||||
f"{new_participant_line}\n\n"
|
|
||||||
f"{last_participants_text}\n\n"
|
|
||||||
f"{remaining_numbers_text}\n\n"
|
|
||||||
f"{raffle_description_for_announce}\n\n"
|
|
||||||
f"🔎 Ver detalles: https://t.me/{REVERSE_CHANNELS.get(channel_id_to_announce)}/{get_main_message_id(raffle_id)}\n\n"
|
|
||||||
f"🌍 Envío internacional: {'Sí ✅' if raffle_details['international_shipping'] else 'No ❌'}\n"
|
|
||||||
f"💵 Donación mínima: {price_per_number}€\n\n"
|
|
||||||
f"📜 Normas y condiciones: {TYC_DOCUMENT_URL} \n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
update_message_id = get_update_message_id(raffle_id)
|
|
||||||
sent_or_edited_message_id = None
|
|
||||||
|
|
||||||
if update_message_id:
|
|
||||||
logger.info(f"Attempting to edit message {update_message_id} in channel {channel_id_to_announce}")
|
|
||||||
|
|
||||||
# Try deleting old message first
|
|
||||||
try:
|
|
||||||
delete_payload = {'chat_id': channel_id_to_announce, 'message_id': update_message_id}
|
|
||||||
delete_response = requests.post(f"{TELEGRAM_API_URL}/deleteMessage", data=delete_payload)
|
|
||||||
if delete_response.status_code == 200:
|
|
||||||
logger.info(f"Successfully deleted old message {update_message_id} in channel {channel_id_to_announce}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"Failed to delete old message {update_message_id} in channel {channel_id_to_announce}: {delete_response.text}. Will send new.")
|
|
||||||
except Exception as e_del:
|
|
||||||
logger.warning(f"Error deleting old message {update_message_id}: {e_del}. Will send new.")
|
|
||||||
|
|
||||||
# Always send new photo after delete attempt, ensures updated image is shown
|
|
||||||
new_msg_info = send_telegram_photo(channel_id_to_announce, image_path, caption=caption, keyboard=keyboard, parse_mode='HTML')
|
|
||||||
if new_msg_info and isinstance(new_msg_info, dict) and 'message_id' in new_msg_info: # If send_telegram_photo returns message object
|
|
||||||
sent_or_edited_message_id = new_msg_info['message_id']
|
|
||||||
elif isinstance(new_msg_info, bool) and new_msg_info is True: # If it just returns True/False
|
|
||||||
# We can't get message_id this way. Need send_telegram_photo to return it.
|
|
||||||
logger.warning("send_telegram_photo did not return message_id, cannot store for future edits.")
|
|
||||||
else: # Sending new failed
|
|
||||||
logger.error(f"Failed to send new photo to channel {channel_id_to_announce} after deleting old.")
|
|
||||||
|
|
||||||
else: # No previous message, send new
|
|
||||||
logger.info(f"No previous message found for raffle {raffle_id} in channel {channel_id_to_announce}. Sending new.")
|
|
||||||
new_msg_info = send_telegram_photo(channel_id_to_announce, image_path, caption=caption, keyboard=keyboard, parse_mode='HTML')
|
|
||||||
# Similar logic to get sent_or_edited_message_id as above
|
|
||||||
if new_msg_info and isinstance(new_msg_info, dict) and 'message_id' in new_msg_info:
|
|
||||||
sent_or_edited_message_id = new_msg_info['message_id']
|
|
||||||
elif isinstance(new_msg_info, bool) and new_msg_info is True:
|
|
||||||
logger.warning("send_telegram_photo did not return message_id for new message.")
|
|
||||||
|
|
||||||
if sent_or_edited_message_id:
|
|
||||||
store_update_message_id(raffle_id, sent_or_edited_message_id)
|
|
||||||
|
|
||||||
# Send image confirmation to user (price not needed in this caption)
|
|
||||||
user_caption = f"¡Apuntado satisfactoriamente al sorteo '{raffle_name}'! Tus participaciones son: {', '.join(numbers)}"
|
|
||||||
send_telegram_photo(user_id, image_path, caption=user_caption)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"Failed to generate raffle table image for {raffle_id} after payment.")
|
logger.error(f"Failed to send raffle update image for {raffle_id} after payment.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# This case means the DB update failed, which is serious if payment was valid.
|
# This case means the DB update failed, which is serious if payment was valid.
|
||||||
@@ -358,6 +234,22 @@ def receive_paypal_payment(invoice_id, payment_status, payment_amount_str):
|
|||||||
# admin_chat_id = "YOUR_ADMIN_CHAT_ID"
|
# admin_chat_id = "YOUR_ADMIN_CHAT_ID"
|
||||||
# send_telegram_message(admin_chat_id, f"CRITICAL DB Error: Failed to confirm numbers for invoice {invoice_id}, user {user_id}, raffle {raffle_id}. Payment was valid. Manual check needed.")
|
# send_telegram_message(admin_chat_id, f"CRITICAL DB Error: Failed to confirm numbers for invoice {invoice_id}, user {user_id}, raffle {raffle_id}. Payment was valid. Manual check needed.")
|
||||||
|
|
||||||
|
def notify_user_of_pending_review(invoice_id, payment_status, status_details):
|
||||||
|
"""Notify user that their payment is pending review."""
|
||||||
|
participant_data = get_user_by_invoice_id(invoice_id)
|
||||||
|
if not participant_data:
|
||||||
|
logger.warning(f"No participant found for Invoice ID: {invoice_id}. Cannot notify about pending review.")
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = participant_data['user_id']
|
||||||
|
send_telegram_message(
|
||||||
|
user_id,
|
||||||
|
f"⚠️ Tu pago para la factura {invoice_id} está pendiente de revisión por PayPal.\n"
|
||||||
|
f"El estado actual es '{payment_status}' con detalles: '{status_details}'.\n"
|
||||||
|
f"El sorteo solo se confirma con pagos 'Completed'. Cuando el pago sea confirmado, recibirás una notificación.\n"
|
||||||
|
f"Tus números reservados se mantendrán durante este proceso."
|
||||||
|
)
|
||||||
|
|
||||||
def capture_order(order_id, access_token):
|
def capture_order(order_id, access_token):
|
||||||
url = f"{PAYPAL_URL}/v2/checkout/orders/{order_id}/capture"
|
url = f"{PAYPAL_URL}/v2/checkout/orders/{order_id}/capture"
|
||||||
headers = {
|
headers = {
|
||||||
@@ -442,6 +334,7 @@ def paypal_webhook():
|
|||||||
status_details = resource["status_details"]["reason"]
|
status_details = resource["status_details"]["reason"]
|
||||||
|
|
||||||
delete_reservation_timestamp(invoice_id)
|
delete_reservation_timestamp(invoice_id)
|
||||||
|
notify_user_of_pending_review(invoice_id, payment_status, status_details)
|
||||||
|
|
||||||
if status_details == "PENDING_REVIEW":
|
if status_details == "PENDING_REVIEW":
|
||||||
logger.info(f"Payment for invoice {invoice_id} is pending review. Notifying admins.")
|
logger.info(f"Payment for invoice {invoice_id} is pending review. Notifying admins.")
|
||||||
@@ -498,4 +391,4 @@ if __name__ == "__main__":
|
|||||||
exit(1)
|
exit(1)
|
||||||
TELEGRAM_API_URL = f"https://api.telegram.org/bot{BOT_TOKEN}" # Set global URL
|
TELEGRAM_API_URL = f"https://api.telegram.org/bot{BOT_TOKEN}" # Set global URL
|
||||||
|
|
||||||
app.run(port=5000, debug=False, host="0.0.0.0") # Disable debug in production
|
app.run(port=5000, debug=False, host="0.0.0.0") # Disable debug in production
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ services:
|
|||||||
- PAYPAL_PERCENTAGE_FEE=${PAYPAL_PERCENTAGE_FEE}
|
- PAYPAL_PERCENTAGE_FEE=${PAYPAL_PERCENTAGE_FEE}
|
||||||
- PAYPAL_FIXED_FEE=${PAYPAL_FIXED_FEE}
|
- PAYPAL_FIXED_FEE=${PAYPAL_FIXED_FEE}
|
||||||
- PAYPAL_URL=${PAYPAL_URL}
|
- PAYPAL_URL=${PAYPAL_URL}
|
||||||
|
- ANNOUNCE_CHANNEL_IDS=${ANNOUNCE_CHANNEL_IDS}
|
||||||
|
- HOMELABS_API_TOKEN=${HOMELABS_API_TOKEN}
|
||||||
|
- HOMELABS_API_URL=${HOMELABS_API_URL}
|
||||||
|
- VIP_DISCOUNT_PER_NUMBER=${VIP_DISCOUNT_PER_NUMBER}
|
||||||
|
networks:
|
||||||
|
- traefik
|
||||||
telerifas_paypal_processor:
|
telerifas_paypal_processor:
|
||||||
build:
|
build:
|
||||||
context: app
|
context: app
|
||||||
@@ -40,6 +46,7 @@ services:
|
|||||||
- TZ="Europe/Madrid"
|
- TZ="Europe/Madrid"
|
||||||
- BOT_TOKEN=${BOT_TOKEN}
|
- BOT_TOKEN=${BOT_TOKEN}
|
||||||
- BOT_NAME=${BOT_NAME}
|
- BOT_NAME=${BOT_NAME}
|
||||||
|
- ADMIN_IDS=${ADMIN_IDS}
|
||||||
- CHANNEL_IDS=${CHANNEL_IDS}
|
- CHANNEL_IDS=${CHANNEL_IDS}
|
||||||
- PAYPAL_EMAIL=${PAYPAL_EMAIL}
|
- PAYPAL_EMAIL=${PAYPAL_EMAIL}
|
||||||
- PAYPAL_HANDLE=${PAYPAL_HANDLE}
|
- PAYPAL_HANDLE=${PAYPAL_HANDLE}
|
||||||
@@ -50,6 +57,9 @@ services:
|
|||||||
- TYC_DOCUMENT_URL=${TYC_DOCUMENT_URL}
|
- TYC_DOCUMENT_URL=${TYC_DOCUMENT_URL}
|
||||||
- NEWRELIC_API_KEY=${NEWRELIC_API_KEY}
|
- NEWRELIC_API_KEY=${NEWRELIC_API_KEY}
|
||||||
- PAYPAL_URL=${PAYPAL_URL}
|
- PAYPAL_URL=${PAYPAL_URL}
|
||||||
|
- HOMELABS_API_TOKEN=${HOMELABS_API_TOKEN}
|
||||||
|
- HOMELABS_API_URL=${HOMELABS_API_URL}
|
||||||
|
- VIP_DISCOUNT_PER_NUMBER=${VIP_DISCOUNT_PER_NUMBER}
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
Reference in New Issue
Block a user