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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in new issue
Block a user