Publicar
Comentarios
Notificaciones
@usuario_pro te ha dado un like. hace 2 min
window.forceLoadVideos = async function(category) { const feed = document.getElementById('feed'); if(!feed) return false; window.currentCategory = category; window.isFetchingMore = false; const miUsuario = localStorage.getItem("user_id") || ""; try { // Usamos directamente la URL de Cloudflare que definiste al principio const server = window.API_BASE_URL; const response = await fetch(`${server}/api/videos/${category}?user=${miUsuario}`); if (!response.ok) throw new Error("Error de red"); const videos = await response.json(); if (!videos || videos.length === 0) { feed.innerHTML = `
No hay videos disponibles
`; return false; } // 1. Limpieza agresiva para liberar RAM en Android if (window.videoObserver) window.videoObserver.disconnect(); feed.querySelectorAll('video').forEach(v => { v.pause(); v.src = ""; v.load(); }); const fragment = document.createDocumentFragment(); videos.forEach((videoItem, index) => { const dataParaPost = { ...(typeof videoItem === 'object' ? videoItem : { file: videoItem }), category: category }; const post = createPost(dataParaPost); const v = post.querySelector('video'); if(v) { // 🚀 TRUCO MAESTRO: Evita el error file:/// de TrebEdit // Le damos un src falso inicial para que el navegador no busque en carpetas locales v.src = "data:video/mp4;base64,AAAA"; // Pre-carga inteligente v.preload = (index < 2) ? "auto" : "metadata"; // "Calentamos" solo los 2 primeros videos para no saturar el Wifi/Datos if(index < 2) { const fileName = dataParaPost.file || dataParaPost.name || ""; const cleanName = fileName.replace(`${category}-`, ""); // Asignamos la ruta real de Termux const realSrc = `${server}/stream/${category}/${cleanName}`; v.setAttribute('data-src', realSrc); // Actualizamos el data-src por si acaso v.src = realSrc; v.style.backgroundColor = "black"; } } fragment.appendChild(post); }); // 2. Insertar en el DOM feed.replaceChildren(fragment); window.scrollTo({ top: 0, behavior: 'instant' }); // 3. Re-activar el observador const allPosts = feed.querySelectorAll('.post-section'); if (window.videoObserver) { allPosts.forEach(p => window.videoObserver.observe(p)); } // 4. Iniciar primer video con un pequeño delay para que el DOM respire setTimeout(() => { const firstVideo = allPosts[0]?.querySelector('video'); if (firstVideo) { firstVideo.muted = (window.isGlobalMuted !== false); firstVideo.play().catch(() => { firstVideo.muted = true; firstVideo.play().catch(() => {}); }); } }, 100); // 5. Scroll infinito if (allPosts.length > 0 && typeof window.watchLastPost === 'function') { window.watchLastPost(allPosts[allPosts.length - 1]); } return true; } catch (err) { console.error("Fallo crítico en forceLoadVideos:", err); return false; } }; // Función para vigilar el último post y cargar más (Scroll Infinito) window.watchLastPost = function(element) { if (!element) return; const observer = new IntersectionObserver((entries) => { // Si el usuario llega al último post y NO estamos cargando ya otros... if (entries[0].isIntersecting && !window.isFetchingMore) { console.log("Detectado final del feed, cargando más..."); if (typeof window.loadMoreVideos === 'function') { window.loadMoreVideos(); } observer.unobserve(entries[0].target); // Dejar de vigilar el viejo último } }, { threshold: 0.1, rootMargin: "0px 0px 500px 0px" }); observer.observe(element); }; /* --- 3. CORRECCIÓN DE LINKS Y PERFILES --- */ window.irAlPerfil = function(username) { if (!username || username === "undefined" || username === "null") { console.error("Usuario inválido"); return; } const targetUser = String(username).replace('@', '').trim(); localStorage.setItem("verPerfilDe", targetUser); window.location.href = `../Perfil.html?user=${targetUser}`; }; window.copyLinkGlobal = function(videoUrl) { const vId = videoUrl || window.currentShareVideoUrl; if (!vId) return; const fullLink = window.location.origin + "/?v=" + encodeURIComponent(vId); if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(fullLink).then(() => { alert("¡Copiado!"); if(window.closeShare) window.closeShare(); }).catch(() => prompt("Copia manualmente:", fullLink)); } else { prompt("Copia manualmente:", fullLink); } }; function createPost(videoData) { // 1. Normalización de datos const data = (typeof videoData === 'string') ? { file: videoData, category: window.currentCategory || "para-ti" } : videoData; const fileName = data.file || data.name || ""; const currentCat = data.category || window.currentCategory || "para-ti"; // Y REEMPLÁZALO POR ESTO: const serverBase = window.API_BASE_URL || window.domain; // Preparar URL de Streaming (Limpieza de prefijos) const prefix = currentCat + "-"; let streamName = fileName; if (fileName.startsWith(prefix)) { streamName = fileName.replace(prefix, ""); } const finalSrc = `${serverBase}/stream/${currentCat}/${streamName}`; // --- IDENTIDAD DEL AUTOR --- const author = data.author || {}; const miIDLogueado = (localStorage.getItem("user_id") || "").replace(/[@ ]/g, '').toLowerCase(); const authorID = data.user_id || author.user || author.username || (fileName.includes(miIDLogueado) ? miIDLogueado : "usuario_desconocido"); const cleanAuthor = String(authorID).replace(/[@ ]/g, '').toLowerCase(); const esMiPropioVideo = (cleanAuthor === miIDLogueado && miIDLogueado !== ""); // Foto y Nombre let autorPhoto; let nombreVisible; if (esMiPropioVideo) { const miFoto = localStorage.getItem("user_photo") || 'https://www.w3schools.com/howto/img_avatar.png'; autorPhoto = miFoto.startsWith('data:') ? miFoto : `${miFoto}?t=${Date.now()}`; nombreVisible = localStorage.getItem("user_name") || "Tú"; } else { autorPhoto = data.author_photo || author.photo || author.foto || 'https://www.w3schools.com/howto/img_avatar.png'; nombreVisible = author.name || author.nombre || "Usuario"; } // --- CREACIÓN DEL ELEMENTO --- const section = document.createElement('section'); section.className = 'post-section'; // Atributos para que el Observer sepa qué cargar section.setAttribute('data-video-file', fileName); section.setAttribute('data-category', currentCat); const descriptionConLinks = (data.description || "").replace(/#(\w+)/g, `#$1` ); // --- ESTADOS DE INTERACCIÓN --- const storageKeyLike = `like_${currentCat}_${fileName}`; const yaTieneLike = (data.is_liked === true) || (localStorage.getItem(storageKeyLike) === 'true'); const storageKeySave = `save_${currentCat}_${fileName}`; const yaEstaGuardado = (data.is_saved === true) || (localStorage.getItem(storageKeySave) === 'true'); if (data.is_liked) localStorage.setItem(storageKeyLike, 'true'); // --- ESTRUCTURA HTML --- // Nota: El src está vacío inicialmente para que el Observer lo maneje y no falle en Android section.innerHTML = `
${esMiPropioVideo ? "" : `
`}
${data.likes_count || data.likes || 0}
${data.comments_count || 0}
${data.saves_count || data.saves || 0}
@${cleanAuthor}

${nombreVisible}

${data.title || ""}

${descriptionConLinks}

`; const video = section.querySelector('video'); const loading = section.querySelector('.loading-circle'); const wrapper = section.querySelector('.video-wrapper'); // 🚀 Usamos 'onplaying' para asegurar que el video ya tiene buffer y se está moviendo video.onplaying = () => { // 1. Lógica Visual Instantánea video.style.opacity = "1"; if (loading) { loading.style.display = "none"; } // 2. Lógica de Contador (Solo la primera vez) if (!video.hasAttribute('data-view-counted')) { video.setAttribute('data-view-counted', 'true'); const idParaEnviar = data._id || data.id || fileName; fetch(`${serverBase}/api/update-views`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_id: idParaEnviar, category: currentCat }) }) .then(res => { if (!res.ok) throw new Error("Error en servidor"); return res.json(); }) .then(res => { console.log("✅ Vista sumada:", idParaEnviar); }) .catch(err => { console.error("❌ Fallo al sumar vista, reintentando en el próximo play"); // Si falla la red, permitimos que se intente de nuevo más tarde video.removeAttribute('data-view-counted'); }); } }; wrapper.addEventListener('click', (e) => { e.stopPropagation(); window.isGlobalMuted = !window.isGlobalMuted; document.querySelectorAll('video').forEach(v => v.muted = window.isGlobalMuted); video.paused ? video.play().catch(()=>{}) : video.pause(); const hint = section.querySelector('.mute-hint'); if (hint) { hint.innerHTML = window.isGlobalMuted ? '' : ''; hint.style.display = "flex"; hint.style.opacity = "1"; setTimeout(() => hint.style.opacity = "0", 600); } }); // --- LÓGICA DE LIKES MEJORADA --- const btnLike = section.querySelector('.btn-like'); btnLike.onclick = async (e) => { e.stopPropagation(); if (!miIDLogueado) { alert("Inicia sesión para dar Me gusta"); return; } const isLiked = btnLike.classList.contains('is-liked'); const icon = btnLike.querySelector('i'); const label = btnLike.querySelector('.action-label'); // 1. Efecto Visual (Optimista) const action = !isLiked ? 'add' : 'remove'; btnLike.classList.toggle('is-liked'); icon.className = (action === 'add') ? 'fas fa-heart' : 'far fa-heart'; icon.style.color = (action === 'add') ? '#fe2c55' : '#fff'; let currentLikes = parseInt(label.innerText) || 0; label.innerText = (action === 'add') ? currentLikes + 1 : Math.max(0, currentLikes - 1); try { const res = await fetch(`${serverBase}/api/like/${currentCat}/${fileName}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ action: action, user: miIDLogueado, author: cleanAuthor // Enviamos quién es el dueño para el perfil }) }); if (res.ok) { const result = await res.json(); label.innerText = result.likes; if (action === 'add') { localStorage.setItem(storageKeyLike, 'true'); } else { localStorage.removeItem(storageKeyLike); } } } catch (err) { console.error("Error likes:", err); // Revertir visualmente si falla btnLike.classList.toggle('is-liked'); label.innerText = currentLikes; } }; return section; } /* --- 2. GESTIÓN DE COMENTARIOS --- */ let currentVideoId = null; let currentVideoCat = null; window.openComments = async function(videoId, category) { currentVideoId = videoId; currentVideoCat = category; const modal = document.getElementById('commentModal'); const list = document.getElementById('commentList'); if (!modal || !list) return; modal.classList.add('active'); document.body.style.overflow = 'hidden'; list.innerHTML = "
Cargando...
"; try { const res = await fetch(`${window.domain}/api/comments/${category}/${videoId}`); if (res.ok) { const comments = await res.json(); list.innerHTML = comments.length > 0 ? comments.map(c => `
${c.username || 'Usuario'}

${c.text}

`).join('') : "

Sé el primero en comentar...

"; } } catch (e) { list.innerHTML = "

Error de conexión

"; } }; // --- ESTA ES LA QUE FALTA --- window.closeComments = function() { const modal = document.getElementById('commentModal'); if (modal) { modal.classList.remove('active'); document.body.style.overflow = 'auto'; // Rehabilitar el scroll de la página } }; // Bonus: Cerrar al hacer clic en el fondo oscuro window.addEventListener('click', (e) => { const modal = document.getElementById('commentModal'); if (e.target === modal) { window.closeComments(); } }); /* --- 3. GESTIÓN DE SHARE (COMPARTIR) --- */ window.openShare = function(videoUrl) { const modal = document.getElementById('shareModal'); if (modal) { window.currentShareVideoUrl = videoUrl; modal.classList.add('active'); } }; window.closeShare = function() { const modal = document.getElementById('shareModal'); if (modal) modal.classList.remove('active'); }; window.copyLinkGlobal = function(videoUrl) { const fullLink = window.location.origin + "?v=" + (videoUrl || window.currentShareVideoUrl); navigator.clipboard.writeText(fullLink).then(() => { alert("¡Enlace copiado!"); if(window.registrarShareEnTermux) window.registrarShareEnTermux(); window.closeShare(); }); }; /* --- 4. NAVEGACIÓN Y PERFIL --- */ window.irAlPerfil = function(username) { if (!username) return; const targetUser = username.replace('@', '').trim(); window.location.href = `../Perfil.html?user=${targetUser}`; }; window.seguirDesdeFeed = async function(usuarioASeguir, elemento) { const miID = localStorage.getItem("user_id"); if (!miID) return alert("Inicia sesión para seguir"); const server = (window.API_BASE_URL || "http://127.0.0.1:8080").replace(/\/$/, ""); try { const res = await fetch(`${server}/api/follow`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ follower: miID.replace('@','').toLowerCase(), target: usuarioASeguir.replace('@','').toLowerCase() }) }); if (res.ok) { elemento.style.transform = "scale(0)"; setTimeout(() => elemento.remove(), 300); } } catch (e) { console.error("Error follow:", e); } }; /* --- 5. BÚSQUEDA Y HASHTAGS --- */ window.buscarPorHashtag = async function(tag) { const query = tag.replace('#', ''); const server = window.API_BASE_URL || "http://127.0.0.1:8080"; try { const res = await fetch(`${server}/api/search?q=${encodeURIComponent(query)}`); const resultados = await res.json(); window.mostrarResultadosEnCuadricula(resultados, `#${query}`); } catch (err) { console.error("Error búsqueda:", err); } }; /* --- 6. INICIALIZACIÓN (VERSIÓN FINAL) --- */ document.addEventListener("DOMContentLoaded", () => { // 1. Configurar clics de la barra inferior const btnInicio = document.getElementById('nav-inicio'); const btnAdultos = document.getElementById('nav-adultos-icon'); if (btnInicio) btnInicio.onclick = () => window.syncUI('para-ti'); if (btnAdultos) btnAdultos.onclick = () => window.syncUI('adulto'); // 2. Configurar botón Perfil const btnPerfil = document.getElementById('nav-perfil'); if (btnPerfil) btnPerfil.onclick = () => window.location.href = "../Perfil.html"; // 3. LÓGICA DE CARGA INICIAL (Detección de Subida o Link) const params = new URLSearchParams(window.location.search); const videoUrl = params.get('video'); const catUrl = params.get('cat'); // <--- Capturamos la categoría del botón "VER" const videoRef = localStorage.getItem("video_referencia"); let catInicial = "para-ti"; // Prioridad 1: Si la URL dice explícitamente la categoría (del botón VER) if (catUrl) { catInicial = catUrl; } // Prioridad 2: Si el nombre del video empieza por "adulto-" else if (videoUrl && videoUrl.startsWith('adulto-')) { catInicial = "adulto"; } // Prioridad 3: Referencia guardada anteriormente else if (videoRef && videoRef.includes('adulto-')) { catInicial = "adulto"; } // 4. Ejecutar la carga del video específico si existe, o la categoría general if (videoUrl) { // Si hay un video en la URL, lo cargamos con su categoría window.loadSpecificVideo(videoUrl, catInicial); window.syncUI(catInicial, false); // Sincronizamos UI sin recargar todo el feed } else { // Si no hay video, carga normal del feed window.syncUI(catInicial); } }); window.syncUI = function(category, reloadVideos = true) { console.log("Sincronizando interfaz para:", category); window.currentCategory = category; // 1. LIMPIAR TODO (Arriba y Abajo) document.querySelectorAll('.nav-tab, .nav-item').forEach(el => { el.classList.remove('active'); }); // 2. ACTIVAR SEGÚN CATEGORÍA if (category === 'adulto') { document.getElementById('tab-adulto')?.classList.add('active'); document.getElementById('nav-adultos-icon')?.classList.add('active'); } else { document.getElementById('tab-para-ti')?.classList.add('active'); document.getElementById('nav-inicio')?.classList.add('active'); } // 3. CARGAR LOS VIDEOS (Solo si no venimos de loadSpecificVideo) if (reloadVideos && window.forceLoadVideos) { window.forceLoadVideos(category); } }; /* --- EVENTO PARA ABRIR EL MODAL AL SELECCIONAR VIDEO --- */ document.getElementById('videoInput').addEventListener('change', function(e) { const file = e.target.files ? e.target.files[0] : null; if (!file) return; const modal = document.getElementById('uploadModal'); modal.style.display = "flex"; // Usamos flex para centrar el contenido // Autocompletar título con el nombre del archivo (limpio) const cleanName = file.name.split('.').slice(0, -1).join('.'); document.getElementById('videoTitle').value = cleanName; }); /* --- FUNCIÓN DE CIERRE ACTUALIZADA --- */ window.closeUpload = function() { document.getElementById('uploadModal').style.display = "none"; document.getElementById('videoInput').value = ""; document.getElementById('videoTitle').value = ""; document.getElementById('videoDesc').value = ""; document.getElementById('videoHashtags').value = ""; // Reset del botón por si acaso quedó en "Procesando" const btn = document.getElementById('finishUpload'); btn.disabled = false; btn.innerHTML = "PUBLICAR"; }; /* --- SUBIDA CON REDIRECCIÓN INTELIGENTE --- */ window.ejecutarSubida = async function() { const fileInput = document.getElementById('videoInput'); const btn = document.getElementById('finishUpload'); const file = fileInput.files[0]; if (!file) { alert("Por favor, selecciona un video primero."); return; } btn.disabled = true; btn.innerHTML = ' SUBIENDO...'; const miAutor = { name: localStorage.getItem("user_name") || "Usuario", user: (localStorage.getItem("user_id") || "anonimo").replace(/[@ ]/g, '').toLowerCase(), photo: localStorage.getItem("user_photo") || 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png' }; // 1. Guardamos la categoría elegida en una variable constante para usarla luego const selectedCat = document.getElementById('videoCategory').value; const formData = new FormData(); formData.append('video', file); const metadata = { title: document.getElementById('videoTitle').value.trim() || "Sin título", description: document.getElementById('videoDesc').value.trim(), hashtags: document.getElementById('videoHashtags').value.trim(), category: selectedCat, author: miAutor }; formData.append('metadata', JSON.stringify(metadata)); formData.append('category', selectedCat); document.getElementById('uploadModal').style.display = "none"; // --- TOAST DE PROGRESO --- const toast = document.createElement('div'); toast.id = "uploadToast"; toast.style = "position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:#1a1a1a; padding:15px; border-radius:18px; z-index:10000; width:90%; max-width:400px; border:1px solid #fe2c55; box-shadow:0 15px 35px rgba(0,0,0,0.7);"; toast.innerHTML = `
SexChapss UPLOADER 0%
`; document.body.appendChild(toast); const xhr = new XMLHttpRequest(); const serverBase = (window.API_BASE_URL || "http://127.0.0.1:8080").replace(/\/$/, ""); xhr.open('POST', `${serverBase}/api/upload`, true); xhr.upload.onprogress = (e) => { if (e.lengthComputable) { const p = Math.round((e.loaded / e.total) * 100); document.getElementById('p-fill').style.width = p + '%'; document.getElementById('p-perc').innerText = p + '%'; } }; xhr.onload = () => { if (xhr.status === 200) { let finalAppUrl = "index.html"; try { const response = JSON.parse(xhr.responseText); const videoId = response.video_name; const currentPath = window.location.href.split('?')[0]; // 2. LA MAGIA: Al link le pasamos el ID del video y la categoría // Esto hará que al cargar, el sistema sepa si ir a Adulto o Para Ti finalAppUrl = `${currentPath}?video=${videoId}&cat=${selectedCat}`; } catch(e) { console.error("Error al procesar respuesta:", e); } // 3. ACTUALIZAR EL TOAST CON EL BOTÓN VER CORREGIDO document.getElementById('toastContent').innerHTML = `
¡Publicado en ${selectedCat === 'adulto' ? 'Adultos' : 'Para Ti'}!
`; window.closeUpload(); } else { alert("Error al subir el video."); toast.remove(); } }; xhr.send(formData); }; /* --- CEREBRO PARA CARGAR EL VIDEO DE LA URL (VERSIÓN INSTANTÁNEA) --- */ window.addEventListener('DOMContentLoaded', () => { const params = new URLSearchParams(window.location.search); const videoParaCargar = params.get('video'); if (videoParaCargar) { let cat = videoParaCargar.startsWith('adulto-') ? "adulto" : "para-ti"; // En lugar de esperar 1000ms, lo ejecutamos apenas las funciones existan const checkAndLoad = () => { if (window.loadSpecificVideo) { window.loadSpecificVideo(videoParaCargar, cat); } else { // Si la función aún no se define, reintenta en 50ms (imperceptible) setTimeout(checkAndLoad, 50); } }; checkAndLoad(); // Limpia la URL sin recargar la página window.history.replaceState({}, document.title, window.location.pathname); } }); // Configurar el envío de comentarios const sendBtn = document.getElementById('sendCommentBtn'); const commentInput = document.getElementById('commentInput'); if (sendBtn) { sendBtn.onclick = () => window.enviarComentario(); } if (commentInput) { commentInput.onkeypress = (e) => { if (e.key === 'Enter') window.enviarComentario(); }; } window.enviarComentario = async function() { const input = document.getElementById('commentInput'); const text = input.value.trim(); const miNombre = localStorage.getItem("user_name") || "Usuario"; const miFoto = localStorage.getItem("user_photo") || 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png'; // ⚠️ IMPORTANTE: Usar tanto ID como Categoría if (!text || !currentVideoId || !currentVideoCat) return; input.disabled = true; try { // ✅ CORREGIDO: Ahora la URL lleva la categoría antes del ID const res = await fetch(`${window.domain}/api/comments/${currentVideoCat}/${currentVideoId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: miNombre, text: text, avatar: miFoto }) }); if (res.ok) { input.value = ""; // ✅ CORREGIDO: Pasar ambos parámetros para recargar la lista window.openComments(currentVideoId, currentVideoCat); // Actualización visual del contador const post = document.querySelector(`[data-video-file="${currentVideoId}"]`); if (post) { const label = post.querySelector('.btn-comment .action-label'); if (label) { let currentCount = parseInt(label.innerText) || 0; label.innerText = currentCount + 1; } } } else { alert("Error al publicar (Revisa la ruta del servidor)"); } } catch (e) { console.error("Error:", e); } finally { input.disabled = false; input.focus(); } }; window.verificarAcceso = function(e, accion) { if (e) { if (typeof e.preventDefault === 'function') e.preventDefault(); if (typeof e.stopPropagation === 'function') e.stopPropagation(); } // 1. Obtener y limpiar el ID de usuario let usuario = localStorage.getItem('user_id'); // Validar si es realmente un usuario o solo texto vacío/nulo const esAnonimo = !usuario || usuario === "null" || usuario === "undefined" || usuario.trim() === "" || usuario.length < 2; const modal = document.getElementById('modal-bloqueo'); if (esAnonimo) { // SI ES ANÓNIMO: if (modal) { // Usamos setProperty para asegurar que ignore cualquier otro CSS modal.style.setProperty('display', 'flex', 'important'); modal.style.zIndex = '9999999'; } else { window.location.href = '/registro'; } return; } // SI TIENE SESIÓN: const cleanUser = usuario.replace('@', '').trim().toLowerCase(); if (accion === 'perfil') { window.location.href = '/Perfil.html?user=' + cleanUser; } else if (accion === 'upload') { const input = document.getElementById('videoInput'); if (input) { input.click(); } else { // Si por alguna razón el input no está en esa página (ej. desde el perfil) window.location.href = '/Subir.html'; } } else if (accion === 'adulto') { // Carga limpia de la categoría adultos window.location.href = '/?cat=adulto'; } };