Merge pull request #530 from dlynas/feat/explicit-badges

feat: add explicit badges to discography modal and artist-detail cards
This commit is contained in:
BoulderBadgeDad 2026-05-13 15:04:14 -07:00 committed by GitHub
commit c77aa61fdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 40 additions and 5 deletions

View file

@ -212,6 +212,7 @@ class Album:
album_type: str
image_url: Optional[str] = None
external_urls: Optional[Dict[str, str]] = None
explicit: Optional[bool] = None
@classmethod
def from_deezer_album(cls, album_data: Dict[str, Any]) -> 'Album':
@ -243,7 +244,8 @@ class Album:
total_tracks=album_data.get('nb_tracks', 0),
album_type=album_type,
image_url=image_url,
external_urls=external_urls if external_urls else None
external_urls=external_urls if external_urls else None,
explicit=bool(album_data.get('explicit_lyrics', False)),
)

View file

@ -565,6 +565,7 @@ class HydrabaseClient:
album_type=item_type,
image_url=item.get('image_url'),
external_urls=ext_urls,
explicit=item.get('explicit'),
))
except Exception as e:
logger.debug(f"Skipping malformed Hydrabase artist album: {e}")

View file

@ -167,7 +167,8 @@ class Album:
album_type: str
image_url: Optional[str] = None
external_urls: Optional[Dict[str, str]] = None
explicit: Optional[bool] = None
@classmethod
def from_itunes_album(cls, album_data: Dict[str, Any]) -> 'Album':
# Get highest quality artwork
@ -209,7 +210,8 @@ class Album:
total_tracks=track_count,
album_type=album_type,
image_url=image_url,
external_urls=external_urls if external_urls else None
external_urls=external_urls if external_urls else None,
explicit=album_data.get('collectionExplicitness') == 'explicit',
)
@dataclass

View file

@ -150,6 +150,7 @@ def _build_discography_release_dict(release: Any, artist_id: str,
'image_url': typed_album.image_url,
'total_tracks': typed_album.total_tracks or 0,
'external_urls': typed_album.external_urls or {},
'explicit': typed_album.explicit,
}
release_id = _extract_lookup_value(release, 'id', 'album_id', 'release_id')
@ -168,6 +169,7 @@ def _build_discography_release_dict(release: Any, artist_id: str,
'image_url': _extract_lookup_value(release, 'image_url', 'thumb_url', 'cover_image'),
'total_tracks': _extract_lookup_value(release, 'total_tracks', default=0) or 0,
'external_urls': _extract_lookup_value(release, 'external_urls', default={}) or {},
'explicit': _extract_lookup_value(release, 'explicit'),
}
@ -436,6 +438,7 @@ def _build_artist_detail_release_card(release: Dict[str, Any],
'track_count': typed_album.total_tracks or 0,
'owned': None,
'track_completion': 'checking',
'explicit': typed_album.explicit,
}
if typed_album.release_date:
card['release_date'] = typed_album.release_date
@ -470,6 +473,7 @@ def _build_artist_detail_release_card(release: Dict[str, Any],
'track_count': _extract_lookup_value(release, 'track_count', 'total_tracks', default=0) or 0,
'owned': None,
'track_completion': 'checking',
'explicit': _extract_lookup_value(release, 'explicit'),
}
if release_date:

View file

@ -100,6 +100,8 @@ class Album:
label: Optional[str] = None # Record label / publisher
barcode: Optional[str] = None # UPC/EAN — Discogs/MusicBrainz only
explicit: Optional[bool] = None # True=explicit, False=clean, None=unknown
# Source provenance
source: str = '' # 'spotify' / 'itunes' / etc — set by converter
external_ids: Dict[str, str] = field(default_factory=dict)
@ -193,6 +195,8 @@ class Album:
release_date = release_date.split('T', 1)[0]
primary_genre = _str(raw.get('primaryGenreName'))
ce = _str(raw.get('collectionExplicitness'))
explicit = True if ce == 'explicit' else (False if ce in ('notExplicit', 'cleaned') else None)
return cls(
id=_str(raw.get('collectionId')),
name=name,
@ -203,6 +207,7 @@ class Album:
image_url=_itunes_artwork(raw.get('artworkUrl100')),
artist_id=artist_id,
genres=[primary_genre] if primary_genre else [],
explicit=explicit,
source='itunes',
external_ids=external_ids,
external_urls=external_urls,
@ -238,6 +243,8 @@ class Album:
if raw.get('link'):
external_urls['deezer'] = _str(raw['link'])
_el = raw.get('explicit_lyrics')
explicit = bool(_el) if _el is not None else None
return cls(
id=_str(raw.get('id')),
name=_str(raw.get('title')),
@ -251,6 +258,7 @@ class Album:
if isinstance(g, dict) and g.get('name')],
label=_str(raw.get('label')) or None,
barcode=external_ids.get('upc'),
explicit=explicit,
source='deezer',
external_ids=external_ids,
external_urls=external_urls,
@ -513,6 +521,8 @@ class Album:
if raw.get('soul_id'):
external_ids['soul'] = _str(raw['soul_id'])
_he = raw.get('explicit')
explicit = bool(_he) if _he is not None else None
return cls(
id=_str(raw.get('id')),
name=_str(raw.get('name') or raw.get('title')),
@ -522,6 +532,7 @@ class Album:
album_type=_str(raw.get('album_type'), default='album'),
image_url=_str(raw.get('image_url') or raw.get('thumb_url')) or None,
artist_id=_str(raw.get('artist_id')) or None,
explicit=explicit,
source='hydrabase',
external_ids=external_ids,
)

View file

@ -1859,7 +1859,7 @@ function createReleaseCard(release) {
content.className = "album-card-content";
const _esc = (s) => String(s || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
content.innerHTML = `
<div class="album-card-name" title="${_esc(release.title)}">${_esc(release.title)}</div>
<div class="album-card-name" title="${_esc(release.title)}">${_esc(release.title)}${release.explicit === true ? ' <span class="explicit-badge">E</span>' : ''}</div>
${yearText ? `<div class="album-card-year">${_esc(yearText)}</div>` : ''}
`;
card.appendChild(content);
@ -2507,7 +2507,7 @@ function _renderDiscogCard(release, index, completionData) {
${statusIcon ? `<span class="discog-card-status">${statusIcon}</span>` : ''}
</div>
<div class="discog-card-info">
<div class="discog-card-title">${_esc(albumName)}</div>
<div class="discog-card-title">${_esc(albumName)}${release.explicit === true ? ' <span class="explicit-badge">E</span>' : ''}</div>
<div class="discog-card-meta">${year}${year && tracks ? ' · ' : ''}${tracks ? tracks + ' tracks' : ''}</div>
</div>
<div class="discog-card-check"></div>

View file

@ -45985,6 +45985,21 @@ textarea.enhanced-meta-field-input {
.discog-card-meta { font-size: 11px; color: rgba(255,255,255,0.35); margin-top: 2px; }
.explicit-badge {
display: inline-block; font-size: 9px; font-weight: 800; letter-spacing: 0.04em;
padding: 1px 4px; border-radius: 3px; vertical-align: middle; margin-left: 5px;
background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.55);
border: 1px solid rgba(255,255,255,0.15); line-height: 1.4; flex-shrink: 0;
font-family: inherit;
}
.album-card-explicit {
display: inline-block; font-size: 9px; font-weight: 800; letter-spacing: 0.04em;
padding: 1px 4px; border-radius: 3px; vertical-align: middle; margin-left: 4px;
background: rgba(255,255,255,0.12); color: rgba(255,255,255,0.55);
border: 1px solid rgba(255,255,255,0.2); line-height: 1.4;
font-family: inherit; text-shadow: none;
}
.discog-card-check {
width: 20px; height: 20px; border-radius: 6px; flex-shrink: 0;
border: 2px solid rgba(255,255,255,0.15); transition: all 0.15s ease;