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:
6 files changed
+395
-5
No files matched your search
+143
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user