implemented explicit filter & other fixes

This commit is contained in:
architect.in.git
2025-03-23 09:35:15 -06:00
parent 81c79aa676
commit ed351ad8fc
12 changed files with 468 additions and 100 deletions

View File

@@ -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

View File

@@ -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')
@@ -92,6 +93,10 @@ def handle_config():
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)
@config_bp.route('/config', methods=['POST', 'PUT'])
@@ -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

View File

@@ -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;

View File

@@ -455,3 +455,48 @@ a:hover, a:focus {
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;
}

View File

@@ -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 = `

View File

@@ -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

View File

@@ -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;

View File

@@ -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
*/

View File

@@ -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.textContent = 'Queueing...';
downloadPlaylistAlbums(playlist)
.then(() => {
downloadAlbumsBtn.textContent = 'Queued!';
})
.catch(err => {
showError('Failed to queue album downloads: ' + (err?.message || 'Unknown error'));
downloadAlbumsBtn.disabled = false;
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.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 || ''}`;

View File

@@ -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

View File

@@ -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>`;

View File

@@ -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">