diff --git a/docker-compose.yaml b/docker-compose.yaml
index 3d21707..51cf668 100755
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -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
diff --git a/routes/config.py b/routes/config.py
index fb46c07..de38a76 100644
--- a/routes/config.py
+++ b/routes/config.py
@@ -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
diff --git a/static/css/config/config.css b/static/css/config/config.css
index 7c4959c..8770f46 100644
--- a/static/css/config/config.css
+++ b/static/css/config/config.css
@@ -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;
diff --git a/static/css/main/base.css b/static/css/main/base.css
index 1f71df9..a933b44 100644
--- a/static/css/main/base.css
+++ b/static/css/main/base.css
@@ -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;
}
\ No newline at end of file
diff --git a/static/js/album.js b/static/js/album.js
index b923132..615ddd3 100644
--- a/static/js/album.js
+++ b/static/js/album.js
@@ -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 = `
+
+
Explicit Content Filtered
+
This album contains explicit content and has been filtered based on your settings.
+
The explicit content filter is controlled by environment variables.
+
+ `;
+
+ 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 = `Album Contains Explicit Tracks `;
+ } 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 = `
+ ${index + 1}
+
+
Explicit Content Filtered
+
This track is not shown due to explicit content filter settings
+
+ --:--
+ `;
+ tracksList.appendChild(trackElement);
+ return;
+ }
+
const trackElement = document.createElement('div');
trackElement.className = 'track';
trackElement.innerHTML = `
diff --git a/static/js/artist.js b/static/js/artist.js
index 627473f..34c2d4e 100644
--- a/static/js/artist.js
+++ b/static/js/artist.js
@@ -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 = `Downloads Restricted `;
+ } 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 = `
- `;
+
+ groupSection.innerHTML = `
+ ${groupHeaderHTML}
`;
@@ -125,24 +151,41 @@ function renderArtist(artistData, artistId) {
const albumElement = document.createElement('div');
albumElement.className = 'album-card';
- albumElement.innerHTML = `
-
-
-
-
-
${album.name || 'Unknown Album'}
-
${album.artists?.map(a => a?.name || 'Unknown Artist').join(', ') || 'Unknown Artist'}
-
-
-
-
- `;
+
+ // Create album card with or without download button based on explicit filter setting
+ if (isExplicitFilterEnabled) {
+ albumElement.innerHTML = `
+
+
+
+
+
${album.name || 'Unknown Album'}
+
${album.artists?.map(a => a?.name || 'Unknown Artist').join(', ') || 'Unknown Artist'}
+
+ `;
+ } else {
+ albumElement.innerHTML = `
+
+
+
+
+
${album.name || 'Unknown Album'}
+
${album.artists?.map(a => a?.name || 'Unknown Artist').join(', ') || 'Unknown Artist'}
+
+
+
+
+ `;
+ }
+
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
diff --git a/static/js/config.js b/static/js/config.js
index 4cc0c51..706b9b6 100644
--- a/static/js/config.js
+++ b/static/js/config.js
@@ -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;
diff --git a/static/js/main.js b/static/js/main.js
index f5e2705..6b5af92 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -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 = `
+
+
No valid results found for "${query}"
+
+ `;
+ 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
*/
diff --git a/static/js/playlist.js b/static/js/playlist.js
index 010c860..bf57266 100644
--- a/static/js/playlist.js
+++ b/static/js/playlist.js
@@ -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 = `Playlist Contains Explicit Tracks `;
+
+ downloadAlbumsBtn.disabled = true;
+ downloadAlbumsBtn.classList.add('download-btn--disabled');
+ downloadAlbumsBtn.innerHTML = `Albums Access Restricted `;
+ } 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 = `
+ ${index + 1}
+
+
Explicit Content Filtered
+
This track is not shown due to explicit content filter settings
+
+ Not available
+ --:--
+ `;
+ 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 || ''}`;
diff --git a/static/js/queue.js b/static/js/queue.js
index b2e61c6..8a58441 100644
--- a/static/js/queue.js
+++ b/static/js/queue.js
@@ -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
diff --git a/static/js/track.js b/static/js/track.js
index 3500d55..f62696e 100644
--- a/static/js/track.js
+++ b/static/js/track.js
@@ -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 = `
+
+
Explicit Content Filtered
+
This track contains explicit content and has been filtered based on your settings.
+
The explicit content filter is controlled by environment variables.
+
+ `;
+
+ 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 =
`${track.name || 'Unknown Track'} `;
diff --git a/templates/config.html b/templates/config.html
index 23e93bd..4a0cf7c 100644
--- a/templates/config.html
+++ b/templates/config.html
@@ -53,6 +53,17 @@
FLAC (premium)
+
+
+
Explicit Content Filter:
+
+
+ Filter explicit content. Controlled by environment variable EXPLICIT_FILTER.
+
+
Download Fallback: