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');
}
}