from flask import Flask, render_template, request, redirect, url_for, jsonify, session import sqlite3 import string import random import os import sys import argparse from datetime import datetime, timedelta import uuid app = Flask(__name__) app.secret_key = 'tu_clave_secreta_aqui' # Cambiar en producción # Configuración de la base de datos DATABASE = 'url_shortener.db' def init_db(): """Inicializar la base de datos""" conn = sqlite3.connect(DATABASE) cursor = conn.cursor() # Tabla para enlaces acortados cursor.execute(''' CREATE TABLE IF NOT EXISTS urls ( id INTEGER PRIMARY KEY AUTOINCREMENT, original_url TEXT NOT NULL, short_code TEXT UNIQUE NOT NULL, user_session TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, clicks INTEGER DEFAULT 0 ) ''') # Agregar columna expires_at si no existe (para bases de datos existentes) try: cursor.execute('ALTER TABLE urls ADD COLUMN expires_at TIMESTAMP') conn.commit() except sqlite3.OperationalError: # La columna ya existe pass conn.commit() conn.close() def generate_short_code(length=6): """Generar código corto aleatorio""" characters = string.ascii_letters + string.digits return ''.join(random.choice(characters) for _ in range(length)) def get_user_session(): """Obtener o crear sesión de usuario anónimo""" if 'user_id' not in session: session['user_id'] = str(uuid.uuid4()) return session['user_id'] @app.route('/') def index(): """Página principal""" return render_template('index.html') @app.route('/my-links') def my_links(): """Página de enlaces del usuario""" user_session = get_user_session() # Obtener enlaces del usuario conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute(''' SELECT original_url, short_code, created_at, clicks, expires_at FROM urls WHERE user_session = ? ORDER BY created_at DESC ''', (user_session,)) user_urls = cursor.fetchall() conn.close() return render_template('my_links.html', user_urls=user_urls) def calculate_expiration(duration): """Calcular fecha de expiración basada en duración""" if duration == 'never': return None elif duration == '1hour': return datetime.now() + timedelta(hours=1) elif duration == '1day': return datetime.now() + timedelta(days=1) elif duration == '1week': return datetime.now() + timedelta(weeks=1) elif duration == '1month': return datetime.now() + timedelta(days=30) elif duration == '1year': return datetime.now() + timedelta(days=365) else: return None def is_url_expired(expires_at): """Verificar si un URL ha expirado""" if expires_at is None: return False return datetime.now() > datetime.fromisoformat(expires_at) @app.route('/shorten', methods=['POST']) def shorten_url(): """Acortar URL""" # Obtener datos tanto de formulario como de JSON if request.is_json: original_url = request.json.get('url') duration = request.json.get('duration', 'never') else: original_url = request.form.get('url') duration = request.form.get('duration', 'never') if not original_url: return jsonify({'error': 'URL es requerida'}), 400 # Agregar http:// si no tiene protocolo if not original_url.startswith(('http://', 'https://')): original_url = 'http://' + original_url user_session = get_user_session() expires_at = calculate_expiration(duration) # Generar código corto único while True: short_code = generate_short_code() conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute('SELECT id FROM urls WHERE short_code = ?', (short_code,)) if not cursor.fetchone(): break conn.close() # Guardar en base de datos cursor.execute(''' INSERT INTO urls (original_url, short_code, user_session, expires_at) VALUES (?, ?, ?, ?) ''', (original_url, short_code, user_session, expires_at)) conn.commit() conn.close() short_url = f"https://please.checkthis.space/s/{short_code}" return jsonify({ 'success': True, 'short_url': short_url, 'short_code': short_code, 'original_url': original_url, 'expires_at': expires_at.isoformat() if expires_at else None }) @app.route('/s/') def redirect_url(short_code): """Redirigir a URL original""" conn = sqlite3.connect(DATABASE) cursor = conn.cursor() # Buscar URL original y verificar expiración cursor.execute(''' SELECT original_url, expires_at FROM urls WHERE short_code = ? ''', (short_code,)) result = cursor.fetchone() if result: original_url, expires_at = result # Verificar si el enlace ha expirado if expires_at and is_url_expired(expires_at): conn.close() return render_template('404.html', error_message='Este enlace ha expirado'), 404 # Incrementar contador de clicks cursor.execute(''' UPDATE urls SET clicks = clicks + 1 WHERE short_code = ? ''', (short_code,)) conn.commit() conn.close() return redirect(original_url) else: conn.close() return render_template('404.html'), 404 @app.route('/delete/', methods=['POST']) def delete_url(short_code): """Eliminar URL acortada""" user_session = get_user_session() conn = sqlite3.connect(DATABASE) cursor = conn.cursor() # Verificar que el enlace pertenece al usuario cursor.execute(''' DELETE FROM urls WHERE short_code = ? AND user_session = ? ''', (short_code, user_session)) conn.commit() affected_rows = cursor.rowcount conn.close() if affected_rows > 0: return jsonify({'success': True}) else: return jsonify({'error': 'Enlace no encontrado o no autorizado'}), 404 @app.route('/stats') def stats(): """Estadísticas generales""" conn = sqlite3.connect(DATABASE) cursor = conn.cursor() # Estadísticas generales cursor.execute('SELECT COUNT(*) FROM urls') total_urls = cursor.fetchone()[0] cursor.execute('SELECT SUM(clicks) FROM urls') total_clicks = cursor.fetchone()[0] or 0 # URLs más populares cursor.execute(''' SELECT original_url, short_code, clicks, created_at FROM urls ORDER BY clicks DESC LIMIT 10 ''') popular_urls = cursor.fetchall() conn.close() return render_template('stats.html', total_urls=total_urls, total_clicks=total_clicks, popular_urls=popular_urls) if __name__ == '__main__': init_db() # Configurar argumentos de línea de comandos parser = argparse.ArgumentParser(description='Acortador de URLs') parser.add_argument('--port', '-p', type=int, default=5000, help='Puerto en el que ejecutar la aplicación (por defecto: 5000)') args = parser.parse_args() print(f"Ejecutando en puerto {args.port}") app.run(debug=True, host='0.0.0.0', port=args.port)