implemented explicit filter & other fixes
This commit is contained in:
@@ -21,6 +21,7 @@ services:
|
||||
- REDIS_DB=0
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- REDIS_BACKEND=redis://redis:6379/0
|
||||
- EXPLICIT_FILTER=false # Set to true to filter out explicit content
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
|
||||
config_bp = Blueprint('config_bp', __name__)
|
||||
CONFIG_PATH = Path('./config/main.json')
|
||||
@@ -91,6 +92,10 @@ def handle_config():
|
||||
for key, default_value in defaults.items():
|
||||
if key not in config:
|
||||
config[key] = default_value
|
||||
|
||||
# Get explicit filter setting from environment variable
|
||||
explicit_filter_env = os.environ.get('EXPLICIT_FILTER', 'false').lower()
|
||||
config['explicitFilter'] = explicit_filter_env in ('true', '1', 'yes', 'on')
|
||||
|
||||
return jsonify(config)
|
||||
|
||||
@@ -101,6 +106,13 @@ def update_config():
|
||||
if not isinstance(new_config, dict):
|
||||
return jsonify({"error": "Invalid config format"}), 400
|
||||
|
||||
# Get existing config to preserve environment-controlled values
|
||||
existing_config = get_config() or {}
|
||||
|
||||
# Preserve the explicitFilter setting from environment
|
||||
explicit_filter_env = os.environ.get('EXPLICIT_FILTER', 'false').lower()
|
||||
new_config['explicitFilter'] = explicit_filter_env in ('true', '1', 'yes', 'on')
|
||||
|
||||
if not save_config(new_config):
|
||||
return jsonify({"error": "Failed to save config"}), 500
|
||||
|
||||
|
||||
@@ -32,6 +32,39 @@ body {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Environment controlled setting styles */
|
||||
.env-controlled-setting {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #2a2a2a;
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.env-controlled-value {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.env-controlled-value.enabled {
|
||||
color: #1db954;
|
||||
}
|
||||
|
||||
.env-controlled-value.disabled {
|
||||
color: #ff5555;
|
||||
}
|
||||
|
||||
.env-controlled-badge {
|
||||
background-color: #555;
|
||||
color: white;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-left: 0.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Back button as floating icon - keep this for our floating button */
|
||||
.back-button.floating-icon {
|
||||
position: fixed;
|
||||
|
||||
@@ -454,4 +454,49 @@ a:hover, a:focus {
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add styles for explicit content filter */
|
||||
.explicit-filter-placeholder {
|
||||
background-color: #2a2a2a;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
margin: 1rem 0;
|
||||
text-align: center;
|
||||
color: #f5f5f5;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.explicit-filter-placeholder h2 {
|
||||
color: #ff5555;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.track-filtered {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.track-name.explicit-filtered {
|
||||
color: #ff5555;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Add styles for disabled download buttons */
|
||||
.download-btn--disabled {
|
||||
background-color: #666;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.download-btn--disabled:hover {
|
||||
background-color: #666;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Add styles for download note in artist view */
|
||||
.download-note {
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@@ -34,6 +34,27 @@ function renderAlbum(album) {
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
document.getElementById('error').classList.add('hidden');
|
||||
|
||||
// Check if album itself is marked explicit and filter is enabled
|
||||
const isExplicitFilterEnabled = downloadQueue.isExplicitFilterEnabled();
|
||||
if (isExplicitFilterEnabled && album.explicit) {
|
||||
// Show placeholder for explicit album
|
||||
const placeholderContent = `
|
||||
<div class="explicit-filter-placeholder">
|
||||
<h2>Explicit Content Filtered</h2>
|
||||
<p>This album contains explicit content and has been filtered based on your settings.</p>
|
||||
<p>The explicit content filter is controlled by environment variables.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const contentContainer = document.getElementById('album-header');
|
||||
if (contentContainer) {
|
||||
contentContainer.innerHTML = placeholderContent;
|
||||
contentContainer.classList.remove('hidden');
|
||||
}
|
||||
|
||||
return; // Stop rendering the actual album content
|
||||
}
|
||||
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
// Set album header info.
|
||||
@@ -75,6 +96,12 @@ function renderAlbum(album) {
|
||||
window.location.href = window.location.origin;
|
||||
});
|
||||
|
||||
// Check if any track in the album is explicit when filter is enabled
|
||||
let hasExplicitTrack = false;
|
||||
if (isExplicitFilterEnabled && album.tracks?.items) {
|
||||
hasExplicitTrack = album.tracks.items.some(track => track && track.explicit);
|
||||
}
|
||||
|
||||
// Create (if needed) the Download Album Button.
|
||||
let downloadAlbumBtn = document.getElementById('downloadAlbumBtn');
|
||||
if (!downloadAlbumBtn) {
|
||||
@@ -85,24 +112,32 @@ function renderAlbum(album) {
|
||||
document.getElementById('album-header').appendChild(downloadAlbumBtn);
|
||||
}
|
||||
|
||||
downloadAlbumBtn.addEventListener('click', () => {
|
||||
// Remove any other download buttons (keeping the full-album button in place).
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
if (btn.id !== 'downloadAlbumBtn') btn.remove();
|
||||
});
|
||||
|
||||
if (isExplicitFilterEnabled && hasExplicitTrack) {
|
||||
// Disable the album download button and display a message explaining why
|
||||
downloadAlbumBtn.disabled = true;
|
||||
downloadAlbumBtn.textContent = 'Queueing...';
|
||||
|
||||
downloadWholeAlbum(album)
|
||||
.then(() => {
|
||||
downloadAlbumBtn.textContent = 'Queued!';
|
||||
})
|
||||
.catch(err => {
|
||||
showError('Failed to queue album download: ' + (err?.message || 'Unknown error'));
|
||||
downloadAlbumBtn.disabled = false;
|
||||
downloadAlbumBtn.classList.add('download-btn--disabled');
|
||||
downloadAlbumBtn.innerHTML = `<span title="Cannot download entire album because it contains explicit tracks">Album Contains Explicit Tracks</span>`;
|
||||
} else {
|
||||
// Normal behavior when no explicit tracks are present
|
||||
downloadAlbumBtn.addEventListener('click', () => {
|
||||
// Remove any other download buttons (keeping the full-album button in place).
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
if (btn.id !== 'downloadAlbumBtn') btn.remove();
|
||||
});
|
||||
});
|
||||
|
||||
downloadAlbumBtn.disabled = true;
|
||||
downloadAlbumBtn.textContent = 'Queueing...';
|
||||
|
||||
downloadWholeAlbum(album)
|
||||
.then(() => {
|
||||
downloadAlbumBtn.textContent = 'Queued!';
|
||||
})
|
||||
.catch(err => {
|
||||
showError('Failed to queue album download: ' + (err?.message || 'Unknown error'));
|
||||
downloadAlbumBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Render each track.
|
||||
const tracksList = document.getElementById('tracks-list');
|
||||
@@ -112,6 +147,23 @@ function renderAlbum(album) {
|
||||
album.tracks.items.forEach((track, index) => {
|
||||
if (!track) return; // Skip null or undefined tracks
|
||||
|
||||
// Skip explicit tracks if filter is enabled
|
||||
if (isExplicitFilterEnabled && track.explicit) {
|
||||
// Add a placeholder for filtered explicit tracks
|
||||
const trackElement = document.createElement('div');
|
||||
trackElement.className = 'track track-filtered';
|
||||
trackElement.innerHTML = `
|
||||
<div class="track-number">${index + 1}</div>
|
||||
<div class="track-info">
|
||||
<div class="track-name explicit-filtered">Explicit Content Filtered</div>
|
||||
<div class="track-artist">This track is not shown due to explicit content filter settings</div>
|
||||
</div>
|
||||
<div class="track-duration">--:--</div>
|
||||
`;
|
||||
tracksList.appendChild(trackElement);
|
||||
return;
|
||||
}
|
||||
|
||||
const trackElement = document.createElement('div');
|
||||
trackElement.className = 'track';
|
||||
trackElement.innerHTML = `
|
||||
|
||||
@@ -32,6 +32,9 @@ function renderArtist(artistData, artistId) {
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
document.getElementById('error').classList.add('hidden');
|
||||
|
||||
// Check if explicit filter is enabled
|
||||
const isExplicitFilterEnabled = downloadQueue.isExplicitFilterEnabled();
|
||||
|
||||
const firstAlbum = artistData.items?.[0] || {};
|
||||
const artistName = firstAlbum?.artists?.[0]?.name || 'Unknown Artist';
|
||||
const artistImage = firstAlbum?.images?.[0]?.url || '/static/images/placeholder.jpg';
|
||||
@@ -65,35 +68,50 @@ function renderArtist(artistData, artistId) {
|
||||
document.getElementById('artist-header').appendChild(downloadArtistBtn);
|
||||
}
|
||||
|
||||
downloadArtistBtn.addEventListener('click', () => {
|
||||
// Optionally remove other download buttons from individual albums.
|
||||
document.querySelectorAll('.download-btn:not(#downloadArtistBtn)').forEach(btn => btn.remove());
|
||||
// When explicit filter is enabled, disable all download buttons
|
||||
if (isExplicitFilterEnabled) {
|
||||
// Disable the artist download button and display a message explaining why
|
||||
downloadArtistBtn.disabled = true;
|
||||
downloadArtistBtn.textContent = 'Queueing...';
|
||||
downloadArtistBtn.classList.add('download-btn--disabled');
|
||||
downloadArtistBtn.innerHTML = `<span title="Direct artist downloads are restricted when explicit filter is enabled. Please visit individual album pages.">Downloads Restricted</span>`;
|
||||
} else {
|
||||
// Normal behavior when explicit filter is not enabled
|
||||
downloadArtistBtn.addEventListener('click', () => {
|
||||
// Optionally remove other download buttons from individual albums.
|
||||
document.querySelectorAll('.download-btn:not(#downloadArtistBtn)').forEach(btn => btn.remove());
|
||||
downloadArtistBtn.disabled = true;
|
||||
downloadArtistBtn.textContent = 'Queueing...';
|
||||
|
||||
// Queue the entire discography (albums, singles, compilations, and appears_on)
|
||||
// Use our local startDownload function instead of downloadQueue.startArtistDownload
|
||||
startDownload(
|
||||
artistUrl,
|
||||
'artist',
|
||||
{ name: artistName, artist: artistName },
|
||||
'album,single,compilation'
|
||||
)
|
||||
.then(() => {
|
||||
downloadArtistBtn.textContent = 'Artist queued';
|
||||
// Make the queue visible after queueing
|
||||
downloadQueue.toggleVisibility(true);
|
||||
})
|
||||
.catch(err => {
|
||||
downloadArtistBtn.textContent = 'Download All Discography';
|
||||
downloadArtistBtn.disabled = false;
|
||||
showError('Failed to queue artist download: ' + (err?.message || 'Unknown error'));
|
||||
});
|
||||
});
|
||||
// Queue the entire discography (albums, singles, compilations, and appears_on)
|
||||
// Use our local startDownload function instead of downloadQueue.startArtistDownload
|
||||
startDownload(
|
||||
artistUrl,
|
||||
'artist',
|
||||
{ name: artistName, artist: artistName },
|
||||
'album,single,compilation'
|
||||
)
|
||||
.then(() => {
|
||||
downloadArtistBtn.textContent = 'Artist queued';
|
||||
// Make the queue visible after queueing
|
||||
downloadQueue.toggleVisibility(true);
|
||||
})
|
||||
.catch(err => {
|
||||
downloadArtistBtn.textContent = 'Download All Discography';
|
||||
downloadArtistBtn.disabled = false;
|
||||
showError('Failed to queue artist download: ' + (err?.message || 'Unknown error'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Group albums by type (album, single, compilation, etc.)
|
||||
const albumGroups = (artistData.items || []).reduce((groups, album) => {
|
||||
if (!album) return groups;
|
||||
|
||||
// Skip explicit albums if filter is enabled
|
||||
if (isExplicitFilterEnabled && album.explicit) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
const type = (album.album_type || 'unknown').toLowerCase();
|
||||
if (!groups[type]) groups[type] = [];
|
||||
groups[type].push(album);
|
||||
@@ -108,14 +126,22 @@ function renderArtist(artistData, artistId) {
|
||||
const groupSection = document.createElement('section');
|
||||
groupSection.className = 'album-group';
|
||||
|
||||
groupSection.innerHTML = `
|
||||
<div class="album-group-header">
|
||||
// If explicit filter is enabled, don't show the group download button
|
||||
const groupHeaderHTML = isExplicitFilterEnabled ?
|
||||
`<div class="album-group-header">
|
||||
<h3>${capitalize(groupType)}s</h3>
|
||||
<div class="download-note">Visit album pages to download content</div>
|
||||
</div>` :
|
||||
`<div class="album-group-header">
|
||||
<h3>${capitalize(groupType)}s</h3>
|
||||
<button class="download-btn download-btn--main group-download-btn"
|
||||
data-group-type="${groupType}">
|
||||
Download All ${capitalize(groupType)}s
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
groupSection.innerHTML = `
|
||||
${groupHeaderHTML}
|
||||
<div class="albums-list"></div>
|
||||
`;
|
||||
|
||||
@@ -125,24 +151,41 @@ function renderArtist(artistData, artistId) {
|
||||
|
||||
const albumElement = document.createElement('div');
|
||||
albumElement.className = 'album-card';
|
||||
albumElement.innerHTML = `
|
||||
<a href="/album/${album.id || ''}" class="album-link">
|
||||
<img src="${album.images?.[1]?.url || album.images?.[0]?.url || '/static/images/placeholder.jpg'}"
|
||||
alt="Album cover"
|
||||
class="album-cover">
|
||||
</a>
|
||||
<div class="album-info">
|
||||
<div class="album-title">${album.name || 'Unknown Album'}</div>
|
||||
<div class="album-artist">${album.artists?.map(a => a?.name || 'Unknown Artist').join(', ') || 'Unknown Artist'}</div>
|
||||
</div>
|
||||
<button class="download-btn download-btn--circle"
|
||||
data-url="${album.external_urls?.spotify || ''}"
|
||||
data-type="${album.album_type || 'album'}"
|
||||
data-name="${album.name || 'Unknown Album'}"
|
||||
title="Download">
|
||||
<img src="/static/images/download.svg" alt="Download">
|
||||
</button>
|
||||
`;
|
||||
|
||||
// Create album card with or without download button based on explicit filter setting
|
||||
if (isExplicitFilterEnabled) {
|
||||
albumElement.innerHTML = `
|
||||
<a href="/album/${album.id || ''}" class="album-link">
|
||||
<img src="${album.images?.[1]?.url || album.images?.[0]?.url || '/static/images/placeholder.jpg'}"
|
||||
alt="Album cover"
|
||||
class="album-cover">
|
||||
</a>
|
||||
<div class="album-info">
|
||||
<div class="album-title">${album.name || 'Unknown Album'}</div>
|
||||
<div class="album-artist">${album.artists?.map(a => a?.name || 'Unknown Artist').join(', ') || 'Unknown Artist'}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
albumElement.innerHTML = `
|
||||
<a href="/album/${album.id || ''}" class="album-link">
|
||||
<img src="${album.images?.[1]?.url || album.images?.[0]?.url || '/static/images/placeholder.jpg'}"
|
||||
alt="Album cover"
|
||||
class="album-cover">
|
||||
</a>
|
||||
<div class="album-info">
|
||||
<div class="album-title">${album.name || 'Unknown Album'}</div>
|
||||
<div class="album-artist">${album.artists?.map(a => a?.name || 'Unknown Artist').join(', ') || 'Unknown Artist'}</div>
|
||||
</div>
|
||||
<button class="download-btn download-btn--circle"
|
||||
data-url="${album.external_urls?.spotify || ''}"
|
||||
data-type="${album.album_type || 'album'}"
|
||||
data-name="${album.name || 'Unknown Album'}"
|
||||
title="Download">
|
||||
<img src="/static/images/download.svg" alt="Download">
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
albumsContainer.appendChild(albumElement);
|
||||
});
|
||||
|
||||
@@ -152,9 +195,12 @@ function renderArtist(artistData, artistId) {
|
||||
document.getElementById('artist-header').classList.remove('hidden');
|
||||
document.getElementById('albums-container').classList.remove('hidden');
|
||||
|
||||
attachDownloadListeners();
|
||||
// Pass the artist URL and name so the group buttons can use the artist download function
|
||||
attachGroupDownloadListeners(artistUrl, artistName);
|
||||
// Only attach download listeners if explicit filter is not enabled
|
||||
if (!isExplicitFilterEnabled) {
|
||||
attachDownloadListeners();
|
||||
// Pass the artist URL and name so the group buttons can use the artist download function
|
||||
attachGroupDownloadListeners(artistUrl, artistName);
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners for group downloads using the artist download function
|
||||
|
||||
@@ -663,11 +663,31 @@ async function loadConfig() {
|
||||
document.getElementById('retryDelaySeconds').value = savedConfig.retryDelaySeconds || '5';
|
||||
document.getElementById('retryDelayIncrease').value = savedConfig.retry_delay_increase || '5';
|
||||
document.getElementById('tracknumPaddingToggle').checked = savedConfig.tracknum_padding === undefined ? true : !!savedConfig.tracknum_padding;
|
||||
|
||||
// Update explicit filter status
|
||||
updateExplicitFilterStatus(savedConfig.explicitFilter);
|
||||
} catch (error) {
|
||||
showConfigError('Error loading config: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updateExplicitFilterStatus(isEnabled) {
|
||||
const statusElement = document.getElementById('explicitFilterStatus');
|
||||
if (statusElement) {
|
||||
// Remove existing classes
|
||||
statusElement.classList.remove('enabled', 'disabled');
|
||||
|
||||
// Add appropriate class and text based on whether filter is enabled
|
||||
if (isEnabled) {
|
||||
statusElement.textContent = 'Enabled';
|
||||
statusElement.classList.add('enabled');
|
||||
} else {
|
||||
statusElement.textContent = 'Disabled';
|
||||
statusElement.classList.add('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showConfigError(message) {
|
||||
const errorDiv = document.getElementById('configError');
|
||||
errorDiv.textContent = message;
|
||||
|
||||
@@ -101,8 +101,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (data && data.items && data.items.length > 0) {
|
||||
resultsContainer.innerHTML = '';
|
||||
|
||||
// Filter out null/undefined items first
|
||||
const validItems = data.items.filter(item => item);
|
||||
// Filter out items with null/undefined essential display parameters
|
||||
const validItems = filterValidItems(data.items, searchType.value);
|
||||
|
||||
if (validItems.length === 0) {
|
||||
// No valid items found after filtering
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="empty-search-results">
|
||||
<p>No valid results found for "${query}"</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
validItems.forEach((item, index) => {
|
||||
const cardElement = createResultCard(item, searchType.value, index);
|
||||
@@ -137,6 +147,75 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out items with null/undefined essential display parameters based on search type
|
||||
*/
|
||||
function filterValidItems(items, type) {
|
||||
if (!items) return [];
|
||||
|
||||
return items.filter(item => {
|
||||
// Skip null/undefined items
|
||||
if (!item) return false;
|
||||
|
||||
// Skip explicit content if filter is enabled
|
||||
if (downloadQueue.isExplicitFilterEnabled() && item.explicit === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check essential parameters based on search type
|
||||
switch (type) {
|
||||
case 'track':
|
||||
// For tracks, we need name, artists, and album
|
||||
return (
|
||||
item.name &&
|
||||
item.artists &&
|
||||
item.artists.length > 0 &&
|
||||
item.artists[0] &&
|
||||
item.artists[0].name &&
|
||||
item.album &&
|
||||
item.album.name &&
|
||||
item.external_urls &&
|
||||
item.external_urls.spotify
|
||||
);
|
||||
|
||||
case 'album':
|
||||
// For albums, we need name, artists, and cover image
|
||||
return (
|
||||
item.name &&
|
||||
item.artists &&
|
||||
item.artists.length > 0 &&
|
||||
item.artists[0] &&
|
||||
item.artists[0].name &&
|
||||
item.external_urls &&
|
||||
item.external_urls.spotify
|
||||
);
|
||||
|
||||
case 'playlist':
|
||||
// For playlists, we need name, owner, and tracks
|
||||
return (
|
||||
item.name &&
|
||||
item.owner &&
|
||||
item.owner.display_name &&
|
||||
item.tracks &&
|
||||
item.external_urls &&
|
||||
item.external_urls.spotify
|
||||
);
|
||||
|
||||
case 'artist':
|
||||
// For artists, we need name
|
||||
return (
|
||||
item.name &&
|
||||
item.external_urls &&
|
||||
item.external_urls.spotify
|
||||
);
|
||||
|
||||
default:
|
||||
// Default case - just check if the item exists
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches download handlers to result cards
|
||||
*/
|
||||
|
||||
@@ -39,6 +39,9 @@ function renderPlaylist(playlist) {
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
document.getElementById('error').classList.add('hidden');
|
||||
|
||||
// Check if explicit filter is enabled
|
||||
const isExplicitFilterEnabled = downloadQueue.isExplicitFilterEnabled();
|
||||
|
||||
// Update header info
|
||||
document.getElementById('playlist-name').textContent = playlist.name || 'Unknown Playlist';
|
||||
document.getElementById('playlist-owner').textContent = `By ${playlist.owner?.display_name || 'Unknown User'}`;
|
||||
@@ -67,6 +70,12 @@ function renderPlaylist(playlist) {
|
||||
window.location.href = window.location.origin;
|
||||
});
|
||||
|
||||
// Check if any track in the playlist is explicit when filter is enabled
|
||||
let hasExplicitTrack = false;
|
||||
if (isExplicitFilterEnabled && playlist.tracks?.items) {
|
||||
hasExplicitTrack = playlist.tracks.items.some(item => item?.track && item.track.explicit);
|
||||
}
|
||||
|
||||
// --- Add "Download Whole Playlist" Button ---
|
||||
let downloadPlaylistBtn = document.getElementById('downloadPlaylistBtn');
|
||||
if (!downloadPlaylistBtn) {
|
||||
@@ -80,26 +89,6 @@ function renderPlaylist(playlist) {
|
||||
headerContainer.appendChild(downloadPlaylistBtn);
|
||||
}
|
||||
}
|
||||
downloadPlaylistBtn.addEventListener('click', () => {
|
||||
// Remove individual track download buttons (but leave the whole playlist button).
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
if (btn.id !== 'downloadPlaylistBtn') {
|
||||
btn.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Disable the whole playlist button to prevent repeated clicks.
|
||||
downloadPlaylistBtn.disabled = true;
|
||||
downloadPlaylistBtn.textContent = 'Queueing...';
|
||||
|
||||
// Initiate the playlist download.
|
||||
downloadWholePlaylist(playlist).then(() => {
|
||||
downloadPlaylistBtn.textContent = 'Queued!';
|
||||
}).catch(err => {
|
||||
showError('Failed to queue playlist download: ' + (err?.message || 'Unknown error'));
|
||||
downloadPlaylistBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
// --- Add "Download Playlist's Albums" Button ---
|
||||
let downloadAlbumsBtn = document.getElementById('downloadAlbumsBtn');
|
||||
@@ -114,24 +103,58 @@ function renderPlaylist(playlist) {
|
||||
headerContainer.appendChild(downloadAlbumsBtn);
|
||||
}
|
||||
}
|
||||
downloadAlbumsBtn.addEventListener('click', () => {
|
||||
// Remove individual track download buttons (but leave this album button).
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
if (btn.id !== 'downloadAlbumsBtn') btn.remove();
|
||||
|
||||
if (isExplicitFilterEnabled && hasExplicitTrack) {
|
||||
// Disable both playlist buttons and display messages explaining why
|
||||
downloadPlaylistBtn.disabled = true;
|
||||
downloadPlaylistBtn.classList.add('download-btn--disabled');
|
||||
downloadPlaylistBtn.innerHTML = `<span title="Cannot download entire playlist because it contains explicit tracks">Playlist Contains Explicit Tracks</span>`;
|
||||
|
||||
downloadAlbumsBtn.disabled = true;
|
||||
downloadAlbumsBtn.classList.add('download-btn--disabled');
|
||||
downloadAlbumsBtn.innerHTML = `<span title="Cannot download albums from this playlist because it contains explicit tracks">Albums Access Restricted</span>`;
|
||||
} else {
|
||||
// Normal behavior when no explicit tracks are present
|
||||
downloadPlaylistBtn.addEventListener('click', () => {
|
||||
// Remove individual track download buttons (but leave the whole playlist button).
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
if (btn.id !== 'downloadPlaylistBtn') {
|
||||
btn.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Disable the whole playlist button to prevent repeated clicks.
|
||||
downloadPlaylistBtn.disabled = true;
|
||||
downloadPlaylistBtn.textContent = 'Queueing...';
|
||||
|
||||
// Initiate the playlist download.
|
||||
downloadWholePlaylist(playlist).then(() => {
|
||||
downloadPlaylistBtn.textContent = 'Queued!';
|
||||
}).catch(err => {
|
||||
showError('Failed to queue playlist download: ' + (err?.message || 'Unknown error'));
|
||||
downloadPlaylistBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
downloadAlbumsBtn.disabled = true;
|
||||
downloadAlbumsBtn.textContent = 'Queueing...';
|
||||
|
||||
downloadPlaylistAlbums(playlist)
|
||||
.then(() => {
|
||||
downloadAlbumsBtn.textContent = 'Queued!';
|
||||
})
|
||||
.catch(err => {
|
||||
showError('Failed to queue album downloads: ' + (err?.message || 'Unknown error'));
|
||||
downloadAlbumsBtn.disabled = false;
|
||||
downloadAlbumsBtn.addEventListener('click', () => {
|
||||
// Remove individual track download buttons (but leave this album button).
|
||||
document.querySelectorAll('.download-btn').forEach(btn => {
|
||||
if (btn.id !== 'downloadAlbumsBtn') btn.remove();
|
||||
});
|
||||
});
|
||||
|
||||
downloadAlbumsBtn.disabled = true;
|
||||
downloadAlbumsBtn.textContent = 'Queueing...';
|
||||
|
||||
downloadPlaylistAlbums(playlist)
|
||||
.then(() => {
|
||||
downloadAlbumsBtn.textContent = 'Queued!';
|
||||
})
|
||||
.catch(err => {
|
||||
showError('Failed to queue album downloads: ' + (err?.message || 'Unknown error'));
|
||||
downloadAlbumsBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Render tracks list
|
||||
const tracksList = document.getElementById('tracks-list');
|
||||
@@ -144,6 +167,25 @@ function renderPlaylist(playlist) {
|
||||
if (!item || !item.track) return; // Skip null/undefined tracks
|
||||
|
||||
const track = item.track;
|
||||
|
||||
// Skip explicit tracks if filter is enabled
|
||||
if (isExplicitFilterEnabled && track.explicit) {
|
||||
// Add a placeholder for filtered explicit tracks
|
||||
const trackElement = document.createElement('div');
|
||||
trackElement.className = 'track track-filtered';
|
||||
trackElement.innerHTML = `
|
||||
<div class="track-number">${index + 1}</div>
|
||||
<div class="track-info">
|
||||
<div class="track-name explicit-filtered">Explicit Content Filtered</div>
|
||||
<div class="track-artist">This track is not shown due to explicit content filter settings</div>
|
||||
</div>
|
||||
<div class="track-album">Not available</div>
|
||||
<div class="track-duration">--:--</div>
|
||||
`;
|
||||
tracksList.appendChild(trackElement);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create links for track, artist, and album using their IDs.
|
||||
const trackLink = `/track/${track.id || ''}`;
|
||||
const artistLink = `/artist/${track.artists?.[0]?.id || ''}`;
|
||||
|
||||
@@ -1244,6 +1244,11 @@ class DownloadQueue {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a method to check if explicit filter is enabled
|
||||
isExplicitFilterEnabled() {
|
||||
return !!this.currentConfig.explicitFilter;
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
|
||||
@@ -40,6 +40,28 @@ function renderTrack(track) {
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
document.getElementById('error').classList.add('hidden');
|
||||
|
||||
// Check if track is explicit and if explicit filter is enabled
|
||||
if (track.explicit && downloadQueue.isExplicitFilterEnabled()) {
|
||||
// Show placeholder for explicit content
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
|
||||
const placeholderContent = `
|
||||
<div class="explicit-filter-placeholder">
|
||||
<h2>Explicit Content Filtered</h2>
|
||||
<p>This track contains explicit content and has been filtered based on your settings.</p>
|
||||
<p>The explicit content filter is controlled by environment variables.</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const contentContainer = document.getElementById('track-header');
|
||||
if (contentContainer) {
|
||||
contentContainer.innerHTML = placeholderContent;
|
||||
contentContainer.classList.remove('hidden');
|
||||
}
|
||||
|
||||
return; // Stop rendering the actual track content
|
||||
}
|
||||
|
||||
// Update track information fields.
|
||||
document.getElementById('track-name').innerHTML =
|
||||
`<a href="/track/${track.id || ''}" title="View track details">${track.name || 'Unknown Track'}</a>`;
|
||||
|
||||
@@ -53,6 +53,17 @@
|
||||
<option value="FLAC">FLAC (premium)</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Explicit Filter Status -->
|
||||
<div class="config-item">
|
||||
<label>Explicit Content Filter:</label>
|
||||
<div class="env-controlled-setting">
|
||||
<span id="explicitFilterStatus" class="env-controlled-value">Loading...</span>
|
||||
<div class="env-controlled-badge">ENV</div>
|
||||
</div>
|
||||
<div class="setting-description">
|
||||
Filter explicit content. Controlled by environment variable EXPLICIT_FILTER.
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Download Fallback:</label>
|
||||
<label class="switch">
|
||||
|
||||
Reference in New Issue
Block a user