Divide home/views.py en el paquete home/views/ por secciones
El módulo monolítico (~3200 líneas) se convierte en un paquete cuyo __init__.py re-exporta todas las vistas, de modo que `views.X`, `from home.views import X` y la cadena 'home.views.not_found' (handler404) siguen funcionando sin tocar urls.py. Módulos por sección: _base (imports comunes + require_paid_stripe), auth, pages, account, recruit, characters, store, points, guild, history, players y payments. Cada submódulo hereda los imports con `from ._base import *`; las dependencias entre secciones se resuelven con imports explícitos (guild->auth, recruit/history->account). Se eliminan dos definiciones duplicadas que quedaban muertas al cargar: - get_character_image (primera copia, idéntica a la efectiva) - store_novawow_success_view (stub sin SOAP, pisado por la versión real) Validado con `manage.py check` (0 issues), resolución de las 100 rutas y un chequeo estático de nombres no definidos por módulo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
-3182
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
"""Paquete de vistas de NovaWoW (antes un único home/views.py de ~3200 líneas).
|
||||
|
||||
Dividido por secciones. El `import *` de cada submódulo re-exporta las vistas
|
||||
para que `home.views.<vista>`, `from home.views import <vista>` y la referencia
|
||||
por cadena `'home.views.not_found'` (handler404) sigan funcionando igual.
|
||||
"""
|
||||
from ._base import * # noqa: F401,F403
|
||||
from .auth import * # noqa: F401,F403
|
||||
from .pages import * # noqa: F401,F403
|
||||
from .account import * # noqa: F401,F403
|
||||
from .recruit import * # noqa: F401,F403
|
||||
from .characters import * # noqa: F401,F403
|
||||
from .store import * # noqa: F401,F403
|
||||
from .points import * # noqa: F401,F403
|
||||
from .guild import * # noqa: F401,F403
|
||||
from .history import * # noqa: F401,F403
|
||||
from .players import * # noqa: F401,F403
|
||||
from .payments import * # noqa: F401,F403
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Imports y utilidades compartidas por el paquete de vistas (`home.views`).
|
||||
|
||||
Cada submódulo de vistas hace `from ._base import *` para heredar estos imports
|
||||
y el decorador `require_paid_stripe`.
|
||||
"""
|
||||
import socket, hashlib, gmpy2, binascii, os, re, json, logging, secrets
|
||||
from collections import Counter
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from functools import wraps
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from django.db import connections
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
import stripe
|
||||
|
||||
from ..ac_soap import execute_soap_command
|
||||
from ..library_correo import enviar_correo
|
||||
from ..pagos_stripe import create_checkout_session, is_session_paid
|
||||
from ..pagos_sumup import crear_checkout, crear_checkout_battlepay, obtener_checkout
|
||||
from ..zone_definitions import get_zone_name
|
||||
from .. import bnet
|
||||
from ..models import (
|
||||
Noticia, ClienteCategoria, ServerSelection, RecruitAFriend, DownloadClientPage,
|
||||
ContentCreator, RecruitReward, ClaimedReward, AccountActivation, SecurityToken,
|
||||
GuildRenameSettings, VoteSite, VoteLog, HomeApiPoints, UnstuckHistory, ReviveHistory,
|
||||
RenamePrice, CustomizePrice, ChangeRacePrice, ChangeFactionPrice, LevelUpPrice,
|
||||
GoldPrice, TransferPrice, Category, Item, StripeLog, LoginAttempt, Pedido,
|
||||
)
|
||||
|
||||
# Configuración del logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def require_paid_stripe(redirect_to='index'):
|
||||
"""
|
||||
Protege las vistas de "éxito" de pago: exige un `session_id` de Stripe
|
||||
realmente PAGADO en la URL (Stripe lo añade tras el pago) y evita re-entregas
|
||||
marcando el StripeLog como `fulfilled`. Cierra el bypass de ir directo a la
|
||||
success-url sin pagar.
|
||||
"""
|
||||
def deco(view):
|
||||
@wraps(view)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
session_id = request.GET.get('session_id')
|
||||
if not is_session_paid(session_id):
|
||||
messages.error(request, 'No se ha podido verificar el pago.')
|
||||
return redirect(redirect_to)
|
||||
log = StripeLog.objects.filter(session_id=session_id).first()
|
||||
if log is not None and log.fulfilled:
|
||||
messages.info(request, 'Este pago ya había sido procesado.')
|
||||
return redirect(redirect_to)
|
||||
response = view(request, *args, **kwargs)
|
||||
if log is not None:
|
||||
log.fulfilled = True
|
||||
log.save(update_fields=['fulfilled'])
|
||||
return response
|
||||
return wrapper
|
||||
return deco
|
||||
@@ -0,0 +1,268 @@
|
||||
from ._base import *
|
||||
|
||||
dias_semana = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo']
|
||||
meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']
|
||||
|
||||
|
||||
def get_class_css(class_id):
|
||||
class_map = {
|
||||
1: 'warrior',
|
||||
2: 'paladin',
|
||||
3: 'hunter',
|
||||
4: 'rogue',
|
||||
5: 'priest',
|
||||
6: 'death-knight',
|
||||
7: 'shaman',
|
||||
8: 'mage',
|
||||
9: 'warlock',
|
||||
11: 'druid'
|
||||
}
|
||||
return class_map.get(class_id, 'unknown')
|
||||
def get_class_text_css(class_id):
|
||||
text_class_map = {
|
||||
1: 'warrior',
|
||||
2: 'paladin',
|
||||
3: 'hunter',
|
||||
4: 'rogue',
|
||||
5: 'priest',
|
||||
6: 'death-knight',
|
||||
7: 'shaman',
|
||||
8: 'mage',
|
||||
9: 'warlock',
|
||||
11: 'druid'
|
||||
}
|
||||
return text_class_map.get(class_id, 'unknown')
|
||||
def formatear_fecha(fecha):
|
||||
"""Formatea la fecha al estilo solicitado."""
|
||||
dia_semana = dias_semana[fecha.weekday()]
|
||||
mes = meses[fecha.month - 1]
|
||||
return f"{dia_semana} {fecha.day:02d} de {mes} del {fecha.year}"
|
||||
def my_account(request):
|
||||
# Verificar si la sesión contiene la cuenta de juego seleccionada
|
||||
username = request.session.get('username')
|
||||
|
||||
# Si aún no se ha elegido cuenta de juego pero hay login Battle.net,
|
||||
# enviar al selector; si no, al inicio de sesión.
|
||||
if not username:
|
||||
if request.session.get('bnet_id'):
|
||||
return redirect('select-account')
|
||||
return redirect('login')
|
||||
|
||||
is_logged_in = True # Si el usuario está en la sesión, asumimos que está conectado
|
||||
|
||||
# Obtener información básica de la cuenta
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, username, reg_mail, email, last_ip, last_attempt_ip, joindate
|
||||
FROM account
|
||||
WHERE username = %s
|
||||
""", [username])
|
||||
account_data = cursor.fetchone()
|
||||
|
||||
if not account_data:
|
||||
return redirect('index')
|
||||
|
||||
account_id = account_data[0]
|
||||
|
||||
# Obtener los personajes asociados a la cuenta
|
||||
characters = get_account_characters(account_id)
|
||||
has_characters = bool(characters)
|
||||
|
||||
# Obtener el estado de la cuenta
|
||||
account_status = get_account_status(account_id)
|
||||
|
||||
# Obtener los puntos (DP y VP) del usuario desde la tabla `home_api_points`
|
||||
user_points = HomeApiPoints.objects.filter(accountID=account_id).first()
|
||||
dp = user_points.dp if user_points else 0
|
||||
vp = user_points.vp if user_points else 0
|
||||
|
||||
|
||||
# Obtener el estado del token de seguridad
|
||||
security_token = SecurityToken.objects.filter(user_id=account_id).first()
|
||||
if security_token:
|
||||
token_status = "Solicitado"
|
||||
token_date = security_token.created_at.strftime('%H:%M:%S %d-%m-%Y')
|
||||
else:
|
||||
token_status = "Sin solicitar"
|
||||
token_date = None
|
||||
|
||||
# Créditos Battlepay de la cuenta Battle.net
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
battlepay_credits = bnet.get_battlepay_credits(cursor, request.session.get('bnet_id'))
|
||||
|
||||
return render(request, 'my-account/my-account.html', {
|
||||
'is_logged_in': is_logged_in,
|
||||
'battlepay_credits': battlepay_credits,
|
||||
'user_info': {
|
||||
'username': account_data[1],
|
||||
'reg_mail': account_data[2],
|
||||
'email': account_data[3],
|
||||
'last_ip': account_data[4],
|
||||
'last_attempt_ip': account_data[5],
|
||||
'joindate': formatear_fecha(account_data[6]),
|
||||
|
||||
},
|
||||
'account_status': account_status,
|
||||
'characters': characters,
|
||||
'has_characters': has_characters,
|
||||
'dp': dp,
|
||||
'vp': vp,
|
||||
'token_status': token_status,
|
||||
'token_date': token_date
|
||||
})
|
||||
def get_account_status(account_id):
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
# Obtener información de la tabla 'account'
|
||||
cursor.execute("""
|
||||
SELECT username, reg_mail, email, last_ip, last_attempt_ip, joindate, recruiter
|
||||
FROM account
|
||||
WHERE id = %s
|
||||
""", [account_id])
|
||||
account_data = cursor.fetchone()
|
||||
|
||||
if not account_data:
|
||||
return None
|
||||
|
||||
# Verificar si la cuenta está baneada
|
||||
cursor.execute("""
|
||||
SELECT unbandate, UNIX_TIMESTAMP()
|
||||
FROM account_banned
|
||||
WHERE id = %s AND active = 1
|
||||
""", [account_id])
|
||||
ban_data = cursor.fetchone()
|
||||
|
||||
# Obtener el número de amigos reclutados
|
||||
cursor.execute("""
|
||||
SELECT id
|
||||
FROM account
|
||||
WHERE recruiter = %s
|
||||
""", [account_id])
|
||||
recruited_accounts = cursor.fetchall()
|
||||
recruited_count = len(recruited_accounts)
|
||||
|
||||
# Contar cuántos personajes de amigos reclutados han alcanzado el nivel 80
|
||||
if recruited_count > 0:
|
||||
recruited_ids = [acc[0] for acc in recruited_accounts]
|
||||
with connections['acore_characters'].cursor() as char_cursor:
|
||||
char_cursor.execute("""
|
||||
SELECT COUNT(*)
|
||||
FROM characters
|
||||
WHERE account IN %s AND level = 80
|
||||
""", [tuple(recruited_ids)])
|
||||
level_80_count = char_cursor.fetchone()[0]
|
||||
else:
|
||||
level_80_count = 0
|
||||
|
||||
# Asignar los datos a variables
|
||||
user_info = {
|
||||
'username': account_data[0] if account_data[0] else 'No disponible',
|
||||
'reg_mail': account_data[1] if account_data[1] else 'No disponible',
|
||||
'email': account_data[2] if account_data[2] else 'No disponible',
|
||||
'last_ip': account_data[3] if account_data[3] else 'Nunca',
|
||||
'last_attempt_ip': account_data[4] if account_data[4] else 'Nunca',
|
||||
'joindate': account_data[5].strftime("%A %d de %B del %Y a las %H:%M:%S Horas") if account_data[5] else 'No disponible',
|
||||
'is_recruited': bool(account_data[6]),
|
||||
'recruited_count': recruited_count,
|
||||
'recruited_level_80_count': level_80_count
|
||||
}
|
||||
|
||||
# Información sobre la suspensión, si existe
|
||||
if ban_data:
|
||||
unban_timestamp, current_timestamp = ban_data
|
||||
remaining_time = unban_timestamp - current_timestamp
|
||||
user_info['is_banned'] = True
|
||||
user_info['unban_date'] = datetime.fromtimestamp(unban_timestamp).strftime("%A %d de %B del %Y a las %H:%M:%S Horas")
|
||||
user_info['remaining_time'] = remaining_time
|
||||
else:
|
||||
user_info['is_banned'] = False
|
||||
|
||||
return user_info
|
||||
def get_character_image(class_id, race, gender):
|
||||
"""
|
||||
Devuelve la imagen del personaje basada en la raza, clase y género.
|
||||
`gender`: 0 = Hombre, 1 = Mujer
|
||||
"""
|
||||
# Definir los nombres de archivos según el género
|
||||
gender_suffix = 'male' if gender == 0 else 'female'
|
||||
|
||||
# Raza y clases compatibles
|
||||
race_class_map = {
|
||||
10: [2, 3, 4, 5, 6, 8, 9], # Elfo de Sangre
|
||||
8: [1, 3, 4, 5, 6, 7, 8], # Trol
|
||||
6: [1, 3, 6, 7, 11], # Tauren
|
||||
5: [1, 4, 5, 6, 8, 9], # No-Muerto
|
||||
2: [1, 3, 4, 6, 7, 9], # Orco
|
||||
11: [1, 2, 3, 5, 6, 7, 8], # Draenei
|
||||
7: [1, 4, 6, 8, 9], # Gnomo
|
||||
4: [1, 3, 4, 5, 6, 11], # Elfo de la Noche
|
||||
3: [1, 2, 3, 4, 5, 6], # Enano
|
||||
1: [1, 2, 4, 5, 6, 8, 9] # Humano
|
||||
}
|
||||
|
||||
# Verificar si la clase es compatible con la raza
|
||||
if class_id not in race_class_map.get(race, []):
|
||||
return 'nw-themes/nw-ryu/nw-images/nw-classes/unknown.webp' # Imagen por defecto si no es compatible
|
||||
|
||||
# Construir el nombre del archivo basado en la raza y el género
|
||||
race_image_map = {
|
||||
10: f"big-blood-elf-{gender_suffix}.webp",
|
||||
8: f"big-troll-{gender_suffix}.webp",
|
||||
6: f"big-tauren-{gender_suffix}.webp",
|
||||
5: f"big-undead-{gender_suffix}.webp",
|
||||
2: f"big-orc-{gender_suffix}.webp",
|
||||
11: f"big-draenei-{gender_suffix}.webp",
|
||||
7: f"big-gnome-{gender_suffix}.webp",
|
||||
4: f"big-night-elf-{gender_suffix}.webp",
|
||||
3: f"big-dwarf-{gender_suffix}.webp",
|
||||
1: f"big-human-{gender_suffix}.webp"
|
||||
}
|
||||
|
||||
# Devolver la imagen según la raza y género, o una imagen por defecto
|
||||
return f"nw-themes/nw-ryu/nw-images/nw-races/{race_image_map.get(race, 'unknown.webp')}"
|
||||
def get_account_characters(account_id):
|
||||
"""Obtiene los personajes de la cuenta dada."""
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name, level, money, race, class, zone, gender
|
||||
FROM characters
|
||||
WHERE account = %s
|
||||
""", [account_id])
|
||||
characters = cursor.fetchall()
|
||||
|
||||
character_list = []
|
||||
for char in characters:
|
||||
name, level, money, race, class_id, zone, gender = char
|
||||
gold = money // 10000
|
||||
silver = (money % 10000) // 100
|
||||
copper = money % 100
|
||||
|
||||
# Obtener la clase CSS y la imagen antes de pasarla a la plantilla
|
||||
class_css = get_class_css(class_id)
|
||||
image_url = get_character_image(class_id, race, gender)
|
||||
|
||||
# Obtener el nombre de la zona usando la función get_zone_name
|
||||
zone_name = get_zone_name(zone)
|
||||
|
||||
character_list.append({
|
||||
'name': name,
|
||||
'level': level,
|
||||
'gold': gold,
|
||||
'silver': silver,
|
||||
'copper': copper,
|
||||
'race': race,
|
||||
'class_id': class_id,
|
||||
'zone': zone_name,
|
||||
'class_css': class_css,
|
||||
'image_url': image_url
|
||||
})
|
||||
return character_list
|
||||
def get_account_id(username):
|
||||
# Consulta para obtener el account_id a partir del username
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id
|
||||
FROM account
|
||||
WHERE username = %s
|
||||
""", [username])
|
||||
result = cursor.fetchone()
|
||||
return result[0] if result else None
|
||||
@@ -0,0 +1,614 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
email = forms.EmailField(max_length=320, required=True)
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=True)
|
||||
def sha1(data):
|
||||
"""Función auxiliar para calcular SHA-1."""
|
||||
return hashlib.sha1(data).digest()
|
||||
def calculate_srp6_verifier(username, password, salt):
|
||||
"""Calcula el verifier usando SRP6 basado en la implementación PHP."""
|
||||
g = gmpy2.mpz(7)
|
||||
N = gmpy2.mpz('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16)
|
||||
|
||||
# Paso 1: Calcular H(username:password)
|
||||
h1 = sha1((username.upper() + ':' + password.upper()).encode('utf-8'))
|
||||
|
||||
# Usar el salt proporcionado (sin invertir)
|
||||
salt_bytes = bytes.fromhex(salt)
|
||||
|
||||
# Paso 2: Calcular H(salt + H(username:password))
|
||||
h2 = sha1(salt_bytes + h1)
|
||||
|
||||
# Convertir el hash a un entero (little-endian)
|
||||
h2_int = gmpy2.mpz.from_bytes(h2, 'little')
|
||||
|
||||
# Calcular el verifier usando g^h2 mod N
|
||||
verifier = gmpy2.powmod(g, h2_int, N)
|
||||
verifier_bytes = verifier.to_bytes(32, 'little')
|
||||
|
||||
# Convertir el resultado a hexadecimal en mayúsculas
|
||||
return binascii.hexlify(verifier_bytes).decode('utf-8').upper()
|
||||
def authenticate(email, password):
|
||||
"""Autentica una cuenta Battle.net por email (SRP6 v2)."""
|
||||
try:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
account = bnet.get_bnet_account_by_email(cursor, email)
|
||||
if not account:
|
||||
return None
|
||||
if bnet.bnet_verify(account['email'], password, account['salt'], account['verifier']):
|
||||
return account
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
def _set_game_account_session(request, game_account):
|
||||
"""Fija en sesión la cuenta de juego seleccionada (para las demás vistas)."""
|
||||
request.session['username'] = game_account['username'].upper()
|
||||
request.session['account_id'] = game_account['id']
|
||||
def login_view(request):
|
||||
if request.method == 'POST':
|
||||
form = LoginForm(request.POST)
|
||||
if form.is_valid():
|
||||
email = form.cleaned_data['email']
|
||||
password = form.cleaned_data['password']
|
||||
ip_address = request.META.get('REMOTE_ADDR')
|
||||
|
||||
account = authenticate(email, password)
|
||||
if account:
|
||||
log_login_attempt(email, "Exito", ip_address)
|
||||
|
||||
# Guardar la identidad Battle.net en la sesión
|
||||
request.session['bnet_id'] = account['id']
|
||||
request.session['bnet_email'] = account['email']
|
||||
|
||||
# Resolver las cuentas de juego enlazadas
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
game_accounts = bnet.get_game_accounts_for_bnet(cursor, account['id'])
|
||||
|
||||
if len(game_accounts) == 1:
|
||||
# Una sola cuenta de juego: seleccionar automáticamente
|
||||
_set_game_account_session(request, game_accounts[0])
|
||||
elif len(game_accounts) > 1:
|
||||
# Varias: dejar la selección pendiente (my-account redirige al selector)
|
||||
request.session.pop('username', None)
|
||||
request.session.pop('account_id', None)
|
||||
# 0 cuentas: se queda sin username; el selector mostrará el aviso
|
||||
|
||||
return JsonResponse({'success': True})
|
||||
else:
|
||||
log_login_attempt(email, "Fracaso", ip_address)
|
||||
return JsonResponse({'success': False, 'error': "Correo o contraseña incorrectos"})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'error': "Formulario no valido"})
|
||||
|
||||
form = LoginForm()
|
||||
return render(request, 'auth/login.html', {'form': form})
|
||||
def select_account_view(request):
|
||||
"""Selector de cuenta de juego para cuentas Battle.net con varias."""
|
||||
bnet_id = request.session.get('bnet_id')
|
||||
if not bnet_id:
|
||||
return redirect('login')
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
game_accounts = bnet.get_game_accounts_for_bnet(cursor, bnet_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
selected_id = int(request.POST.get('account_id', ''))
|
||||
except (TypeError, ValueError):
|
||||
selected_id = None
|
||||
chosen = next((g for g in game_accounts if g['id'] == selected_id), None)
|
||||
if chosen:
|
||||
_set_game_account_session(request, chosen)
|
||||
return redirect('my-account')
|
||||
messages.error(request, 'Cuenta de juego no válida.')
|
||||
|
||||
# Autoseleccionar si solo hay una
|
||||
if len(game_accounts) == 1:
|
||||
_set_game_account_session(request, game_accounts[0])
|
||||
return redirect('my-account')
|
||||
|
||||
return render(request, 'auth/select_account.html', {
|
||||
'game_accounts': game_accounts,
|
||||
'bnet_email': request.session.get('bnet_email'),
|
||||
})
|
||||
def logout_view(request):
|
||||
for key in ('username', 'account_id', 'bnet_id', 'bnet_email'):
|
||||
request.session.pop(key, None)
|
||||
return redirect('index')
|
||||
def register_view(request):
|
||||
if request.method == 'POST':
|
||||
password = request.POST.get('password', '').strip()
|
||||
conf_password = request.POST.get('conf-password', '').strip()
|
||||
email = request.POST.get('email', '').strip()
|
||||
conf_email = request.POST.get('conf-email', '').strip()
|
||||
recruiter = request.POST.get('recruiter', '').strip()
|
||||
|
||||
# En 3.4.3 la identidad es el email (cuenta Battle.net); no hay username.
|
||||
if not password or not email:
|
||||
return JsonResponse({'success': False, 'message': 'Por favor, complete todos los campos.'})
|
||||
|
||||
if password != conf_password:
|
||||
return JsonResponse({'success': False, 'message': 'Las contraseñas no coinciden.'})
|
||||
|
||||
if len(password) > 16:
|
||||
return JsonResponse({'success': False, 'message': 'La contraseña no debe exceder los 16 caracteres.'})
|
||||
|
||||
if email != conf_email or not re.match(r'^[a-zA-Z0-9._%+-]+@gmail\.com$', email):
|
||||
return JsonResponse({'success': False, 'message': 'El correo electrónico no es válido.'})
|
||||
|
||||
# Verificar si ya existe una cuenta Battle.net con ese email
|
||||
email_norm = bnet.normalize_email(email)
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT COUNT(*) FROM battlenet_accounts WHERE email = %s", [email_norm])
|
||||
if cursor.fetchone()[0] > 0:
|
||||
return JsonResponse({'success': False, 'message': 'Ya existe una cuenta con ese correo electrónico.'})
|
||||
|
||||
# Comprobar que no haya un registro de activación pendiente para ese email
|
||||
if AccountActivation.objects.filter(email=email, old_email__isnull=True).exists():
|
||||
AccountActivation.objects.filter(email=email, old_email__isnull=True).delete()
|
||||
|
||||
# Validar y obtener recruiter_id (id de cuenta de juego del reclutador)
|
||||
recruiter_id = 0
|
||||
if recruiter:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [recruiter])
|
||||
recruiter_data = cursor.fetchone()
|
||||
if recruiter_data:
|
||||
recruiter_id = recruiter_data[0]
|
||||
else:
|
||||
with connections['acore_characters'].cursor() as char_cursor:
|
||||
char_cursor.execute("SELECT account FROM characters WHERE name = %s", [recruiter])
|
||||
character_data = char_cursor.fetchone()
|
||||
if character_data:
|
||||
recruiter_id = character_data[0]
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': 'El reclutador ingresado no existe.'})
|
||||
|
||||
# Generar un hash único para activación. Los salt/verifier se calculan al
|
||||
# activar (tanto el de bnet como el de la cuenta de juego).
|
||||
activation_hash = get_random_string(32)
|
||||
|
||||
AccountActivation.objects.create(
|
||||
email=email,
|
||||
password=password,
|
||||
recruiter_id=recruiter_id,
|
||||
hash=activation_hash
|
||||
)
|
||||
|
||||
# Enviar correo de activación
|
||||
activation_link = f"{settings.URL_PRINCIPAL}/es/activate-account?act={activation_hash}"
|
||||
context = {
|
||||
'username': email,
|
||||
'password': password,
|
||||
'activation_link': activation_link,
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
|
||||
}
|
||||
enviar_correo(
|
||||
subject=f'Activación de la cuenta {email} - {settings.NOMBRE_SERVIDOR}',
|
||||
to_email=email,
|
||||
template='emails/activation.html',
|
||||
context=context
|
||||
)
|
||||
|
||||
message = f"""
|
||||
<div class="alert-message" id="create-response" style="display: block;">
|
||||
<span class="ok-form-response">La cuenta '{email}' ha sido creada.</span><br><br>
|
||||
<span>Se ha enviado un enlace de activación al correo {email}.</span>
|
||||
</div>
|
||||
"""
|
||||
return JsonResponse({'success': True, 'message': message})
|
||||
|
||||
return render(request, 'auth/register.html')
|
||||
def activate_account_view(request):
|
||||
activation_hash = request.GET.get('act')
|
||||
|
||||
try:
|
||||
activation = AccountActivation.objects.get(hash=activation_hash)
|
||||
|
||||
if activation.is_expired():
|
||||
return render(request, 'auth/activation_invalid.html')
|
||||
|
||||
email_norm = bnet.normalize_email(activation.email)
|
||||
password = activation.password
|
||||
last_ip = request.META.get('REMOTE_ADDR')
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
# Evitar duplicados si el enlace se usa dos veces
|
||||
cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [email_norm])
|
||||
existing = cursor.fetchone()
|
||||
if existing:
|
||||
activation.delete()
|
||||
return render(request, 'auth/activation_success.html')
|
||||
|
||||
# 1) Crear la cuenta Battle.net (SRP6 v2)
|
||||
bnet_salt, bnet_verifier, srp_version = bnet.bnet_make_registration(email_norm, password)
|
||||
cursor.execute(
|
||||
"INSERT INTO battlenet_accounts (email, srp_version, salt, verifier) "
|
||||
"VALUES (%s, %s, %s, %s)",
|
||||
[email_norm, srp_version, bnet_salt, bnet_verifier]
|
||||
)
|
||||
|
||||
# 2) Recuperar el id de la cuenta bnet recién creada
|
||||
cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [email_norm])
|
||||
bnet_id = cursor.fetchone()[0]
|
||||
|
||||
# 3) Crear la cuenta de juego enlazada (<bnetId>#1, SRP6 Grunt)
|
||||
game_username = bnet.make_game_account_username(bnet_id, 1)
|
||||
game_salt, game_verifier = bnet.game_make_registration(game_username, password)
|
||||
cursor.execute(
|
||||
"INSERT INTO account "
|
||||
"(username, salt, verifier, email, reg_mail, recruiter, joindate, "
|
||||
" last_ip, expansion, battlenet_account, battlenet_index) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, %s, %s, %s)",
|
||||
[
|
||||
game_username, game_salt, game_verifier,
|
||||
email_norm, email_norm, activation.recruiter_id or 0,
|
||||
last_ip, 2, bnet_id, 1
|
||||
]
|
||||
)
|
||||
|
||||
activation.delete()
|
||||
return render(request, 'auth/activation_success.html')
|
||||
|
||||
except AccountActivation.DoesNotExist:
|
||||
return render(request, 'auth/activation_invalid.html')
|
||||
def recover_account_view(request):
|
||||
return render(request, 'auth/recover_account.html')
|
||||
def change_password_view(request):
|
||||
# Verificar si el usuario está autenticado mediante la sesión
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener el usuario desde la base de datos de `acore_auth`
|
||||
user_data = get_user_from_acore(username)
|
||||
if not user_data:
|
||||
return redirect('login')
|
||||
|
||||
user_id = user_data['id']
|
||||
email = user_data['email']
|
||||
|
||||
if request.method == 'POST':
|
||||
current_password = request.POST.get('cur-password', '').strip()
|
||||
new_password = request.POST.get('new-password', '').strip()
|
||||
conf_new_password = request.POST.get('conf-new-password', '').strip()
|
||||
security_token = request.POST.get('security-token', '').strip()
|
||||
|
||||
# Validar campos vacíos
|
||||
if not current_password or not new_password or not conf_new_password or not security_token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, complete todos los campos.</span>'})
|
||||
|
||||
# Validar que la nueva contraseña coincida con su confirmación
|
||||
if new_password != conf_new_password:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Las contraseñas no coinciden.</span>'})
|
||||
|
||||
# Validar longitud de la nueva contraseña
|
||||
if len(new_password) > 16:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña nueva no debe exceder los 16 caracteres.</span>'})
|
||||
|
||||
# Verificar si el token de seguridad ha sido generado
|
||||
existing_token = SecurityToken.objects.filter(user_id=user_id).first()
|
||||
if not existing_token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes un token de seguridad generado. Por favor, genera uno antes de cambiar tu contraseña.</span>'})
|
||||
|
||||
# Verificar si el token proporcionado es correcto
|
||||
if existing_token.token != security_token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El token ingresado es incorrecto.</span>'})
|
||||
|
||||
# Verificar la contraseña actual contra la cuenta Battle.net (por email)
|
||||
if not authenticate(email, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña actual es incorrecta.</span>'})
|
||||
|
||||
# Generar salt y verifier SRP6 v2 para la nueva contraseña (bnet)
|
||||
new_salt_bytes, new_verifier_bytes, srp_version = bnet.bnet_make_registration(email, new_password)
|
||||
|
||||
# Localizar la cuenta bnet (por sesión o por email) y actualizarla
|
||||
bnet_id = request.session.get('bnet_id')
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
if not bnet_id:
|
||||
account = bnet.get_bnet_account_by_email(cursor, email)
|
||||
bnet_id = account['id'] if account else None
|
||||
if not bnet_id:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No se ha encontrado la cuenta.</span>'})
|
||||
cursor.execute(
|
||||
"UPDATE battlenet_accounts SET srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
|
||||
[srp_version, new_salt_bytes, new_verifier_bytes, bnet_id]
|
||||
)
|
||||
|
||||
# Cerrar la sesión por seguridad
|
||||
for key in ('username', 'account_id', 'bnet_id', 'bnet_email'):
|
||||
request.session.pop(key, None)
|
||||
|
||||
# Redirigir al usuario a la página de inicio de sesión
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': '<span class="ok-form-response">Contraseña cambiada exitosamente. Has sido desconectado por seguridad.</span>',
|
||||
'redirect': True
|
||||
})
|
||||
|
||||
return render(request, 'account/change_password.html')
|
||||
def get_user_from_acore(username):
|
||||
"""
|
||||
Obtiene la información del usuario desde la base de datos `acore_auth`.
|
||||
"""
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id, email FROM account WHERE username = %s", [username])
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return {'id': result[0], 'email': result[1]}
|
||||
return None
|
||||
def security_token_view(request):
|
||||
# Verificar si el usuario está autenticado mediante la sesión
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('index')
|
||||
|
||||
# Obtener el usuario desde la base de datos de `acore_auth`
|
||||
user_data = get_user_from_acore(username)
|
||||
if not user_data:
|
||||
return redirect('index')
|
||||
|
||||
user_id = user_data['id']
|
||||
email = user_data['email']
|
||||
ip_address = request.META.get('REMOTE_ADDR')
|
||||
|
||||
# Verificar si existe un token activo para mostrar la fecha en la plantilla
|
||||
existing_token = SecurityToken.objects.filter(user_id=user_id).first()
|
||||
|
||||
# Obtener la fecha del token o "Sin solicitar" si no hay token
|
||||
token_date = "Sin solicitar"
|
||||
if existing_token:
|
||||
token_date = existing_token.created_at.strftime('%H:%M:%S %d-%m-%Y')
|
||||
|
||||
if request.method == 'POST':
|
||||
# Verificar si el correo del usuario está vacío
|
||||
if not email:
|
||||
# Si no tiene correo, devolver un mensaje de error en formato JSON
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'<span class="red-form-response">Debes añadir un correo electrónico a tu cuenta antes de solicitar un Token de seguridad.</span>'
|
||||
})
|
||||
# Verificar si ya existe un token y si se puede generar uno nuevo después de 7 días
|
||||
if existing_token:
|
||||
# Comprobar si han pasado al menos 7 días desde la creación del token
|
||||
days_since_creation = (timezone.now() - existing_token.created_at).days
|
||||
if days_since_creation < 7:
|
||||
remaining_days = 7 - days_since_creation
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'<span class="red-form-response">Sólo puedes solicitar un nuevo Token de seguridad cada 7 días, faltan {remaining_days} días para generar un nuevo Token.</span>'
|
||||
})
|
||||
|
||||
# Generar un nuevo token
|
||||
token = secrets.token_urlsafe(4)[:6]
|
||||
expires_at = timezone.now() + timedelta(days=7)
|
||||
|
||||
# Eliminar el token anterior y crear uno nuevo
|
||||
if existing_token:
|
||||
existing_token.delete()
|
||||
|
||||
# Crear y guardar el nuevo token
|
||||
new_token = SecurityToken.objects.create(
|
||||
user_id=user_id,
|
||||
token=token,
|
||||
ip_address=ip_address,
|
||||
expires_at=expires_at # Esta fecha solo se usa para el control de los 7 días
|
||||
)
|
||||
|
||||
# Enviar el correo con el token al usuario
|
||||
send_security_token_email(email, username, token, ip_address)
|
||||
|
||||
# Obtener la nueva fecha del token
|
||||
new_token_date = new_token.created_at.strftime('%H:%M:%S %d-%m-%Y')
|
||||
|
||||
# Responder con un mensaje exitoso y la nueva fecha
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'<span class="ok-form-response">Se ha enviado el Token de seguridad al correo {email}.</span>',
|
||||
'token_date': new_token_date
|
||||
})
|
||||
|
||||
# Pasar `token_date` al contexto de la plantilla
|
||||
return render(request, 'account/security_token.html', {
|
||||
'token_date': token_date
|
||||
})
|
||||
def send_security_token_email(email, username, token, ip_address):
|
||||
"""
|
||||
Envía un correo electrónico con el token de seguridad.
|
||||
"""
|
||||
context = {
|
||||
'username': username,
|
||||
'token': token,
|
||||
'ip_address': ip_address,
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
|
||||
}
|
||||
|
||||
# Llamada a tu función personalizada `enviar_correo`
|
||||
enviar_correo(
|
||||
subject=f'Token de seguridad de la cuenta {username} - {settings.NOMBRE_SERVIDOR}',
|
||||
to_email=email,
|
||||
template='emails/security_token.html', # Asegúrate de que este template exista
|
||||
context=context
|
||||
)
|
||||
def change_email_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener el usuario desde la base de datos de `acore_auth`
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, reg_mail
|
||||
FROM account
|
||||
WHERE username = %s
|
||||
""", [username])
|
||||
account_data = cursor.fetchone()
|
||||
|
||||
if not account_data:
|
||||
return redirect('login')
|
||||
|
||||
user_id, current_email, reg_email = account_data
|
||||
security_token = SecurityToken.objects.filter(user_id=user_id).first()
|
||||
|
||||
if request.method == 'POST':
|
||||
print("Datos recibidos:", request.POST)
|
||||
|
||||
current_password = request.POST.get('cur-password', '').strip()
|
||||
current_email_input = request.POST.get('cur-email', '').strip()
|
||||
new_email = request.POST.get('new-email', '').strip()
|
||||
conf_new_email = request.POST.get('conf-new-email', '').strip()
|
||||
token = request.POST.get('security-token', '').strip()
|
||||
|
||||
# Validar los campos
|
||||
if not all([current_password, current_email_input, new_email, conf_new_email, token]):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, complete todos los campos.</span>'})
|
||||
|
||||
if current_email_input != current_email:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El correo actual no es correcto.</span>'})
|
||||
|
||||
if new_email != conf_new_email:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Los correos no coinciden.</span>'})
|
||||
|
||||
if not re.match(r'^[a-zA-Z0-9._%+-]+@gmail\.com$', new_email):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El nuevo correo no es válido.</span>'})
|
||||
|
||||
if not security_token or security_token.token != token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Token de seguridad incorrecto.</span>'})
|
||||
|
||||
# Verificar la contraseña contra la cuenta Battle.net (por email actual)
|
||||
if not authenticate(current_email, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Contraseña incorrecta.</span>'})
|
||||
|
||||
# Generar hashes
|
||||
old_email_hash = secrets.token_urlsafe(16)
|
||||
new_email_hash = secrets.token_urlsafe(16)
|
||||
|
||||
# Guardar en `AccountActivation`. Se conserva la contraseña porque al
|
||||
# cambiar el email hay que recalcular el verifier de bnet (el usuario SRP
|
||||
# depende del email).
|
||||
AccountActivation.objects.create(
|
||||
username=username,
|
||||
email=new_email,
|
||||
old_email=current_email,
|
||||
password=current_password,
|
||||
recruiter_id=user_id,
|
||||
hash=new_email_hash,
|
||||
old_email_hash=old_email_hash
|
||||
)
|
||||
|
||||
# Enviar confirmación al correo actual
|
||||
confirm_old_email_link = f"{settings.URL_PRINCIPAL}/es/confirm-old-email?hash={old_email_hash}"
|
||||
context_old_email = {
|
||||
'username': username,
|
||||
'confirm_link': confirm_old_email_link,
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
|
||||
}
|
||||
enviar_correo(
|
||||
subject=f'Confirme el cambio de correo para {username}',
|
||||
to_email=current_email,
|
||||
template='emails/confirm_old_email.html',
|
||||
context=context_old_email
|
||||
)
|
||||
|
||||
return JsonResponse({'success': True, 'message': '<span class="ok-form-response">Se ha enviado un correo de confirmación al correo actual.</span>'})
|
||||
|
||||
return render(request, 'account/change_email.html')
|
||||
def confirm_old_email_view(request):
|
||||
hash = request.GET.get('hash')
|
||||
activation = AccountActivation.objects.filter(old_email_hash=hash, is_used=False).first()
|
||||
|
||||
if not activation or activation.is_expired():
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': '<span class="red-form-response">Enlace no válido o expirado.</span>',
|
||||
'redirect_url': reverse('expired-link')
|
||||
})
|
||||
|
||||
# Marcar solo el old_email_hash como usado
|
||||
activation.is_used = True
|
||||
activation.save()
|
||||
|
||||
# Enviar correo al nuevo correo para confirmar el cambio
|
||||
activation_link = f"{settings.URL_PRINCIPAL}/es/confirm-new-email?hash={activation.hash}"
|
||||
context_new_email = {
|
||||
'username': activation.username,
|
||||
'activation_link': activation_link,
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
|
||||
}
|
||||
enviar_correo(
|
||||
subject=f'Confirme su nuevo correo para {activation.username}',
|
||||
to_email=activation.email,
|
||||
template='emails/confirm_new_email.html',
|
||||
context=context_new_email
|
||||
)
|
||||
|
||||
return JsonResponse({'success': True, 'message': '<span class="ok-form-response">Correo confirmado. Se ha enviado un enlace al nuevo correo.</span>'})
|
||||
def confirm_new_email_view(request):
|
||||
hash = request.GET.get('hash')
|
||||
activation = AccountActivation.objects.filter(hash=hash, is_new_email_used=False).first()
|
||||
|
||||
if not activation or activation.is_expired():
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': '<span class="red-form-response">Enlace no válido o expirado.</span>',
|
||||
'redirect_url': reverse('expired-link')
|
||||
})
|
||||
|
||||
# Marcar solo el new_email_hash como usado
|
||||
activation.is_new_email_used = True
|
||||
activation.save()
|
||||
|
||||
# Cambiar el email en la cuenta Battle.net implica recalcular salt/verifier,
|
||||
# porque el usuario SRP = SHA256(UPPER(email)).
|
||||
new_email_norm = bnet.normalize_email(activation.email)
|
||||
old_email_norm = bnet.normalize_email(activation.old_email or '')
|
||||
new_salt, new_verifier, srp_version = bnet.bnet_make_registration(new_email_norm, activation.password)
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
# Localizar la cuenta bnet por su email actual (el antiguo)
|
||||
cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [old_email_norm])
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': '<span class="red-form-response">No se ha encontrado la cuenta Battle.net.</span>',
|
||||
})
|
||||
bnet_id = row[0]
|
||||
|
||||
# Actualizar email + verifier en battlenet_accounts
|
||||
cursor.execute(
|
||||
"UPDATE battlenet_accounts SET email = %s, srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
|
||||
[new_email_norm, srp_version, new_salt, new_verifier, bnet_id]
|
||||
)
|
||||
# Actualizar el email de las cuentas de juego enlazadas (cosmético)
|
||||
cursor.execute(
|
||||
"UPDATE account SET email = %s, reg_mail = %s WHERE battlenet_account = %s",
|
||||
[new_email_norm, new_email_norm, bnet_id]
|
||||
)
|
||||
|
||||
# Enviar notificación al correo anterior
|
||||
context_old_email = {
|
||||
'username': activation.username,
|
||||
'new_email': activation.email,
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
|
||||
}
|
||||
enviar_correo(
|
||||
subject=f'Su correo ha sido cambiado en {settings.NOMBRE_SERVIDOR}',
|
||||
to_email=activation.old_email,
|
||||
template='emails/old_email_notification.html',
|
||||
context=context_old_email
|
||||
)
|
||||
|
||||
return JsonResponse({'success': True, 'message': '<span class="ok-form-response">El cambio de correo ha sido confirmado y completado con éxito.</span>'})
|
||||
def expired_link_view(request):
|
||||
return render(request, 'account/expired_link.html')
|
||||
def log_login_attempt(username, status, ip_address):
|
||||
# Registrar intento de inicio de sesión en el historial
|
||||
LoginAttempt.objects.create(
|
||||
username=username,
|
||||
status=status,
|
||||
ip_address=ip_address,
|
||||
timestamp=timezone.now()
|
||||
)
|
||||
@@ -0,0 +1,978 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
def rename_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# 🔹 Obtener información de la cuenta desde acore_auth
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
# 🔹 Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name FROM characters WHERE account = %s
|
||||
""", [account_id])
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# 🔹 Obtener el precio del renombrado
|
||||
price_obj = RenamePrice.objects.first()
|
||||
price = price_obj.price if price_obj else 2.00
|
||||
|
||||
# 🔹 Ejecutar el comando SOAP para verificar el servidor
|
||||
command = f".server info"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
if response is None:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error de comunicación con el servidor. Intente nuevamente más tarde.</span>'})
|
||||
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para renombrar este personaje.</span>'})
|
||||
|
||||
# 🔹 Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('rename-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('rename-cancel'))
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=price,
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Renombrar personaje: {character_name}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
request.session['rename_character'] = character_name
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/rename_character.html', {'characters': characters, 'price': price})
|
||||
@require_paid_stripe()
|
||||
def rename_success_view(request):
|
||||
# Procesar el comando SOAP tras el pago exitoso
|
||||
character_name = request.session.get('rename_character')
|
||||
if not character_name:
|
||||
return redirect('rename-character')
|
||||
|
||||
command = f".char rename {character_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"El personaje {character_name} ha sido marcado para renombrar."
|
||||
request.session.pop('rename_character', None) # Limpiar la sesión
|
||||
return render(request, 'account/rename_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'account/rename_success.html', {'message': f"Error: {response}"})
|
||||
def rename_cancel_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
return render(request, 'account/rename_cancel.html')
|
||||
def restore_character_view(request):
|
||||
# Verifica si el usuario está autenticado y obtiene su nombre de usuario
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login') # Redirige si no hay sesión
|
||||
|
||||
# Obtener la ID de cuenta del usuario conectado a partir del sistema de autenticación personalizada
|
||||
try:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [username])
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
# Si no se encuentra la cuenta, mostrar un error
|
||||
messages.error(request, "No se encontró la cuenta asociada a ese usuario.")
|
||||
return redirect('login') # Redirige al login si no se encuentra la cuenta
|
||||
|
||||
account_id = result[0] # Obtén el ID de la cuenta
|
||||
except Exception as e:
|
||||
messages.error(request, f"Ocurrió un error al recuperar la cuenta: {str(e)}")
|
||||
return redirect('login')
|
||||
|
||||
# Verificar si se ha seleccionado un personaje para restaurar
|
||||
if request.method == 'POST':
|
||||
# Obtener el GUID del personaje seleccionado para restaurar
|
||||
guid = request.POST.get('guid')
|
||||
|
||||
if not guid:
|
||||
messages.error(request, "No se seleccionó un personaje para restaurar.")
|
||||
return redirect('restore-character')
|
||||
|
||||
# Aquí es donde usamos SOAP en lugar de la actualización directa
|
||||
try:
|
||||
command = f".char del restore {guid}" # Comando SOAP para restaurar el personaje
|
||||
response = execute_soap_command(command) # Ejecuta el comando SOAP
|
||||
|
||||
# Si la respuesta es exitosa, informamos al usuario
|
||||
if response == "success":
|
||||
messages.success(request, "Personaje restaurado exitosamente.")
|
||||
return redirect('my-account') # Redirigir a la cuenta del usuario
|
||||
else:
|
||||
messages.error(request, "Hubo un error al restaurar el personaje.")
|
||||
return redirect('restore-character')
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Hubo un error al intentar restaurar el personaje: {str(e)}")
|
||||
return redirect('restore-character')
|
||||
|
||||
# Realizar la consulta para obtener los personajes eliminados de la cuenta
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT guid, level, deleteInfos_Name, deleteInfos_Account, deleteDate
|
||||
FROM characters
|
||||
WHERE deleteInfos_Account = %s AND deleteDate IS NOT NULL
|
||||
""", [account_id]) # Filtra por account_id
|
||||
|
||||
# Obtener los resultados de la consulta
|
||||
deleted_characters = cursor.fetchall()
|
||||
|
||||
# Si no hay personajes eliminados
|
||||
if not deleted_characters:
|
||||
return render(request, 'account/restore_character.html', {
|
||||
'message': 'No hay personajes eliminados que puedas recuperar.'
|
||||
})
|
||||
|
||||
# Crear un listado de personajes eliminados con su nombre y nivel (si lo tienes)
|
||||
character_list = []
|
||||
for character in deleted_characters:
|
||||
guid, level, name, account_id, delete_date = character
|
||||
# Agregar la información del personaje al listado
|
||||
character_list.append({
|
||||
'name': name,
|
||||
'guid': guid,
|
||||
'level': level,
|
||||
'delete_date': delete_date
|
||||
})
|
||||
|
||||
# Renderiza la plantilla con los personajes eliminados
|
||||
return render(request, 'account/restore_character.html', {
|
||||
'character_list': character_list
|
||||
})
|
||||
def restore_items_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
characters = []
|
||||
deleted_items = []
|
||||
selected_character = None
|
||||
|
||||
try:
|
||||
# Obtener la ID de cuenta del usuario
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [username])
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
account_id = result[0]
|
||||
else:
|
||||
messages.error(request, "No se encontró la cuenta asociada.")
|
||||
return redirect('login')
|
||||
|
||||
# Obtener los personajes del usuario
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("SELECT name FROM characters WHERE account = %s", [account_id])
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error al obtener los personajes: {str(e)}")
|
||||
|
||||
if request.method == "GET" and "character_name" in request.GET:
|
||||
selected_character = request.GET.get("character_name")
|
||||
|
||||
if selected_character:
|
||||
try:
|
||||
# Ejecutar comando SOAP para obtener ítems eliminados del personaje seleccionado
|
||||
command = f".item restore list {selected_character}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
# Procesar la respuesta del servidor
|
||||
for line in response.split("\n"):
|
||||
match = re.search(r"Recover id: (\d+) \| Item: (.+) \((\d+)\) \| Count: (\d+)", line)
|
||||
if match:
|
||||
recover_id = match.group(1)
|
||||
item_name = match.group(2)
|
||||
item_id = match.group(3)
|
||||
count = match.group(4)
|
||||
deleted_items.append({
|
||||
"recover_id": recover_id,
|
||||
"item_name": item_name,
|
||||
"item_id": item_id,
|
||||
"count": count
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error al obtener los ítems eliminados: {str(e)}")
|
||||
|
||||
if request.method == "POST":
|
||||
recover_id = request.POST.get("recover_id")
|
||||
selected_character = request.POST.get("character_name")
|
||||
|
||||
if not recover_id or not selected_character:
|
||||
messages.error(request, "Selecciona un personaje y un ítem para restaurar.")
|
||||
return redirect('restore-items')
|
||||
|
||||
try:
|
||||
# Ejecutar comando SOAP para restaurar el ítem
|
||||
command = f".item restore {recover_id} {selected_character}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if "success" in response.lower():
|
||||
messages.success(request, "Ítem restaurado exitosamente.")
|
||||
else:
|
||||
messages.error(request, "No se pudo restaurar el ítem.")
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error al restaurar el ítem: {str(e)}")
|
||||
|
||||
return redirect('restore-items')
|
||||
|
||||
return render(request, 'account/restore_items.html', {
|
||||
'characters': characters,
|
||||
'deleted_items': deleted_items,
|
||||
'selected_character': selected_character
|
||||
})
|
||||
def quest_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
return render(request, 'account/quest_character.html')
|
||||
def send_gift_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
return render(request, 'account/send_gift.html')
|
||||
def customize_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# ?? Obtener información de la cuenta desde acore_auth
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
# ?? Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name FROM characters WHERE account = %s
|
||||
""", [account_id])
|
||||
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Obtener el precio de la personalización (si se gestiona desde el admin)
|
||||
price_obj = CustomizePrice.objects.first()
|
||||
price = price_obj.price if price_obj else 1.00
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
# Validar si el personaje fue seleccionado
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para personalizar este personaje.</span>'})
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('customize-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('customize-cancel'))
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=price,
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Personalizar personaje: {character_name}"
|
||||
)
|
||||
|
||||
|
||||
if stripe_response["success"]:
|
||||
# Guardar temporalmente el nombre del personaje en la sesión
|
||||
request.session['customize_character'] = character_name
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/customize_character.html', {'characters': characters, 'price': price})
|
||||
@require_paid_stripe()
|
||||
def customize_success_view(request):
|
||||
# Procesar el comando SOAP tras el pago exitoso
|
||||
character_name = request.session.get('customize_character')
|
||||
if not character_name:
|
||||
return redirect('customize-character')
|
||||
|
||||
command = f".char customi {character_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"El personaje {character_name} ha sido marcado para personalización."
|
||||
request.session.pop('customize_character', None) # Limpiar la sesión
|
||||
return render(request, 'account/customize_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'account/customize_success.html', {'message': f"Error: {response}"})
|
||||
def customize_cancel_view(request):
|
||||
return render(request, 'account/customize_cancel.html')
|
||||
def change_race_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
# ?? Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name FROM characters WHERE account = %s
|
||||
""", [account_id])
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Obtener el precio del cambio de raza
|
||||
price_obj = ChangeRacePrice.objects.first()
|
||||
price = price_obj.price if price_obj else 10.00 # Precio predeterminado si no hay registro
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
# Validar si el personaje fue seleccionado
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para cambiar la raza de este personaje.</span>'})
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('change-race-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('change-race-cancel'))
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=price,
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Cambio de Raza del personaje: {character_name}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
# Guardar el personaje en la sesión para el cambio
|
||||
request.session['change_race_character'] = character_name
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/change_race.html', {'characters': characters, 'price': price})
|
||||
@require_paid_stripe()
|
||||
def change_race_success_view(request):
|
||||
# Procesar el comando SOAP tras el pago exitoso
|
||||
character_name = request.session.get('change_race_character')
|
||||
if not character_name:
|
||||
return redirect('change-race')
|
||||
|
||||
command = f".char changerace {character_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"El personaje {character_name} ha sido marcado para cambio de raza."
|
||||
request.session.pop('change_race_character', None) # Limpiar la sesión
|
||||
return render(request, 'account/change_race_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'account/change_race_success.html', {'message': f"Error: {response}"})
|
||||
def change_race_cancel_view(request):
|
||||
return render(request, 'account/change_race_cancel.html')
|
||||
def change_faction_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
# ?? Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name FROM characters WHERE account = %s
|
||||
""", [account_id])
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Obtener el precio del cambio de facción
|
||||
price_obj = ChangeFactionPrice.objects.first()
|
||||
price = price_obj.price if price_obj else 10.00
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
# Validar si el personaje fue seleccionado
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para cambiar la facción de este personaje.</span>'})
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('change-faction-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('change-faction-cancel'))
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=price,
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Cambiar Faccion del personaje: {character_name}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
# Guardar el personaje seleccionado en la sesión
|
||||
request.session['change_faction_character'] = character_name
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/change_faction.html', {'characters': characters, 'price': price})
|
||||
@require_paid_stripe()
|
||||
def change_faction_success_view(request):
|
||||
# Procesar el comando SOAP tras el pago exitoso
|
||||
character_name = request.session.get('change_faction_character')
|
||||
if not character_name:
|
||||
return redirect('change-faction-character')
|
||||
|
||||
command = f".char changef {character_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"El personaje {character_name} ha cambiado de facción con éxito."
|
||||
request.session.pop('change_faction_character', None) # Limpiar la sesión
|
||||
return render(request, 'account/change_faction_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'account/change_faction_success.html', {'message': f"Error: {response}"})
|
||||
def change_faction_cancel_view(request):
|
||||
return render(request, 'account/change_faction_cancel.html')
|
||||
def level_up_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
# ?? Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name FROM characters WHERE account = %s
|
||||
""", [account_id])
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Obtener el precio de subir a nivel 80
|
||||
price_obj = LevelUpPrice.objects.first()
|
||||
price = price_obj.price if price_obj else 10.00
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
# Validar si el personaje fue seleccionado
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para subir de nivel a este personaje.</span>'})
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('level-up-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('level-up-cancel'))
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=price,
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Subir el Nivel 80 del personaje: {character_name}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
# Guardar el personaje en la sesión para usarlo después del pago
|
||||
request.session['levelup_character'] = character_name
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/level_up.html', {'characters': characters, 'price': price})
|
||||
@require_paid_stripe()
|
||||
def level_up_success_view(request):
|
||||
character_name = request.session.get('levelup_character')
|
||||
if not character_name:
|
||||
return redirect('level-up-character')
|
||||
|
||||
# Ejecutar el comando SOAP
|
||||
command = f".char level {character_name} 80"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"El personaje {character_name} ha sido subido al nivel 80."
|
||||
else:
|
||||
message = f"Error: {response}"
|
||||
|
||||
# Limpiar la sesión
|
||||
request.session.pop('levelup_character', None)
|
||||
|
||||
return render(request, 'account/level_up_success.html', {'message': message})
|
||||
def level_up_cancel_view(request):
|
||||
return render(request, 'account/level_up_cancel.html')
|
||||
def gold_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener personajes y estado de conexión del usuario
|
||||
try:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name, online, money FROM characters WHERE account = %s
|
||||
""", [account_id])
|
||||
characters_data = cursor.fetchall()
|
||||
|
||||
characters = {row[0]: {'online': row[1], 'money': row[2]} for row in characters_data}
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al obtener los personajes: {str(e)}</span>'})
|
||||
|
||||
# Obtener precios del oro
|
||||
gold_prices = GoldPrice.objects.all().order_by('gold_amount')
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
gold_amount = request.POST.get('gold_amount')
|
||||
|
||||
# Validaciones iniciales
|
||||
if not character_name or not gold_amount:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Seleccione un personaje y una cantidad de oro.</span>'})
|
||||
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para enviar oro a este personaje.</span>'})
|
||||
|
||||
if characters[character_name]['online'] == 1:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El personaje debe estar desconectado para recibir oro.</span>'})
|
||||
|
||||
try:
|
||||
gold_amount = int(gold_amount)
|
||||
except ValueError:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Cantidad de oro no válida.</span>'})
|
||||
|
||||
gold_price = GoldPrice.objects.filter(gold_amount=gold_amount).first()
|
||||
if not gold_price:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Cantidad de oro no válida.</span>'})
|
||||
|
||||
# El precio va en euros (con decimales); el helper lo pasa a céntimos (×100)
|
||||
stripe_price = gold_price.price
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('gold-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('gold-cancel'))
|
||||
|
||||
# Llamada corregida a create_checkout_session con todos los parámetros requeridos
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=stripe_price, # El precio calculado
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Aumentar el Oro: {gold_amount} al Personaje: {character_name}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
# Guardar en sesión los datos para el callback tras el pago
|
||||
request.session['gold_transaction'] = {
|
||||
'character_name': character_name,
|
||||
'gold_amount': gold_amount
|
||||
}
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/gold_character.html', {'characters': characters, 'gold_prices': gold_prices})
|
||||
@require_paid_stripe()
|
||||
def gold_success_view(request):
|
||||
# Procesar el pago exitoso
|
||||
transaction = request.session.pop('gold_transaction', None)
|
||||
if not transaction:
|
||||
return redirect('gold-character')
|
||||
|
||||
character_name = transaction['character_name']
|
||||
gold_amount = int(transaction['gold_amount'])
|
||||
|
||||
# Calcular el oro en formato del juego
|
||||
game_gold_amount = gold_amount * 10000
|
||||
|
||||
# Actualizar la cantidad de oro en la base de datos
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
UPDATE characters
|
||||
SET money = money + %s
|
||||
WHERE name = %s
|
||||
""", [game_gold_amount, character_name])
|
||||
|
||||
return render(request, 'account/gold_success.html', {'character_name': character_name, 'gold_amount': gold_amount})
|
||||
def gold_cancel_view(request):
|
||||
return render(request, 'account/gold_cancel.html')
|
||||
def transfer_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener el precio de transferencia
|
||||
price_obj = TransferPrice.objects.first()
|
||||
price = price_obj.price if price_obj else 10.00
|
||||
|
||||
# Obtener el ID de usuario desde acore_auth.account
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id, email, last_ip FROM account WHERE username = %s", [username])
|
||||
user_data = cursor.fetchone()
|
||||
if not user_data:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Cuenta no encontrada. Por favor, inicie sesión nuevamente.</span>'})
|
||||
|
||||
user_id, email, acore_ip = user_data # ID de la cuenta, email y la última IP
|
||||
|
||||
# Obtener personajes de la cuenta de origen
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name, class, online
|
||||
FROM characters
|
||||
WHERE account = %s
|
||||
""", [user_id])
|
||||
characters = cursor.fetchall()
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
destination_account = request.POST.get('destination_account')
|
||||
token = request.POST.get('security_token')
|
||||
|
||||
# Validaciones
|
||||
if not all([character_name, destination_account, token]):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, complete todos los campos.</span>'})
|
||||
|
||||
character = next((char for char in characters if char[0] == character_name), None)
|
||||
if not character:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para transferir este personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje está desconectado
|
||||
if character[2] == 1: # Si está conectado
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El personaje debe estar desconectado para ser transferido.</span>'})
|
||||
|
||||
# Verificar el token de seguridad
|
||||
security_token = SecurityToken.objects.filter(user_id=user_id, token=token).first()
|
||||
if not security_token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Token de seguridad incorrecto.</span>'})
|
||||
|
||||
# Verificar si el token ha expirado
|
||||
if security_token.expires_at < timezone.now():
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El token ha expirado. Solicite uno nuevo.</span>'})
|
||||
|
||||
# Verificar si la cuenta de destino existe
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [destination_account])
|
||||
destination_account_data = cursor.fetchone()
|
||||
|
||||
if not destination_account_data:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La cuenta de destino no existe.</span>'})
|
||||
|
||||
# Verificar restricciones en la cuenta de destino
|
||||
destination_account_id = destination_account_data[0]
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT account, name, class, level, online
|
||||
FROM characters
|
||||
WHERE account = %s
|
||||
""", [destination_account_id])
|
||||
destination_characters = cursor.fetchall()
|
||||
|
||||
if not any(char[3] >= 55 and char[2] != 6 for char in destination_characters):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La cuenta destino debe tener al menos un personaje de nivel 55 o superior que no sea Caballero de la Muerte.</span>'})
|
||||
|
||||
if any(char[2] == 6 for char in destination_characters):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La cuenta destino no puede tener un Caballero de la Muerte.</span>'})
|
||||
|
||||
# Verificar que ninguna cuenta esté online
|
||||
for char in destination_characters:
|
||||
if char[4] == 1:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Todos los personajes de la cuenta destino deben estar desconectados.</span>'})
|
||||
for char in characters:
|
||||
if char[2] == 1:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Todos los personajes de la cuenta origen deben estar desconectados.</span>'})
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('transfer-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('transfer-cancel'))
|
||||
|
||||
# Llamada corregida a create_checkout_session con todos los parámetros requeridos
|
||||
stripe_response = create_checkout_session(
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
account_id=user_id, # Pasar el account_id
|
||||
amount=price, # El precio de la transferencia
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Transferir el personaje: {character_name} a la cuenta de destino: {destination_account}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
# Guardar los datos de la transferencia en la sesión para usarlos después del pago
|
||||
request.session['transfer_data'] = {
|
||||
'character_name': character_name,
|
||||
'destination_account': destination_account
|
||||
}
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error al iniciar el pago: {stripe_response["error"]}</span>'})
|
||||
|
||||
return render(request, 'account/transfer_character.html', {'characters': characters, 'price': price})
|
||||
@require_paid_stripe()
|
||||
def transfer_success_view(request):
|
||||
transfer_data = request.session.get('transfer_data')
|
||||
if not transfer_data:
|
||||
return redirect('transfer-character')
|
||||
|
||||
character_name = transfer_data['character_name']
|
||||
destination_account = transfer_data['destination_account']
|
||||
|
||||
# Ejecutar comando SOAP
|
||||
command = f".char changeaccount {destination_account} {character_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"El personaje {character_name} ha sido transferido a la cuenta {destination_account}."
|
||||
request.session.pop('transfer_data', None) # Limpiar sesión
|
||||
return render(request, 'account/transfer_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'account/transfer_success.html', {'message': f"Error: {response}"})
|
||||
def transfer_cancel_view(request):
|
||||
return render(request, 'account/transfer_cancel.html')
|
||||
def revive_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name, online
|
||||
FROM characters
|
||||
WHERE account = (
|
||||
SELECT id
|
||||
FROM acore_auth.account
|
||||
WHERE username = %s
|
||||
)
|
||||
""", [username])
|
||||
characters_data = cursor.fetchall()
|
||||
characters = {row[0]: row[1] for row in characters_data} # Diccionario con nombre y estado de conexión
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
# Validar si el personaje fue seleccionado
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para revivir este personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje está desconectado
|
||||
if characters[character_name] == 1: # Si está conectado
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El personaje debe estar desconectado para ser revivido.</span>'})
|
||||
|
||||
# Verificar el historial de uso del comando `revive`
|
||||
last_revive = ReviveHistory.objects.filter(character_name=character_name).order_by('-used_at').first()
|
||||
if last_revive and last_revive.used_at > now() - timedelta(hours=12):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El personaje solo puede revivirse una vez cada 12 horas.</span>'})
|
||||
|
||||
# Ejecutar el comando SOAP
|
||||
command = f".revive {character_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
# None = fallo de comunicación con el servidor
|
||||
if response is None:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error de comunicación con el servidor. Intente nuevamente más tarde.</span>'})
|
||||
|
||||
# Respuesta vacía = éxito (el comando .revive no devuelve texto si va bien)
|
||||
if response.strip() == "":
|
||||
ReviveHistory.objects.create(character_name=character_name, used_at=now())
|
||||
return JsonResponse({'success': True, 'message': f'<span class="ok-form-response">El personaje {character_name} ha sido revivido con éxito.</span>'})
|
||||
|
||||
# Respuesta no vacía = el servidor devolvió un mensaje/error
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">{response}</span>'})
|
||||
|
||||
return render(request, 'account/revive_character.html', {'characters': characters.keys()})
|
||||
def unstuck_character_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos `acore_characters`
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name, online
|
||||
FROM characters
|
||||
WHERE account = (
|
||||
SELECT id
|
||||
FROM acore_auth.account
|
||||
WHERE username = %s
|
||||
)
|
||||
""", [username])
|
||||
characters_data = cursor.fetchall()
|
||||
characters = {row[0]: row[1] for row in characters_data} # Diccionario con nombre y estado de conexión
|
||||
|
||||
if request.method == 'POST':
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
# Validar si el personaje fue seleccionado
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, seleccione un personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes permiso para desbloquear este personaje.</span>'})
|
||||
|
||||
# Verificar si el personaje está desconectado
|
||||
if characters[character_name] == 1: # Si está conectado
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El personaje debe estar desconectado para ser desbloqueado.</span>'})
|
||||
|
||||
# Verificar el historial de uso del comando
|
||||
last_unstuck = UnstuckHistory.objects.filter(character_name=character_name).order_by('-used_at').first()
|
||||
if last_unstuck and last_unstuck.used_at > now() - timedelta(hours=12):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El personaje solo puede desbloquearse una vez cada 12 horas.</span>'})
|
||||
|
||||
# Ejecutar el comando SOAP
|
||||
command = f".unstuck {character_name} startzone"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response is None:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error de comunicación con el servidor. Intente nuevamente más tarde.</span>'})
|
||||
|
||||
# Analizar la respuesta
|
||||
if f"You are summoning {character_name} (offline)" in response:
|
||||
# Registrar el uso del comando
|
||||
UnstuckHistory.objects.create(character_name=character_name, used_at=now())
|
||||
return JsonResponse({'success': True, 'message': f'<span class="ok-form-response">El personaje {character_name} ha sido desbloqueado con éxito.</span>'})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'<span class="red-form-response">Error: {response}</span>'})
|
||||
|
||||
return render(request, 'account/unstuck_character.html', {'characters': characters.keys()})
|
||||
@@ -0,0 +1,126 @@
|
||||
from ._base import *
|
||||
from .auth import authenticate, get_user_from_acore
|
||||
|
||||
|
||||
def rename_guild_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
user_data = get_user_from_acore(username)
|
||||
if not user_data:
|
||||
return redirect('login')
|
||||
|
||||
user_id = user_data['id']
|
||||
email = user_data['email']
|
||||
|
||||
security_token = SecurityToken.objects.filter(user_id=user_id).first()
|
||||
guild_settings, created = GuildRenameSettings.objects.get_or_create(defaults={'cost': 10})
|
||||
rename_cost = guild_settings.cost
|
||||
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT g.guildid, g.name, gm.rank, c.name
|
||||
FROM guild g
|
||||
JOIN guild_member gm ON g.guildid = gm.guildid
|
||||
JOIN characters c ON c.guid = gm.guid
|
||||
WHERE c.account = %s AND gm.rank = 0
|
||||
""", [user_id])
|
||||
guild_leader_data = cursor.fetchall()
|
||||
|
||||
if not guild_leader_data:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: No tienes personajes que sean Maestros de Hermandad en tu cuenta.</span>'})
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, email, last_ip FROM account WHERE username = %s
|
||||
""", [username])
|
||||
account_info = cursor.fetchone()
|
||||
|
||||
if not account_info:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Error: Cuenta no encontrada.</span>'})
|
||||
|
||||
account_id, email, acore_ip = account_info
|
||||
client_ip = request.META.get('REMOTE_ADDR', '')
|
||||
|
||||
if request.method == 'POST':
|
||||
old_guild_name = request.POST.get('old-guild-name', '').strip()
|
||||
new_guild_name = request.POST.get('new-guild-name', '').strip()
|
||||
conf_new_guild_name = request.POST.get('conf-new-guild-name', '').strip()
|
||||
current_password = request.POST.get('cur-password', '').strip()
|
||||
token = request.POST.get('security-token', '').strip()
|
||||
|
||||
if not old_guild_name or not new_guild_name or not conf_new_guild_name or not current_password or not token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, completa todos los campos.</span>'})
|
||||
|
||||
if not authenticate(email, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña actual es incorrecta.</span>'})
|
||||
|
||||
if not security_token or security_token.token != token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Token de seguridad incorrecto.</span>'})
|
||||
|
||||
if new_guild_name != conf_new_guild_name:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Los nombres nuevos no coinciden.</span>'})
|
||||
|
||||
success_url = request.build_absolute_uri(reverse('guild-success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('guild-cancel'))
|
||||
|
||||
stripe_response = create_checkout_session(
|
||||
account_id=user_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
amount=rename_cost,
|
||||
character_name=old_guild_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Renombrar hermandad: {old_guild_name} → {new_guild_name}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
request.session['guild_rename'] = {
|
||||
'old_name': old_guild_name,
|
||||
'new_name': new_guild_name
|
||||
}
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'Error al iniciar el pago: {stripe_response["error"]}'})
|
||||
|
||||
return render(request, 'account/rename_guild.html', {
|
||||
'guild_leader_data': guild_leader_data,
|
||||
'rename_cost': rename_cost
|
||||
})
|
||||
@require_paid_stripe()
|
||||
def guild_rename_success_view(request):
|
||||
# Obtener los datos de la sesión
|
||||
guild_rename_data = request.session.get('guild_rename')
|
||||
if not guild_rename_data:
|
||||
return redirect('rename-cancel') # Redirigir si no hay datos de renombrado
|
||||
|
||||
old_guild_name = guild_rename_data.get('old_name')
|
||||
new_guild_name = guild_rename_data.get('new_name')
|
||||
|
||||
if not old_guild_name or not new_guild_name:
|
||||
return redirect('rename-cancel') # Redirigir si faltan datos
|
||||
|
||||
# Ejecutar el comando SOAP
|
||||
command = f".guild rename {old_guild_name} {new_guild_name}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
# Limpiar la sesión después de la ejecución
|
||||
request.session.pop('guild_rename', None)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"La Hermandad '{old_guild_name}' ha cambiado su nombre a '{new_guild_name}'. Cierra sesión y vuelve a entrar para que los cambios se reflejen en el juego."
|
||||
return render(request, 'account/guild_rename_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'account/guild_rename_success.html', {'message': f"Error: {response}"})
|
||||
def guild_rename_cancel_view(request):
|
||||
"""
|
||||
Vista para la página de intercambio de puntos.
|
||||
"""
|
||||
return render(request, 'account/guild_rename_cancel.html')
|
||||
@@ -0,0 +1,71 @@
|
||||
from ._base import *
|
||||
from .account import get_account_id
|
||||
|
||||
|
||||
def format_datetime(timestamp):
|
||||
if timestamp:
|
||||
return datetime.utcfromtimestamp(timestamp).strftime('%d-%m-%Y %H:%M:%S')
|
||||
return "No disponible"
|
||||
|
||||
|
||||
def get_ban_history(account_id):
|
||||
# Consulta de baneos de la cuenta
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT bandate, unbandate, banreason, active, account.username
|
||||
FROM account_banned
|
||||
JOIN account ON account_banned.id = account.id
|
||||
WHERE account_banned.id = %s
|
||||
""", [account_id])
|
||||
account_bans = cursor.fetchall()
|
||||
|
||||
# Formatear las fechas
|
||||
account_bans = [(format_datetime(ban[0]), format_datetime(ban[1]), ban[2], ban[3], ban[4]) for ban in account_bans]
|
||||
|
||||
# Consulta de baneos de los personajes asociados a la cuenta
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT bandate, unbandate, banreason, active, characters.name
|
||||
FROM character_banned
|
||||
JOIN characters ON character_banned.guid = characters.guid
|
||||
WHERE characters.account = %s
|
||||
""", [account_id])
|
||||
character_bans = cursor.fetchall()
|
||||
|
||||
# Formatear las fechas
|
||||
character_bans = [(format_datetime(ban[0]), format_datetime(ban[1]), ban[2], ban[3], ban[4]) for ban in character_bans]
|
||||
|
||||
return account_bans, character_bans
|
||||
def calculate_unmute_date(mute_date, mute_duration):
|
||||
if isinstance(mute_date, int):
|
||||
mute_date = datetime.fromtimestamp(mute_date)
|
||||
return mute_date + timedelta(seconds=mute_duration)
|
||||
def ban_history_view(request):
|
||||
# Verificar si el usuario tiene un `username` en la sesión
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener el account_id a partir del username
|
||||
account_id = get_account_id(username)
|
||||
if not account_id:
|
||||
return redirect('login') # O redirigir a otra página si no se encuentra el account_id
|
||||
|
||||
print(f"Account ID: {account_id}") # Depurar el account_id
|
||||
|
||||
account_bans, character_bans = get_ban_history(account_id)
|
||||
|
||||
return render(request, 'account/ban_history.html', {
|
||||
'account_bans': account_bans,
|
||||
'character_bans': character_bans,
|
||||
'now': datetime.now(),
|
||||
})
|
||||
def security_history_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login') # Si no está autenticado, redirigir al login
|
||||
|
||||
# Obtener los últimos 20 intentos de inicio de sesión
|
||||
login_attempts = LoginAttempt.objects.filter(username=username).order_by('-timestamp')[:20]
|
||||
|
||||
return render(request, 'account/security_history.html', {'login_attempts': login_attempts})
|
||||
@@ -0,0 +1,86 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
def get_online_characters_count():
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("SELECT COUNT(*) FROM characters WHERE online = 1")
|
||||
result = cursor.fetchone()
|
||||
return result[0] if result else 0
|
||||
def check_server_status(host, port):
|
||||
if not host or host == "N/A":
|
||||
return False
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(1)
|
||||
try:
|
||||
s.connect((host, port))
|
||||
s.close()
|
||||
return True
|
||||
except (socket.timeout, ConnectionRefusedError, socket.gaierror):
|
||||
return False
|
||||
def home_view(request):
|
||||
noticias = Noticia.objects.order_by('-fecha_publicacion')[:50]
|
||||
online_characters = get_online_characters_count()
|
||||
server_selection = ServerSelection.objects.first()
|
||||
status = check_server_status(server_selection.address, server_selection.port) if server_selection else None
|
||||
game_version = 'WotLK' if server_selection and server_selection.gamebuild == 12340 else 'Cataclysm'
|
||||
|
||||
context = {
|
||||
'noticias': noticias,
|
||||
'server': server_selection,
|
||||
'status': 'Online' if status else 'Offline',
|
||||
'expansion': game_version,
|
||||
'online_characters': online_characters,
|
||||
}
|
||||
return render(request, 'home/home.html', context)
|
||||
def download_client_view(request):
|
||||
download_content = DownloadClientPage.objects.first()
|
||||
categorias = ClienteCategoria.objects.all()
|
||||
return render(request, 'download/download_client.html', {
|
||||
'download_content': download_content,
|
||||
'categorias': categorias,
|
||||
})
|
||||
def download_addons_view(request):
|
||||
return redirect("https://foro.novawow.com/files/")
|
||||
def content_creators_view(request):
|
||||
content_creator = ContentCreator.objects.first()
|
||||
return render(request, 'community/content_creators.html', {'content_creator': content_creator})
|
||||
def server_status_view(request):
|
||||
server_selection = ServerSelection.objects.first()
|
||||
if server_selection:
|
||||
status = check_server_status(server_selection.address, server_selection.port)
|
||||
game_version = 'WotLK' if server_selection.gamebuild == 12340 else 'Cataclysm'
|
||||
else:
|
||||
status = None
|
||||
game_version = None
|
||||
|
||||
return render(request, 'server_status/server_status.html', {
|
||||
'server': server_selection,
|
||||
'status': 'Online' if status else 'Offline',
|
||||
'game_version': game_version,
|
||||
})
|
||||
def novawow_realm_view(request):
|
||||
return render(request, 'realms/novawow_realm.html')
|
||||
def contact_us_view(request):
|
||||
return render(request, 'contact/contact_us.html')
|
||||
def legal_notice_view(request):
|
||||
return render(request, 'legal/legal_notice.html')
|
||||
def maintenance_view(request):
|
||||
return render(request, 'maintenance/maintenance.html')
|
||||
def not_found(request, exception=None):
|
||||
return render(request, '404.html', status=404)
|
||||
def terms_and_conditions_view(request):
|
||||
return render(request, 'legal/terms_and_conditions.html')
|
||||
def privacy_policy_view(request):
|
||||
return render(request, 'legal/privacy_policy.html')
|
||||
def refund_policy_view(request):
|
||||
return render(request, 'legal/refund_policy.html')
|
||||
def cookies_view(request):
|
||||
return render(request, 'legal/cookies.html')
|
||||
def help_view(request):
|
||||
# Verificar si el usuario está logueado
|
||||
if 'username' not in request.session:
|
||||
# Si no está logueado, redirigir al inicio de sesión
|
||||
return redirect('index')
|
||||
|
||||
# Si está logueado, renderizar la página de ayuda
|
||||
return render(request, 'community/help.html')
|
||||
@@ -0,0 +1,132 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def stripe_webhook(request):
|
||||
payload = request.body
|
||||
sig_header = request.META.get("HTTP_STRIPE_SIGNATURE")
|
||||
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
|
||||
|
||||
if event["type"] == "checkout.session.completed":
|
||||
session = event["data"]["object"]
|
||||
session_id = session["id"]
|
||||
stripe_ip = request.META.get("REMOTE_ADDR") # IP detectada
|
||||
|
||||
# 🔹 Actualizar la IP de Stripe en el log
|
||||
StripeLog.objects.filter(session_id=session_id).update(stripe_ip=stripe_ip, timestamp=now())
|
||||
|
||||
return JsonResponse({"success": True})
|
||||
|
||||
except stripe.error.SignatureVerificationError:
|
||||
return JsonResponse({"success": False, "message": "Firma de webhook no válida."}, status=400)
|
||||
def get_pending_battlepay_orders(account_id):
|
||||
"""Órdenes Battlepay PENDING de una cuenta de juego."""
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT id, reference, product_id, product_name, price_eur, status, created_at "
|
||||
"FROM battlepay_orders WHERE account_id = %s AND status = 'PENDING' "
|
||||
"ORDER BY created_at DESC",
|
||||
[account_id],
|
||||
)
|
||||
cols = [c[0] for c in cursor.description]
|
||||
return [dict(zip(cols, row)) for row in cursor.fetchall()]
|
||||
def _mark_order_paid_by_reference(reference):
|
||||
"""Marca una orden como PAID solo si estaba PENDING. Devuelve filas afectadas."""
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute(
|
||||
"UPDATE battlepay_orders SET status = 'PAID', paid_at = UNIX_TIMESTAMP() "
|
||||
"WHERE reference = %s AND status = 'PENDING'",
|
||||
[reference],
|
||||
)
|
||||
return cursor.rowcount
|
||||
def battlepay_view(request):
|
||||
"""Lista las órdenes Battlepay pendientes de la cuenta de juego seleccionada."""
|
||||
account_id = request.session.get('account_id')
|
||||
if not account_id:
|
||||
if request.session.get('bnet_id'):
|
||||
return redirect('select-account')
|
||||
return redirect('login')
|
||||
|
||||
orders = get_pending_battlepay_orders(account_id)
|
||||
return render(request, 'account/battlepay.html', {
|
||||
'orders': orders,
|
||||
'username': request.session.get('username'),
|
||||
})
|
||||
def battlepay_pay_view(request):
|
||||
"""Crea un checkout de SumUp para pagar una orden Battlepay pendiente."""
|
||||
account_id = request.session.get('account_id')
|
||||
if not account_id:
|
||||
return redirect('login')
|
||||
if request.method != 'POST':
|
||||
return redirect('battlepay')
|
||||
|
||||
reference = request.POST.get('reference', '').strip()
|
||||
|
||||
# Validar que la orden pertenece a la cuenta y sigue pendiente
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT product_name, price_eur FROM battlepay_orders "
|
||||
"WHERE reference = %s AND account_id = %s AND status = 'PENDING'",
|
||||
[reference, account_id],
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return render(request, 'account/pago_sumup.html', {'error': 'Orden no encontrada o ya procesada.'})
|
||||
|
||||
product_name, price_eur = row
|
||||
try:
|
||||
checkout = crear_checkout_battlepay(reference, price_eur, product_name)
|
||||
except Exception as e:
|
||||
return render(request, 'account/pago_sumup.html', {'error': str(e)})
|
||||
|
||||
return render(request, 'account/pago_sumup.html', {
|
||||
'checkout_id': checkout.get('id'),
|
||||
'reference': reference,
|
||||
'product_name': product_name,
|
||||
'price_eur': price_eur,
|
||||
})
|
||||
def pagar_sumup(request):
|
||||
"""Alias de compatibilidad: envía al listado de órdenes Battlepay."""
|
||||
return redirect('battlepay')
|
||||
@csrf_exempt
|
||||
def sumup_webhook(request):
|
||||
"""Webhook de SumUp: marca la orden Battlepay como PAID al completarse el pago."""
|
||||
if request.method != "POST":
|
||||
return JsonResponse({"error": "Método no permitido"}, status=405)
|
||||
|
||||
try:
|
||||
data = json.loads(request.body or b'{}')
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({"error": "JSON inválido"}, status=400)
|
||||
|
||||
payload = data.get('payload') or data.get('data') or {}
|
||||
reference = data.get('checkout_reference') or payload.get('checkout_reference')
|
||||
status = (data.get('status') or payload.get('status') or '').upper()
|
||||
checkout_id = data.get('id') or payload.get('checkout_id') or payload.get('id')
|
||||
event_type = (data.get('event_type') or '').lower()
|
||||
|
||||
# Si falta la referencia o el estado, consultarlo a la API de SumUp
|
||||
if (not reference or status not in ('PAID', 'SUCCESSFUL')) and checkout_id:
|
||||
try:
|
||||
checkout = obtener_checkout(checkout_id)
|
||||
reference = reference or checkout.get('checkout_reference')
|
||||
status = (checkout.get('status') or status).upper()
|
||||
except Exception as e:
|
||||
logger.error("SumUp webhook: no se pudo consultar el checkout %s: %s", checkout_id, e)
|
||||
|
||||
pagado = status in ('PAID', 'SUCCESSFUL') or event_type == 'transaction.successful'
|
||||
|
||||
if reference and pagado:
|
||||
try:
|
||||
afectadas = _mark_order_paid_by_reference(reference)
|
||||
except Exception as e:
|
||||
logger.error("SumUp webhook: error actualizando la orden %s: %s", reference, e)
|
||||
return JsonResponse({"error": "Error interno"}, status=500)
|
||||
if afectadas:
|
||||
return JsonResponse({"message": "Orden marcada como pagada"}, status=200)
|
||||
return JsonResponse({"message": "Orden no pendiente o inexistente"}, status=200)
|
||||
|
||||
return JsonResponse({"message": "Evento ignorado"}, status=200)
|
||||
@@ -0,0 +1,228 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
def get_race_icon(race):
|
||||
race_icons = {
|
||||
1: "human-male",
|
||||
2: "orc-male",
|
||||
3: "dwarf-male",
|
||||
4: "night-elf-male",
|
||||
5: "undead-male",
|
||||
6: "tauren-male",
|
||||
7: "gnome-male",
|
||||
8: "troll-male",
|
||||
9: "goblin-male",
|
||||
10: "blood-elf-male",
|
||||
11: "draenei-male"
|
||||
}
|
||||
return race_icons.get(race, "unknown")
|
||||
def get_race_iconss(race):
|
||||
race_iconss = {
|
||||
1: "human",
|
||||
2: "orc",
|
||||
3: "dwarf",
|
||||
4: "night-elf",
|
||||
5: "undead",
|
||||
6: "tauren",
|
||||
7: "gnome",
|
||||
8: "troll",
|
||||
9: "goblin",
|
||||
10: "blood-elf",
|
||||
11: "draenei"
|
||||
}
|
||||
return race_iconss.get(race, "unknown")
|
||||
def get_class_icon(char_class):
|
||||
class_icons = {
|
||||
1: "warrior",
|
||||
2: "paladin",
|
||||
3: "hunter",
|
||||
4: "rogue",
|
||||
5: "priest",
|
||||
6: "death-knight",
|
||||
7: "shaman",
|
||||
8: "mage",
|
||||
9: "warlock",
|
||||
11: "druid"
|
||||
}
|
||||
return class_icons.get(char_class, "unknown")
|
||||
def get_class_name(char_class):
|
||||
names = {
|
||||
1: "Guerrero",
|
||||
2: "Paladín",
|
||||
3: "Cazador",
|
||||
4: "Pícaro",
|
||||
5: "Sacerdote",
|
||||
6: "Caballero de la Muerte",
|
||||
7: "Chamán",
|
||||
8: "Mago",
|
||||
9: "Brujo",
|
||||
11: "Druida"
|
||||
}
|
||||
return names.get(char_class, "Desconocido")
|
||||
def get_achievement_name(achievement_id):
|
||||
achievement_names = {
|
||||
457: "¡Primero del reino! Nivel 80",
|
||||
458: "¡Primero del reino! Pícaro nivel 80",
|
||||
463: "¡Primero del reino! Brujo nivel 80",
|
||||
461: "¡Primero del reino! Caballero de la Muerte nivel 80",
|
||||
462: "¡Primero del reino! Cazador nivel 80",
|
||||
467: "¡Primero del reino! Chamán nivel 80",
|
||||
466: "¡Primero del reino! Druida nivel 80",
|
||||
459: "¡Primero del reino! Guerrero nivel 80",
|
||||
460: "¡Primero del reino! Mago nivel 80",
|
||||
465: "¡Primero del reino! Paladín nivel 80",
|
||||
464: "¡Primero del reino! Sacerdote nivel 80",
|
||||
1408: "¡Primero del reino! Humano nivel 80",
|
||||
1407: "¡Primero del reino! Enano nivel 80",
|
||||
1409: "¡Primero del reino! Elfo de la noche nivel 80",
|
||||
1404: "¡Primero del reino! Gnomo nivel 80",
|
||||
1406: "¡Primero del reino! Draenei nivel 80",
|
||||
1410: "¡Primero del reino! Orco nivel 80",
|
||||
1413: "¡Primero del reino! Renegado nivel 80",
|
||||
1411: "¡Primero del reino! Tauren nivel 80",
|
||||
1412: "¡Primero del reino! Trol nivel 80",
|
||||
1405: "¡Primero del reino! Elfo de sangre nivel 80"
|
||||
}
|
||||
return achievement_names.get(achievement_id, "Logro Desconocido")
|
||||
def novawow_players_view(request):
|
||||
# Verificar si el usuario está logueado
|
||||
if 'username' not in request.session:
|
||||
return redirect('index')
|
||||
|
||||
# Obtener las 5 hermandades con más miembros
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
guild_query = """
|
||||
SELECT g.name, c.name AS leader,
|
||||
CASE WHEN c.race IN (1, 3, 4, 7, 11) THEN 'alliance' ELSE 'horde' END AS faction,
|
||||
COUNT(gm.guid) AS members,
|
||||
g.createdate
|
||||
FROM guild g
|
||||
JOIN characters c ON c.guid = g.leaderguid
|
||||
LEFT JOIN guild_member gm ON gm.guildid = g.guildid
|
||||
GROUP BY g.guildid
|
||||
ORDER BY members DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
cursor.execute(guild_query)
|
||||
guilds = cursor.fetchall()
|
||||
|
||||
formatted_guilds = [
|
||||
(name, leader, faction, members, datetime.fromtimestamp(createdate).strftime('%d-%m-%Y'))
|
||||
for name, leader, faction, members, createdate in guilds
|
||||
]
|
||||
|
||||
# Obtener el top 10 de jugadores con más muertes con honor
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
kills_query = """
|
||||
SELECT name, race, class, totalKills, todayKills
|
||||
FROM characters
|
||||
ORDER BY totalKills DESC
|
||||
LIMIT 10
|
||||
"""
|
||||
cursor.execute(kills_query)
|
||||
top_kills = cursor.fetchall()
|
||||
|
||||
players_with_kills = [
|
||||
{
|
||||
'name': name,
|
||||
'race_icon': f"big-{get_race_icon(race)}",
|
||||
'class_icon': f"{get_class_icon(char_class)}-medium",
|
||||
'total_kills': total_kills,
|
||||
'today_kills': today_kills
|
||||
}
|
||||
for name, race, char_class, total_kills, today_kills in top_kills
|
||||
]
|
||||
|
||||
# Obtener personajes por clase y facción
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
class_faction_query = """
|
||||
SELECT class,
|
||||
SUM(CASE WHEN race IN (1, 3, 4, 7, 11) THEN 1 ELSE 0 END) AS alliance_count,
|
||||
SUM(CASE WHEN race IN (2, 5, 6, 8, 10) THEN 1 ELSE 0 END) AS horde_count,
|
||||
COUNT(*) AS total_count
|
||||
FROM characters
|
||||
GROUP BY class
|
||||
"""
|
||||
cursor.execute(class_faction_query)
|
||||
class_faction_data = cursor.fetchall()
|
||||
|
||||
classes_data = [
|
||||
{
|
||||
'class_icon': f"{get_class_icon(char_class)}-chest",
|
||||
'class_name': get_class_name(char_class),
|
||||
'total': total_count,
|
||||
'alliance': alliance_count,
|
||||
'horde': horde_count
|
||||
}
|
||||
for char_class, alliance_count, horde_count, total_count in class_faction_data
|
||||
]
|
||||
|
||||
# Obtener el top 10 de jugadores con más logros
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
achievements_query = """
|
||||
SELECT c.name, c.race, c.class, MAX(cap.counter) AS achievement_points
|
||||
FROM character_achievement_progress cap
|
||||
JOIN characters c ON cap.guid = c.guid
|
||||
GROUP BY c.guid
|
||||
ORDER BY achievement_points DESC
|
||||
LIMIT 10
|
||||
"""
|
||||
cursor.execute(achievements_query)
|
||||
top_achievements = cursor.fetchall()
|
||||
|
||||
players_with_achievements = [
|
||||
{
|
||||
'name': name,
|
||||
'race_icon': f"big-{get_race_icon(race)}",
|
||||
'class_icon': f"{get_class_icon(char_class)}-medium",
|
||||
'achievement_points': achievement_points
|
||||
}
|
||||
for name, race, char_class, achievement_points in top_achievements
|
||||
]
|
||||
|
||||
# Obtener logros "Primeros del Reino"
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
first_realm_query = """
|
||||
SELECT ca.achievement, c.name, c.race, c.class, c.gender, ca.date
|
||||
FROM character_achievement ca
|
||||
JOIN characters c ON ca.guid = c.guid
|
||||
WHERE ca.achievement IN (
|
||||
457, 458, 463, 461, 462, 467, 466, 459, 460, 465, 464,
|
||||
1408, 1407, 1409, 1404, 1406, 1410, 1413, 1411, 1412, 1405
|
||||
)
|
||||
ORDER BY ca.date ASC
|
||||
"""
|
||||
cursor.execute(first_realm_query)
|
||||
first_realm_achievements = cursor.fetchall()
|
||||
|
||||
first_realm_data = []
|
||||
for achievement in first_realm_achievements:
|
||||
achievement_id, name, race, char_class, gender, date = achievement
|
||||
formatted_date = datetime.fromtimestamp(date).strftime('%H:%M:%S %d-%m-%Y')
|
||||
|
||||
# Determinar el ícono correcto basado en el logro
|
||||
if achievement_id == 457:
|
||||
icon_url = f"{settings.URL_PRINCIPAL}/static/nw-themes/nw-ryu/nw-images/nw-icons/achievement-level-80.jpg"
|
||||
elif achievement_id in [1408, 1407, 1409, 1404, 1406, 1410, 1413, 1411, 1412, 1405]:
|
||||
race_iconss = get_race_iconss(race)
|
||||
gender_suffix = "male" if gender == 0 else "female"
|
||||
icon_url = f"{settings.URL_PRINCIPAL}/static/nw-themes/nw-ryu/nw-images/nw-races/big-{race_iconss}-{gender_suffix}.webp"
|
||||
else:
|
||||
icon_url = f"{settings.URL_PRINCIPAL}/static/nw-themes/nw-ryu/nw-images/nw-classes/{get_class_icon(char_class)}-medium.jpg"
|
||||
|
||||
first_realm_data.append({
|
||||
'achievement_name': get_achievement_name(achievement_id),
|
||||
'name': name,
|
||||
'race_iconss': f"big-{get_race_iconss(race)}",
|
||||
'icon_url': icon_url,
|
||||
'date': formatted_date
|
||||
})
|
||||
|
||||
# Renderizar la plantilla con los datos obtenidos
|
||||
return render(request, 'realms/novawow_players.html', {
|
||||
'guilds': formatted_guilds,
|
||||
'players_with_kills': players_with_kills,
|
||||
'players_with_achievements': players_with_achievements,
|
||||
'classes_data': classes_data,
|
||||
'first_realm_data': first_realm_data
|
||||
})
|
||||
@@ -0,0 +1,89 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
def promo_code_view(request):
|
||||
return render(request, 'account/promo_code.html')
|
||||
def transfer_d_points_view(request):
|
||||
# Renderizar la plantilla para la transferencia de puntos
|
||||
return render(request, 'account/transfer_d_points.html')
|
||||
def vote_points_view(request):
|
||||
# Verificar si el usuario tiene un `username` en la sesión
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
# Obtener el `account_id` basado en el `username`
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [username])
|
||||
account_data = cursor.fetchone()
|
||||
|
||||
if not account_data:
|
||||
return JsonResponse({'success': False, 'message': 'Cuenta no encontrada.'})
|
||||
|
||||
account_id = account_data[0]
|
||||
|
||||
if request.method == 'POST':
|
||||
vote_url = request.POST.get('vote').strip()
|
||||
|
||||
site = VoteSite.objects.filter(url=vote_url).first()
|
||||
if not site:
|
||||
return JsonResponse({'success': False, 'message': 'Sitio de votación no encontrado.'})
|
||||
|
||||
# Verificar si ya ha votado en las últimas 12 horas y 30 minutos
|
||||
last_vote = VoteLog.objects.filter(account_id=account_id, vote_site=site).order_by('-created_at').first()
|
||||
now = timezone.now()
|
||||
if last_vote:
|
||||
time_diff = now - last_vote.created_at
|
||||
cooldown_period = timedelta(hours=12, minutes=30)
|
||||
|
||||
if time_diff < cooldown_period:
|
||||
remaining_time = cooldown_period - time_diff
|
||||
hours, remainder = divmod(remaining_time.seconds, 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'<span class="red-form-response">Tienes que esperar {hours} horas y {minutes} minutos antes de volver a votar.</span>'
|
||||
})
|
||||
|
||||
# Acreditar puntos al usuario y registrar el voto
|
||||
user_points = HomeApiPoints.objects.filter(accountID=account_id).first()
|
||||
if user_points:
|
||||
user_points.vp += site.points
|
||||
user_points.save()
|
||||
else:
|
||||
HomeApiPoints.objects.create(accountID=account_id, vp=site.points, dp=0)
|
||||
|
||||
# Registrar el voto
|
||||
VoteLog.objects.create(account_id=account_id, vote_site=site, last_vote_time=now)
|
||||
|
||||
return JsonResponse({'success': True, 'message': f'Has votado en {site.name}. Se han acreditado {site.points} PV.'})
|
||||
|
||||
vote_sites = VoteSite.objects.all()
|
||||
return render(request, 'account/vote_points.html', {'vote_sites': vote_sites})
|
||||
def d_points_view(request):
|
||||
"""
|
||||
Vista para la página de D-Points.
|
||||
Simplemente renderiza la plantilla d_points.html.
|
||||
"""
|
||||
return render(request, 'account/d_points.html')
|
||||
def points_history_view(request):
|
||||
return render(request, 'account/points_history.html')
|
||||
def trans_history_view(request):
|
||||
"""
|
||||
Vista para mostrar el historial de transacciones de la cuenta y sus personajes.
|
||||
"""
|
||||
username = request.session.get('username') # Obtener el nombre de usuario de la sesión
|
||||
|
||||
if username:
|
||||
# Filtramos las transacciones que corresponden al usuario y a los personajes asociados
|
||||
transactions = StripeLog.objects.filter(username=username)
|
||||
else:
|
||||
transactions = []
|
||||
|
||||
# Renderizamos la vista con el historial de transacciones
|
||||
return render(request, 'account/trans_history.html', {'transactions': transactions})
|
||||
def trade_points_view(request):
|
||||
"""
|
||||
Vista para la página de intercambio de puntos.
|
||||
"""
|
||||
return render(request, 'account/trade_points.html')
|
||||
@@ -0,0 +1,161 @@
|
||||
from ._base import *
|
||||
from .account import get_account_status, get_account_characters
|
||||
|
||||
|
||||
def recruit_a_friend_view(request):
|
||||
username = request.session.get('username')
|
||||
account_status = None
|
||||
characters = []
|
||||
claimed_rewards_count = 0
|
||||
|
||||
# Si el usuario está logueado, obtener su información
|
||||
if username:
|
||||
# Obtener información del usuario
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id, last_ip FROM account WHERE username = %s", [username])
|
||||
account_data = cursor.fetchone()
|
||||
|
||||
if account_data:
|
||||
account_id = account_data[0]
|
||||
user_ip = account_data[1]
|
||||
account_status = get_account_status(account_id)
|
||||
characters = get_account_characters(account_id)
|
||||
claimed_rewards_count = ClaimedReward.objects.filter(account_id=account_id).count()
|
||||
else:
|
||||
account_id = None
|
||||
user_ip = None
|
||||
else:
|
||||
account_id = None
|
||||
user_ip = None
|
||||
|
||||
# Obtener todas las recompensas
|
||||
all_rewards = RecruitReward.objects.all()
|
||||
available_rewards = []
|
||||
|
||||
# Si el usuario está logueado, filtrar recompensas disponibles
|
||||
if account_id:
|
||||
available_rewards = [reward for reward in all_rewards if not ClaimedReward.objects.filter(account_id=account_id, recruit_reward=reward).exists()]
|
||||
|
||||
# Manejar solicitudes POST (solo si el usuario está logueado)
|
||||
if request.method == 'POST':
|
||||
if not username:
|
||||
return JsonResponse({'success': False, 'message': 'Debes estar logueado para reclamar una recompensa.'})
|
||||
|
||||
if not account_id or account_status is None:
|
||||
return JsonResponse({'success': False, 'message': 'No se ha encontrado tu cuenta.'})
|
||||
|
||||
reward_id = request.POST.get('reward_id')
|
||||
character_name = request.POST.get('character')
|
||||
|
||||
if not reward_id or not character_name:
|
||||
return JsonResponse({'success': False, 'message': 'Selecciona una recompensa y un personaje.'})
|
||||
|
||||
try:
|
||||
reward = RecruitReward.objects.get(id=reward_id)
|
||||
|
||||
# Verificar si ya se ha reclamado la recompensa
|
||||
if ClaimedReward.objects.filter(account_id=account_id, recruit_reward=reward).exists():
|
||||
return JsonResponse({'success': False, 'message': 'Ya has reclamado esta recompensa.'})
|
||||
|
||||
# Verificar si cumple los requisitos
|
||||
if account_status['recruited_level_80_count'] < reward.required_friends:
|
||||
return JsonResponse({'success': False, 'message': 'No cumples con los requisitos.'})
|
||||
|
||||
# Obtener amigos reclutados que han alcanzado el nivel 80
|
||||
recruited_friends = get_recruited_friends_at_level_80(account_id, reward.required_friends)
|
||||
|
||||
# Comprobar si alguno de los reclutados es válido
|
||||
if len(recruited_friends) < reward.required_friends:
|
||||
return JsonResponse({'success': False, 'message': 'No cumples con los requisitos o los amigos tienen la misma IP.'})
|
||||
|
||||
# Comprobar si alguno de los reclutados tiene la misma IP que el usuario
|
||||
if check_recruited_ip_conflict(recruited_friends, user_ip):
|
||||
return JsonResponse({'success': False, 'message': 'No puedes reclamar recompensas de amigos que tienen la misma IP.'})
|
||||
|
||||
# Ejecutar el comando SOAP
|
||||
command = f".send items {character_name} 'Recompensa de Recluta Amigo' 'Tu amigo alcanzó el Nivel 80' {reward.item_id}:{reward.item_quantity}"
|
||||
response = execute_soap_command(command)
|
||||
|
||||
if response and "Mail sent to" in response:
|
||||
# Registrar la recompensa como reclamada
|
||||
ClaimedReward.objects.create(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
recruit_reward=reward,
|
||||
character_name=character_name,
|
||||
claimed_at=timezone.now(),
|
||||
ip_address=request.META.get('REMOTE_ADDR')
|
||||
)
|
||||
claimed_rewards_count = ClaimedReward.objects.filter(account_id=account_id).count()
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f"Recompensa '{reward.reward_name}' entregada a {character_name}.",
|
||||
'claimed_count': claimed_rewards_count
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': 'Error al entregar la recompensa.'})
|
||||
|
||||
except RecruitReward.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'message': 'Recompensa no encontrada.'})
|
||||
|
||||
# Renderizar la página para usuarios no logueados y logueados
|
||||
return render(request, 'recruit/recruit_a_friend.html', {
|
||||
'account_status': account_status,
|
||||
'all_rewards': all_rewards,
|
||||
'available_rewards': available_rewards,
|
||||
'characters': characters,
|
||||
'claimed_rewards_count': claimed_rewards_count,
|
||||
'is_logged_in': username is not None
|
||||
})
|
||||
def check_recruited_ip_conflict(recruited_accounts, user_ip):
|
||||
"""
|
||||
Verifica si alguna de las cuentas reclutadas tiene la misma IP que la cuenta del reclutador.
|
||||
"""
|
||||
if not recruited_accounts:
|
||||
return False
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT last_ip FROM account WHERE id IN %s
|
||||
""", [tuple(recruited_accounts)])
|
||||
recruited_ips = [ip[0] for ip in cursor.fetchall()]
|
||||
|
||||
return user_ip in recruited_ips
|
||||
def get_recruited_friends_at_level_80(account_id, required_count):
|
||||
"""
|
||||
Obtiene una lista de IDs de amigos reclutados que han alcanzado el nivel 80 y
|
||||
no tienen la misma IP que el reclutador.
|
||||
"""
|
||||
# Primero obtener los IDs de cuentas reclutadas desde `acore_auth`
|
||||
with connections['acore_auth'].cursor() as auth_cursor:
|
||||
auth_cursor.execute("""
|
||||
SELECT id, last_ip FROM account WHERE recruiter = %s
|
||||
""", [account_id])
|
||||
recruited_accounts = auth_cursor.fetchall()
|
||||
|
||||
if not recruited_accounts:
|
||||
return []
|
||||
|
||||
# Extraer IDs y IPs de las cuentas reclutadas
|
||||
recruited_ids = [acc[0] for acc in recruited_accounts]
|
||||
recruited_ips = {acc[0]: acc[1] for acc in recruited_accounts}
|
||||
|
||||
# Obtener la IP del usuario actual
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT last_ip FROM account WHERE id = %s", [account_id])
|
||||
user_ip = cursor.fetchone()[0]
|
||||
|
||||
# Luego, verificar cuáles de esas cuentas tienen personajes al nivel 80 en `acore_characters`
|
||||
with connections['acore_characters'].cursor() as char_cursor:
|
||||
char_cursor.execute("""
|
||||
SELECT DISTINCT account FROM characters
|
||||
WHERE level = 80 AND account IN %s
|
||||
LIMIT %s
|
||||
""", [tuple(recruited_ids), required_count])
|
||||
recruited_at_level_80 = [acc[0] for acc in char_cursor.fetchall()]
|
||||
|
||||
# Filtrar cuentas que no tienen la misma IP que el usuario actual
|
||||
valid_recruits = [acc for acc in recruited_at_level_80 if recruited_ips[acc] != user_ip]
|
||||
|
||||
return valid_recruits
|
||||
@@ -0,0 +1,173 @@
|
||||
from ._base import *
|
||||
|
||||
|
||||
def store_novawow_cancel_view(request):
|
||||
return render(request, 'store/store_novawow_cancel.html')
|
||||
def store_novawow_view(request):
|
||||
username = request.session.get('username')
|
||||
if not username:
|
||||
return redirect('login')
|
||||
|
||||
category_name = request.GET.get("category")
|
||||
|
||||
# Obtener personajes del usuario desde la base de datos acore_characters
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT name
|
||||
FROM characters
|
||||
WHERE account = (
|
||||
SELECT id
|
||||
FROM acore_auth.account
|
||||
WHERE username = %s
|
||||
)
|
||||
""", [username])
|
||||
characters = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Obtener categorías e ítems
|
||||
categories = Category.objects.prefetch_related("items").all()
|
||||
store_data = {category.name: list(category.items.values()) for category in categories}
|
||||
|
||||
# Si se está solicitando una categoría específica, devolver solo los ítems de esa categoría
|
||||
if category_name:
|
||||
try:
|
||||
category = Category.objects.get(name=category_name)
|
||||
except Category.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'message': 'Categoría no encontrada'}, status=404)
|
||||
return JsonResponse({'success': True, 'items': list(category.items.values())})
|
||||
|
||||
# Obtener carrito desde la sesión
|
||||
carrito = request.session.get('carrito', [])
|
||||
|
||||
# Calcular el precio total con 2 decimales
|
||||
total_price = sum(Decimal(item['precio']) for item in carrito)
|
||||
total_price_with_iva = (total_price * Decimal('1.21')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
||||
|
||||
if request.method == 'POST':
|
||||
# Obtener datos del body
|
||||
data = json.loads(request.body)
|
||||
character_name = data.get('character')
|
||||
carrito_cliente = data.get('carrito') or []
|
||||
|
||||
if not character_name:
|
||||
return JsonResponse({'success': False, 'message': 'Por favor, selecciona un personaje.'})
|
||||
|
||||
# Verificar si el personaje pertenece al usuario
|
||||
if character_name not in characters:
|
||||
return JsonResponse({'success': False, 'message': 'No tienes permiso para este personaje.'})
|
||||
|
||||
if not carrito_cliente:
|
||||
return JsonResponse({'success': False, 'message': 'El carrito está vacío.'})
|
||||
|
||||
# VALIDAR ÍTEMS Y PRECIOS CONTRA LA BASE DE DATOS (nunca confiar en el
|
||||
# precio ni en los ítems que manda el cliente).
|
||||
try:
|
||||
ids_solicitados = [int(item['id']) for item in carrito_cliente]
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return JsonResponse({'success': False, 'message': 'Carrito no válido.'})
|
||||
|
||||
items_db = {i.item_id: i for i in Item.objects.filter(item_id__in=ids_solicitados)}
|
||||
|
||||
carrito = [] # carrito saneado que se guardará en sesión
|
||||
total_price = Decimal('0.00')
|
||||
for item in carrito_cliente:
|
||||
item_id = int(item['id'])
|
||||
db_item = items_db.get(item_id)
|
||||
if not db_item:
|
||||
return JsonResponse({'success': False, 'message': f'Ítem no disponible en la tienda (ID {item_id}).'})
|
||||
try:
|
||||
cantidad = max(1, int(item.get('cantidad', 1)))
|
||||
except (TypeError, ValueError):
|
||||
cantidad = 1
|
||||
total_price += db_item.price * cantidad
|
||||
carrito.append({'id': item_id, 'nombre': db_item.name, 'cantidad': cantidad})
|
||||
|
||||
total_price_with_iva = (total_price * Decimal('1.21')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
|
||||
|
||||
# Guardar en sesión el carrito YA VALIDADO (para la entrega tras el pago)
|
||||
request.session['carrito'] = carrito
|
||||
request.session.modified = True
|
||||
|
||||
# Descripción del producto a partir de los datos validados
|
||||
product_description = ", ".join([f"{it['nombre']} x{it['cantidad']}" for it in carrito])
|
||||
|
||||
# Obtener detalles del usuario
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id, email, last_ip FROM account WHERE username = %s", [username])
|
||||
user_data = cursor.fetchone()
|
||||
|
||||
if not user_data:
|
||||
return JsonResponse({'success': False, 'message': 'Cuenta no encontrada.'})
|
||||
|
||||
user_id, email, acore_ip = user_data # ID de la cuenta, email y la última IP
|
||||
|
||||
# Crear sesión de pago con Stripe
|
||||
success_url = request.build_absolute_uri(reverse('store-novawow/success'))
|
||||
cancel_url = request.build_absolute_uri(reverse('store-novawow/cancel'))
|
||||
|
||||
# Llamada corregida a create_checkout_session con todos los parámetros requeridos
|
||||
stripe_response = create_checkout_session(
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
account_id=user_id, # Pasar el account_id
|
||||
amount=total_price_with_iva, # El precio con IVA
|
||||
character_name=character_name,
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
product_name=f"Compra de ítems para el personaje: {character_name} : {product_description}"
|
||||
)
|
||||
|
||||
if stripe_response["success"]:
|
||||
request.session['store_novawow'] = character_name
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': stripe_response["session_id"],
|
||||
'stripe_public_key': settings.STRIPE_PUBLIC_KEY
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': f'Error al iniciar el pago: {stripe_response["error"]}'})
|
||||
|
||||
return render(request, 'store/store_novawow.html', {
|
||||
'characters': characters,
|
||||
'store_data': store_data,
|
||||
'categories': categories,
|
||||
'total_price': total_price_with_iva
|
||||
})
|
||||
@require_paid_stripe(redirect_to='store-novawow')
|
||||
def store_novawow_success_view(request):
|
||||
# Procesar el comando SOAP tras el pago exitoso
|
||||
character_name = request.session.get('store_novawow')
|
||||
carrito = request.session.get('carrito', [])
|
||||
|
||||
if not character_name or not carrito:
|
||||
return redirect('store-novawow')
|
||||
|
||||
# Contar correctamente los ítems por ID
|
||||
item_counts = Counter(item["id"] for item in carrito)
|
||||
|
||||
# Generar la lista de ítems en formato "ID:Cantidad"
|
||||
items_str = " ".join(f"{item_id}:{count}" for item_id, count in item_counts.items())
|
||||
|
||||
# Construir el comando SOAP
|
||||
command = f".send items {character_name} 'Gracias por donar a Nova WoW' 'Aquí tienes tu donación' {items_str}"
|
||||
|
||||
# Ejecutar el comando SOAP
|
||||
response = execute_soap_command(command)
|
||||
|
||||
# Limpiar la sesión después del envío
|
||||
request.session.pop('store_novawow', None)
|
||||
request.session.pop('carrito', None)
|
||||
|
||||
if response is None or response.strip() == "":
|
||||
message = f"Los ítems han sido añadidos al personaje {character_name} correctamente."
|
||||
return render(request, 'store/store_novawow_success.html', {'message': message})
|
||||
else:
|
||||
return render(request, 'store/store_novawow_success.html', {'message': f"Error: {response}"})
|
||||
def store_novawow_buscar_categoria_view(request, categoria):
|
||||
try:
|
||||
# Buscar la categoría desde el parámetro de la URL
|
||||
category = Category.objects.get(name=categoria)
|
||||
items = category.items.all().values("id", "name", "price", "image_url")
|
||||
return JsonResponse({"success": True, "items": list(items)})
|
||||
except Category.DoesNotExist:
|
||||
return JsonResponse({"success": False, "message": "Categoría no encontrada"}, status=404)
|
||||
Reference in New Issue
Block a user