feat: dodanie możliwości przesyłania własnych plików na serwer (wszystkie formaty ffmpeg), odtwarzanie jednym kliknięciem z web i komenda /playfile w Discord

This commit is contained in:
mmacierzanka committed 2026-07-04 15:33:43 +02:00
1 parent 444b797cd0
commit dfd681be04
6 files changed
+395 -5

No files matched your search

+143
View File
@@ -42,6 +42,14 @@ const queueList = document.getElementById('queue-list');
const emptyQueueMsg = document.getElementById('empty-queue-msg');
const queueCountBadge = document.getElementById('queue-count');
const toastContainer = document.getElementById('toast-container');
const fileInput = document.getElementById('file-input');
const btnSelectFile = document.getElementById('btn-select-file');
const uploadProgressContainer = document.getElementById('upload-progress-container');
const uploadProgressFill = document.getElementById('upload-progress-fill');
const uploadStatusText = document.getElementById('upload-status-text');
const uploadsList = document.getElementById('uploads-list');
const emptyUploadsMsg = document.getElementById('empty-uploads-msg');
const uploadsCountBadge = document.getElementById('uploads-count');
// Progress Bar DOM Elements
const progressContainer = document.getElementById('progress-container');
@@ -100,6 +108,7 @@ function connectWebSocket() {
document.addEventListener('DOMContentLoaded', () => {
// Connect to WebSocket instead of polling
connectWebSocket();
fetchUploads();
// Start Smooth Progress Tracker (every 100ms)
setInterval(() => {
@@ -148,6 +157,12 @@ function bindEvents() {
// Text Channel Select
textChannelSelect.addEventListener('change', changeTextChannel);
// Upload Events
if (btnSelectFile && fileInput) {
btnSelectFile.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileUpload);
}
}
// Show Toast Notification
@@ -719,3 +734,131 @@ async function changeTextChannel() {
showToast('Nie udało się zapisać kanału powiadomień', 'error');
}
}
// Fetch Uploaded Files
async function fetchUploads() {
try {
const res = await fetch('/api/uploads');
if (!res.ok) return;
const data = await res.json();
renderUploads(data.files || []);
} catch (err) {
console.error('Błąd pobierania plików:', err);
}
}
// Render Uploaded Files
function renderUploads(files) {
if (!uploadsList) return;
const items = uploadsList.querySelectorAll('.queue-item');
items.forEach(el => el.remove());
if (!files || files.length === 0) {
emptyUploadsMsg.style.display = 'flex';
uploadsCountBadge.textContent = '0 plików';
return;
}
emptyUploadsMsg.style.display = 'none';
uploadsCountBadge.textContent = `${files.length} ${files.length === 1 ? 'plik' : files.length < 5 ? 'pliki' : 'plików'}`;
files.forEach(file => {
const li = document.createElement('li');
li.className = 'queue-item';
const safeFilename = file.filename.replace(/'/g, "\\'");
li.innerHTML = `
<div class="queue-index"><i data-lucide="file-audio"></i></div>
<div class="queue-track-info" style="flex: 1; min-width: 0;">
<div class="queue-track-title" title="${file.filename}" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${file.filename}</div>
<div class="queue-track-artist">${file.size_mb} MB</div>
</div>
<div class="queue-item-actions" style="display: flex; gap: 6px; align-items: center;">
<button class="btn btn-primary" style="padding: 4px 10px; font-size: 0.8rem; display: flex; align-items: center; gap: 4px;" onclick="playUploadedFile('${safeFilename}')" title="Dodaj do kolejki">
<i data-lucide="play"></i> Dodaj
</button>
<button class="btn-action-icon delete" onclick="deleteUploadedFile('${safeFilename}')" title="Usuń z serwera">
<i data-lucide="trash-2"></i>
</button>
</div>
`;
uploadsList.appendChild(li);
});
lucide.createIcons();
}
// Handle File Upload
async function handleFileUpload(e) {
const files = e.target.files;
if (!files || files.length === 0) return;
uploadProgressContainer.style.display = 'block';
for (let i = 0; i < files.length; i++) {
const file = files[i];
const formData = new FormData();
formData.append('file', file);
uploadStatusText.textContent = `Przesyłanie: ${file.name}...`;
uploadProgressFill.style.width = '50%';
try {
const res = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (!res.ok) {
const errData = await res.json();
throw new Error(errData.detail || 'Błąd przesyłania');
}
showToast(`Przesłano plik: ${file.name}`, 'success');
} catch (err) {
showToast(`Błąd przesyłania ${file.name}: ${err.message}`, 'error');
}
}
uploadProgressFill.style.width = '100%';
setTimeout(() => {
uploadProgressContainer.style.display = 'none';
uploadProgressFill.style.width = '0%';
fileInput.value = '';
fetchUploads();
}, 500);
}
// Play Uploaded File
async function playUploadedFile(filename) {
if (!currentStatus || !currentStatus.active_voice_channel) {
showToast('Bot musi być połączony z kanałem głosowym!', 'error');
return;
}
try {
const res = await fetch('/api/play_file', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: filename })
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Błąd odtwarzania pliku');
showToast(`Dodano plik do kolejki!`, 'success');
} catch (err) {
showToast(`Błąd odtwarzania: ${err.message}`, 'error');
}
}
// Delete Uploaded File
async function deleteUploadedFile(filename) {
try {
const res = await fetch(`/api/uploads/${encodeURIComponent(filename)}`, {
method: 'DELETE'
});
if (!res.ok) throw new Error('Błąd usuwania');
showToast('Usunięto plik z serwera', 'info');
fetchUploads();
} catch (err) {
showToast('Nie udało się usunąć pliku', 'error');
}
}
+30
View File
@@ -148,6 +148,36 @@
</form>
</div>
<!-- Uploaded Files Library Panel -->
<div class="glass-card uploads-panel">
<div class="queue-header">
<h2 class="card-title"><i data-lucide="folder-up"></i> Serwerowa baza plików</h2>
<span class="queue-count" id="uploads-count">0 plików</span>
</div>
<div class="upload-area" id="upload-dropzone" style="margin-bottom: 15px;">
<input type="file" id="file-input" style="display: none;" accept="*/*" multiple>
<button type="button" id="btn-select-file" class="btn btn-secondary" style="width: 100%; margin-bottom: 10px; display: flex; align-items: center; justify-content: center; gap: 8px;">
<i data-lucide="upload-cloud"></i> Prześlij plik wideo/audio na serwer
</button>
<div id="upload-progress-container" style="display: none; margin-bottom: 10px;">
<div class="progress-bar-bg">
<div class="progress-bar-fill" id="upload-progress-fill" style="width: 0%; background: var(--primary);"></div>
</div>
<span class="time-text" id="upload-status-text" style="display: block; text-align: center; margin-top: 4px;">Przesyłanie... 0%</span>
</div>
</div>
<div class="queue-list-container" style="max-height: 250px;">
<ul class="queue-list" id="uploads-list">
<div class="empty-queue-message" id="empty-uploads-msg">
<i data-lucide="folder-open"></i>
<p>Brak przesłanych plików na serwerze.</p>
</div>
</ul>
</div>
</div>
<!-- Queue List Panel -->
<div class="glass-card queue-panel">
<div class="queue-header">