Auto commit: 2026-02-17T18:55:52.856Z

This commit is contained in:
Flatlogic Bot 2026-02-17 18:55:52 +00:00
parent 9b189df875
commit 52fa755b06
4 changed files with 304 additions and 60 deletions

107
admin.php
View File

@ -151,6 +151,22 @@ $locations = $stmt->fetchAll();
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5><i class="bi bi-images text-success"></i> Moderación de Galería y Chat</h5>
<button class="btn btn-sm btn-outline-success" onclick="fetchChatImages()">Cargar Imágenes Recientes</button>
</div>
<div class="row" id="chat-images-container">
<div class="col-12 text-center text-secondary py-4">
Cargando imágenes del chat...
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card p-4">
@ -258,48 +274,69 @@ $locations = $stmt->fetchAll();
initChart();
async function fetchRequests() {
const tbody = document.getElementById('requests-body');
// ... (keeping existing logic)
}
async function fetchChatImages() {
const container = document.getElementById('chat-images-container');
try {
const response = await fetch('api/song_requests.php?status=all&limit=50');
const data = await response.json();
const response = await fetch('api/chat.php');
const messages = await response.json();
if (data.success) {
if (data.requests.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-secondary">No hay peticiones registradas</td></tr>';
return;
}
tbody.innerHTML = data.requests.map(req => `
<tr>
<td>${req.artist}</td>
<td>${req.song}</td>
<td><span class="badge bg-secondary">${req.requester}</span></td>
<td><small>${new Date(req.created_at).toLocaleString()}</small></td>
<td>
<span class="badge ${req.status === 'played' ? 'bg-success' : 'bg-warning text-dark'}">
${req.status === 'played' ? 'Reproducida' : 'Pendiente'}
</span>
</td>
<td>
<div class="btn-group btn-group-sm">
${req.status === 'pending' ? `
<button class="btn btn-success" onclick="updateStatus(${req.id}, 'mark_played')" title="Marcar como reproducida">
<i class="bi bi-play-fill"></i>
</button>
` : ''}
<button class="btn btn-danger" onclick="updateStatus(${req.id}, 'delete')" title="Eliminar">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
`).join('');
const images = messages.filter(m => m.type === 'image');
if (images.length === 0) {
container.innerHTML = '<div class="col-12 text-center text-secondary py-4">No hay imágenes recientes en el chat.</div>';
return;
}
container.innerHTML = images.map(img => `
<div class="col-md-3 mb-4">
<div class="card h-100 p-2" style="background: rgba(0,0,0,0.3);">
<img src="${img.message}" class="card-img-top rounded" style="height: 150px; object-fit: cover;">
<div class="card-body p-2">
<p class="small mb-1">Enviado por: <b>${img.username}</b></p>
<div class="input-group input-group-sm mb-2">
<input type="text" class="form-control bg-dark text-white border-secondary" placeholder="Leyenda..." id="caption-${img.id}">
</div>
<button class="btn btn-sm btn-success w-100" onclick="promoteToGallery(${img.id})">
<i class="bi bi-star-fill"></i> Publicar en Galería
</button>
</div>
</div>
</div>
`).join('');
} catch (error) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Error al cargar peticiones</td></tr>';
container.innerHTML = '<div class="col-12 text-center text-danger py-4">Error al cargar imágenes.</div>';
}
}
async function promoteToGallery(messageId) {
const caption = document.getElementById(`caption-${messageId}`).value;
try {
const response = await fetch('api/gallery.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'promote',
message_id: messageId,
caption: caption
})
});
const data = await response.json();
if (data.success) {
alert('Imagen publicada en la galería con éxito.');
fetchChatImages();
} else {
alert('Error: ' + data.error);
}
} catch (error) {
alert('Error de conexión.');
}
}
fetchChatImages();
async function fetchStats() {
const artistsDiv = document.getElementById('top-artists');
const songsDiv = document.getElementById('top-songs');

54
api/gallery.php Normal file
View File

@ -0,0 +1,54 @@
<?php
require_once __DIR__ . '/../db/config.php';
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET') {
try {
$stmt = db()->prepare("SELECT * FROM gallery ORDER BY created_at DESC LIMIT 50");
$stmt->execute();
$images = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'images' => $images]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
} elseif ($method === 'POST') {
// This endpoint will be used by admin to promote an image
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
if ($action === 'promote') {
$messageId = $input['message_id'] ?? 0;
$caption = $input['caption'] ?? '';
try {
// Get the image message
$stmt = db()->prepare("SELECT * FROM messages WHERE id = ? AND type = 'image'");
$stmt->execute([$messageId]);
$msg = $stmt->fetch();
if (!$msg) {
echo json_encode(['success' => false, 'error' => 'Imagen no encontrada']);
exit;
}
// Insert into gallery
$stmt = db()->prepare("INSERT INTO gallery (image_url, username, caption) VALUES (?, ?, ?)");
$stmt->execute([$msg['message'], $msg['username'], $caption]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
} elseif ($action === 'like') {
$id = $input['id'] ?? 0;
try {
db()->prepare("UPDATE gallery SET likes = likes + 1 WHERE id = ?")->execute([$id]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
}
}

View File

@ -0,0 +1,11 @@
-- Migration: Create gallery table
-- Description: Stores images promoted from chat to the community gallery.
CREATE TABLE IF NOT EXISTS gallery (
id INT AUTO_INCREMENT PRIMARY KEY,
image_url VARCHAR(255) NOT NULL,
username VARCHAR(100) NOT NULL,
caption VARCHAR(255) DEFAULT NULL,
likes INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

192
index.php
View File

@ -568,6 +568,104 @@ $twitter_link = "https://twitter.com/";
background-color: #00c853;
}
/* Gallery Styles */
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 10px;
margin-top: 1rem;
}
.gallery-item {
position: relative;
aspect-ratio: 1/1;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: transform 0.3s ease;
border: 1px solid rgba(255,255,255,0.1);
}
.gallery-item:hover {
transform: scale(1.05);
z-index: 2;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.gallery-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
padding: 8px;
font-size: 0.7rem;
opacity: 0;
transition: opacity 0.3s;
display: flex;
justify-content: space-between;
align-items: center;
}
.gallery-item:hover .gallery-overlay {
opacity: 1;
}
.gallery-username {
font-weight: bold;
color: #fff;
}
.gallery-likes {
color: #ff4081;
display: flex;
align-items: center;
gap: 3px;
}
/* Lightbox */
.lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.95);
z-index: 2000;
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 20px;
}
.lightbox-img {
max-width: 100%;
max-height: 80vh;
border-radius: 12px;
box-shadow: 0 0 30px rgba(0,0,0,0.5);
}
.lightbox-info {
margin-top: 20px;
text-align: center;
color: white;
}
.lightbox-close {
position: absolute;
top: 20px;
right: 20px;
font-size: 2rem;
color: white;
cursor: pointer;
}
/* Center Section: Featured Image */
.image-section {
width: 100%;
@ -1366,6 +1464,17 @@ $twitter_link = "https://twitter.com/";
</div>
</div>
<!-- Gallery Widget -->
<div class="glass-card mt-4" style="margin-top: 1.5rem;">
<h3 style="font-size: 1.2rem; margin-bottom: 0.5rem; color: #ffca28;">
<i class="bi bi-images"></i> GALERÍA COMUNIDAD
</h3>
<p style="font-size: 0.75rem; opacity: 0.7; margin-bottom: 1rem;">Fotos compartidas por nuestros oyentes.</p>
<div id="gallery-container" class="gallery-grid">
<div style="grid-column: 1/-1; text-align: center; padding: 2rem; opacity: 0.5; font-size: 0.85rem;">Cargando galería...</div>
</div>
</div>
<!-- Social Media Feed Widget -->
<div class="glass-card mt-4" style="margin-top: 1.5rem;">
<h3 style="font-size: 1.2rem; margin-bottom: 1rem; color: #f472b6;">
@ -1487,6 +1596,16 @@ $twitter_link = "https://twitter.com/";
</div>
</div>
<!-- Lightbox for Gallery -->
<div id="lightbox" class="lightbox" onclick="closeLightbox()">
<span class="lightbox-close" onclick="closeLightbox()">&times;</span>
<img id="lightbox-img" class="lightbox-img" src="" alt="Full view">
<div class="lightbox-info">
<h4 id="lightbox-username" style="margin: 0; color: var(--primary-color);"></h4>
<p id="lightbox-caption" style="margin: 5px 0 0; opacity: 0.8;"></p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
<audio id="radio-audio" src="https://listen.radioking.com/radio/828046/stream/897251" preload="auto" crossorigin="anonymous"></audio>
<audio id="welcome-sound" src="https://assets.mixkit.co/active_storage/sfx/2013/2013-preview.mp3" preload="auto"></audio>
@ -2378,36 +2497,59 @@ $twitter_link = "https://twitter.com/";
let progressInterval = null;
async function updateMetadata() {
// ... (keeping existing logic)
}
async function fetchGallery() {
const container = document.getElementById('gallery-container');
try {
fetchUpcomingTracks(); // Fetch next tracks in parallel
const response = await fetch('https://www.radioking.com/widgets/api/v1/radio/828046/track/current');
const response = await fetch('api/gallery.php');
const data = await response.json();
if (data && data.title) {
const title = data.title;
const artist = data.artist || 'Lili Records';
const album = data.album || '';
const coverUrl = data.cover || './assets/pasted-20260215-163754-def41f49.png';
if (data.success) {
if (data.images.length === 0) {
container.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 2rem; opacity: 0.5; font-size: 0.85rem;">Aún no hay fotos en la galería.</div>';
return;
}
const fullDisplay = title.includes(artist) ? title : `${artist} - ${title}`;
if (trackTitle.textContent !== title || (trackArtist && trackArtist.textContent !== artist)) {
// Clear existing progress interval
if (progressInterval) clearInterval(progressInterval);
container.innerHTML = data.images.map(img => `
<div class="gallery-item" onclick="openLightbox('${img.image_url}', '${img.username}', '${img.caption || ''}')">
<img src="${img.image_url}" alt="Foto de ${img.username}">
<div class="gallery-overlay">
<span class="gallery-username">@${img.username}</span>
<span class="gallery-likes"><i class="bi bi-heart-fill"></i> ${img.likes}</span>
</div>
</div>
`).join('');
}
} catch (error) {
container.innerHTML = '<div style="grid-column: 1/-1; text-align: center; padding: 2rem; opacity: 0.5; font-size: 0.85rem; color: #ff4444;">Error al cargar la galería.</div>';
}
}
function openLightbox(url, username, caption) {
const lightbox = document.getElementById('lightbox');
const img = document.getElementById('lightbox-img');
const userText = document.getElementById('lightbox-username');
const captionText = document.getElementById('lightbox-caption');
img.src = url;
userText.innerText = '@' + username;
captionText.innerText = caption;
lightbox.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
document.body.style.overflow = 'auto';
}
// Initial fetch
fetchGallery();
setInterval(fetchGallery, 60000); // Update every minute
// Add previous song to history before changing
if (trackTitle.textContent !== "Cargando stream..." && trackTitle.textContent !== "Lili Records Radio - En Vivo") {
const prevTrack = {
title: trackTitle.textContent + (trackArtist ? ' - ' + trackArtist.textContent : ''),
cover: trackCover.src,
likes: document.getElementById('like-count').innerText || '0'
};
if (recentTracks.length === 0 || recentTracks[0].title !== prevTrack.title) {
recentTracks.unshift(prevTrack);
if (recentTracks.length > 5) recentTracks.pop();
localStorage.setItem('recentTracks', JSON.stringify(recentTracks));
renderRecentTracks();
}
}
trackTitle.style.opacity = '0';