From ceb6ca7406af8e5025309e79076c0aca649a2327 Mon Sep 17 00:00:00 2001 From: Oliver Salzburg Date: Sat, 21 Mar 2026 08:38:59 +0100 Subject: [PATCH] feat: Provide quality indication with release date Wraps the provided release date with additional information on the assumed quality of the date. This is primarily used to indicate when a release predates the launch date of the platform. This change only expands the data structure, without making further use of the newly available information. This was intentionally left open to be resolved in a future change. Contributes to: #101 --- harmonizer/types.ts | 4 +- musicbrainz/seeding.ts | 2 +- .../Bandcamp/__snapshots__/mod.test.ts.snap | 81 ++++++++++++------- providers/Bandcamp/mod.ts | 14 +++- providers/Beatport/mod.ts | 2 +- .../Deezer/__snapshots__/mod.test.ts.snap | 9 ++- providers/Deezer/mod.ts | 2 +- providers/Mora/__snapshots__/mod.test.ts.snap | 27 ++++--- providers/Mora/mod.ts | 2 +- providers/MusicBrainz/mod.ts | 2 +- .../Ototoy/__snapshots__/mod.test.ts.snap | 63 ++++++++++----- providers/Ototoy/mod.test.ts | 2 +- providers/Ototoy/mod.ts | 2 +- .../Spotify/__snapshots__/mod.test.ts.snap | 9 ++- providers/Spotify/mod.ts | 2 +- .../Tidal/__snapshots__/mod.test.ts.snap | 28 ++++--- providers/Tidal/v1/lookup.ts | 2 +- providers/Tidal/v2/lookup.ts | 2 +- providers/base.ts | 30 ++++++- .../iTunes/__snapshots__/mod.test.ts.snap | 10 ++- providers/iTunes/mod.ts | 2 +- server/components/Release.tsx | 4 +- utils/date.ts | 6 ++ 23 files changed, 213 insertions(+), 94 deletions(-) diff --git a/harmonizer/types.ts b/harmonizer/types.ts index 1852877c..af10b07d 100644 --- a/harmonizer/types.ts +++ b/harmonizer/types.ts @@ -4,7 +4,7 @@ import type { EntityType } from '@kellnerd/musicbrainz'; import type { ReleasePackaging, ReleaseStatus } from '@kellnerd/musicbrainz/data/release'; import type { ReleaseGroupType } from '@kellnerd/musicbrainz/data/release-group'; export type { ReleaseGroupType } from '@kellnerd/musicbrainz/data/release-group'; -import type { PartialDate } from '../utils/date.ts'; +import type { ReleaseDate } from '../utils/date.ts'; import type { ScriptFrequency } from '../utils/script.ts'; /** MusicBrainz entity types which Harmony supports. */ @@ -52,7 +52,7 @@ export type HarmonyRelease = { script?: ScriptFrequency; status?: ReleaseStatus; types?: ReleaseGroupType[]; - releaseDate?: PartialDate; + releaseDate?: ReleaseDate; labels?: Label[]; packaging?: ReleasePackaging; images?: Artwork[]; diff --git a/musicbrainz/seeding.ts b/musicbrainz/seeding.ts index 1d07eea3..bd39c167 100644 --- a/musicbrainz/seeding.ts +++ b/musicbrainz/seeding.ts @@ -77,7 +77,7 @@ export function createReleaseSeed(release: HarmonyRelease, options: ReleaseSeedO release_group: release.releaseGroup?.mbid, barcode: release.gtin?.toString(), events: countries.map((country) => ({ - date: release.releaseDate, + date: release.releaseDate?.date, country, })), labels: release.labels?.map((label) => ({ diff --git a/providers/Bandcamp/__snapshots__/mod.test.ts.snap b/providers/Bandcamp/__snapshots__/mod.test.ts.snap index b6449011..cd9aa10a 100644 --- a/providers/Bandcamp/__snapshots__/mod.test.ts.snap +++ b/providers/Bandcamp/__snapshots__/mod.test.ts.snap @@ -128,9 +128,12 @@ suika.love", ], packaging: "None", releaseDate: { - day: 17, - month: 7, - year: 2019, + date: { + day: 17, + month: 7, + year: 2019, + }, + quality: "assumed-valid", }, status: "Official", title: "and it was a burned into my mind! yet i faltered like a broken record", @@ -220,9 +223,12 @@ snapshot[`Bandcamp provider > release lookup > subscriber-only release 1`] = ` ], packaging: "None", releaseDate: { - day: 30, - month: 4, - year: 2025, + date: { + day: 30, + month: 4, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "Des papiers II", @@ -320,9 +326,12 @@ All the things, Steve. Cover model: Flapjack.", ], packaging: "None", releaseDate: { - day: 4, - month: 7, - year: 2025, + date: { + day: 4, + month: 7, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "Ambiguous Hands", @@ -416,9 +425,12 @@ Artwork by David Rundlöf", ], packaging: "None", releaseDate: { - day: 27, - month: 9, - year: 2024, + date: { + day: 27, + month: 9, + year: 2024, + }, + quality: "assumed-valid", }, status: "Official", title: "Mr. Florida '81", @@ -605,9 +617,12 @@ snapshot[`Bandcamp provider > release lookup > release with name your price (non ], packaging: "None", releaseDate: { - day: 7, - month: 4, - year: 2023, + date: { + day: 7, + month: 4, + year: 2023, + }, + quality: "assumed-valid", }, status: "Official", title: "demo", @@ -776,9 +791,12 @@ snapshot[`Bandcamp provider > release lookup > release with name your price (non ], packaging: "None", releaseDate: { - day: 8, - month: 9, - year: 2013, + date: { + day: 8, + month: 9, + year: 2013, + }, + quality: "assumed-valid", }, status: "Official", title: "Ambient Energy (Name your price)", @@ -918,9 +936,12 @@ snapshot[`Bandcamp provider > release lookup > release with some download only t ], packaging: "None", releaseDate: { - day: 22, - month: 9, - year: 2023, + date: { + day: 22, + month: 9, + year: 2023, + }, + quality: "assumed-valid", }, status: "Official", title: "Futon Feels", @@ -1112,9 +1133,12 @@ snapshot[`Bandcamp provider > release lookup > release with a hidden track 1`] = ], packaging: "None", releaseDate: { - day: 12, - month: 5, - year: 2022, + date: { + day: 12, + month: 5, + year: 2022, + }, + quality: "assumed-valid", }, status: "Official", title: "Do You Like Acid EP", @@ -1424,9 +1448,12 @@ LM005", ], packaging: "None", releaseDate: { - day: 26, - month: 9, - year: 2025, + date: { + day: 26, + month: 9, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "Bear Creek", diff --git a/providers/Bandcamp/mod.ts b/providers/Bandcamp/mod.ts index 89182b5e..96e14d88 100644 --- a/providers/Bandcamp/mod.ts +++ b/providers/Bandcamp/mod.ts @@ -13,7 +13,7 @@ import type { } from '@/harmonizer/types.ts'; import { type CacheEntry, MetadataProvider, ReleaseLookup } from '@/providers/base.ts'; import { DurationPrecision, FeatureQuality, FeatureQualityMap } from '@/providers/features.ts'; -import { parseISODateTime, PartialDate } from '@/utils/date.ts'; +import { parseISODateTime, PartialDate, type ReleaseDate } from '@/utils/date.ts'; import { ProviderError, ResponseError } from '@/utils/errors.ts'; import { extractDataAttribute, extractMetadataTag, extractTextFromHtml } from '@/utils/html.ts'; import { plural, pluralWithCount } from '@/utils/plural.ts'; @@ -436,7 +436,7 @@ export class BandcampReleaseLookup extends ReleaseLookup { diff --git a/providers/Beatport/mod.ts b/providers/Beatport/mod.ts index e23c44d5..606c5283 100644 --- a/providers/Beatport/mod.ts +++ b/providers/Beatport/mod.ts @@ -145,7 +145,7 @@ export class BeatportReleaseLookup extends ReleaseLookup release lookup > single by two artists 1`] = ` ], packaging: "None", releaseDate: { - day: 16, - month: 8, - year: 2024, + date: { + day: 16, + month: 8, + year: 2024, + }, + quality: "assumed-valid", }, status: "Official", title: "Die With A Smile", diff --git a/providers/Deezer/mod.ts b/providers/Deezer/mod.ts index 015886ec..ee8800e0 100644 --- a/providers/Deezer/mod.ts +++ b/providers/Deezer/mod.ts @@ -208,7 +208,7 @@ export class DeezerReleaseLookup extends ReleaseApiLookup release lookup > release with GTIN in distPartNo 1`] = ], packaging: "None", releaseDate: { - day: 31, - month: 10, - year: 2025, + date: { + day: 31, + month: 10, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "MAGIC", @@ -673,9 +676,12 @@ snapshot[`Mora provider > release lookup > release with GTIN in cdPartNo 1`] = ` ], packaging: "None", releaseDate: { - day: 15, - month: 10, - year: 2025, + date: { + day: 15, + month: 10, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "バンドリ! ガールズバンドパーティ! カバーコレクションVol.10", @@ -767,9 +773,12 @@ snapshot[`Mora provider > release lookup > video release 1`] = ` ], packaging: "None", releaseDate: { - day: 22, - month: 11, - year: 2025, + date: { + day: 22, + month: 11, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "Woman is a Tree", diff --git a/providers/Mora/mod.ts b/providers/Mora/mod.ts index c7bab6a4..6d3bd544 100644 --- a/providers/Mora/mod.ts +++ b/providers/Mora/mod.ts @@ -186,7 +186,7 @@ export class MoraReleaseLookup extends ReleaseLookup artists: [this.makeArtistCreditName(albumPage.artistName, albumPage.artistNo)], labels: [label], gtin, - releaseDate: parseISODateTime(albumPage.startDate + ' +0'), + releaseDate: this.convertReleaseDate(parseISODateTime(albumPage.startDate + ' +0')), availableIn: ['JP'], media: [{ format: 'Digital Media', diff --git a/providers/MusicBrainz/mod.ts b/providers/MusicBrainz/mod.ts index 738a3b2c..41e3d9d2 100644 --- a/providers/MusicBrainz/mod.ts +++ b/providers/MusicBrainz/mod.ts @@ -164,7 +164,7 @@ export class MusicBrainzReleaseLookup extends ReleaseApiLookup ({ name: info.label?.name, catalogNumber: info['catalog-number'] ?? undefined, diff --git a/providers/Ototoy/__snapshots__/mod.test.ts.snap b/providers/Ototoy/__snapshots__/mod.test.ts.snap index 6c8eaaee..4e78dcd8 100644 --- a/providers/Ototoy/__snapshots__/mod.test.ts.snap +++ b/providers/Ototoy/__snapshots__/mod.test.ts.snap @@ -78,9 +78,12 @@ snapshot[`OTOTOY provider > release lookup > single track release 1`] = ` ], packaging: "None", releaseDate: { - day: 1, - month: 9, - year: 2025, + date: { + day: 1, + month: 9, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "トゥイー・ボックスの人形劇場", @@ -1644,9 +1647,12 @@ snapshot[`OTOTOY provider > release lookup > multi-disc release 1`] = ` ], packaging: "None", releaseDate: { - day: 21, - month: 11, - year: 2025, + date: { + day: 21, + month: 11, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "Anthology Collection", @@ -1775,9 +1781,12 @@ snapshot[`OTOTOY provider > release lookup > multiple artists 1`] = ` ], packaging: "None", releaseDate: { - day: 25, - month: 2, - year: 2021, + date: { + day: 25, + month: 2, + year: 2021, + }, + quality: "assumed-valid", }, status: "Official", title: "Gimme吟味virtuaる最高star!!!! (feat. さくらみこ, 白上フブキ, 夏色まつり & 宝鐘マリン)", @@ -2124,9 +2133,12 @@ snapshot[`OTOTOY provider > release lookup > no label 1`] = ` ], packaging: "None", releaseDate: { - day: 19, - month: 11, - year: 2025, + date: { + day: 19, + month: 11, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "UNTIL", @@ -2274,9 +2286,12 @@ snapshot[`OTOTOY provider > release lookup > original release date only 1`] = ` ], packaging: "None", releaseDate: { - day: 4, - month: 10, - year: 2023, + date: { + day: 4, + month: 10, + year: 2023, + }, + quality: "assumed-valid", }, status: "Official", title: "THE BOOK 3", @@ -2424,9 +2439,12 @@ snapshot[`OTOTOY provider > release lookup > catalog number 1`] = ` ], packaging: "None", releaseDate: { - day: 4, - month: 10, - year: 2023, + date: { + day: 4, + month: 10, + year: 2023, + }, + quality: "assumed-valid", }, status: "Official", title: "THE BOOK 3", @@ -2672,9 +2690,12 @@ snapshot[`OTOTOY provider > release lookup > per-track artists 1`] = ` ], packaging: "None", releaseDate: { - day: 12, - month: 12, - year: 2025, + date: { + day: 12, + month: 12, + year: 2025, + }, + quality: "assumed-valid", }, status: "Official", title: "Friends", diff --git a/providers/Ototoy/mod.test.ts b/providers/Ototoy/mod.test.ts index a0535293..3ba0e6a0 100644 --- a/providers/Ototoy/mod.test.ts +++ b/providers/Ototoy/mod.test.ts @@ -68,7 +68,7 @@ describe('OTOTOY provider', () => { assert: async (release, ctx) => { await assertSnapshot(ctx, release); - assertEquals(release.releaseDate, { + assertEquals(release.releaseDate?.date, { day: 4, month: 10, year: 2023, diff --git a/providers/Ototoy/mod.ts b/providers/Ototoy/mod.ts index 250626d7..40219c55 100644 --- a/providers/Ototoy/mod.ts +++ b/providers/Ototoy/mod.ts @@ -380,7 +380,7 @@ export class OtotoyReleaseLookup extends ReleaseLookup release lookup > single by two artists 1`] = ` ], packaging: "None", releaseDate: { - day: 16, - month: 8, - year: 2024, + date: { + day: 16, + month: 8, + year: 2024, + }, + quality: "assumed-valid", }, status: "Official", title: "Die With A Smile", diff --git a/providers/Spotify/mod.ts b/providers/Spotify/mod.ts index 891ee83f..b54fbfe3 100644 --- a/providers/Spotify/mod.ts +++ b/providers/Spotify/mod.ts @@ -287,7 +287,7 @@ export class SpotifyReleaseLookup extends ReleaseApiLookup release lookup > live album with video tracks and fea ], packaging: "None", releaseDate: { - day: 1, - month: 1, - year: 2012, + date: { + day: 1, + month: 1, + year: 2012, + }, + note: "Release predates platform launch date.", + quality: "assumed-invalid", }, status: "Official", title: "Live In Brooklyn", @@ -555,9 +559,12 @@ snapshot[`Tidal provider > release lookup > single by two artists (v2 API) 1`] = ], packaging: "None", releaseDate: { - day: 16, - month: 8, - year: 2024, + date: { + day: 16, + month: 8, + year: 2024, + }, + quality: "assumed-valid", }, status: "Official", title: "Die With A Smile", @@ -662,9 +669,12 @@ snapshot[`Tidal provider > release lookup > lyric video (v2 API) 1`] = ` ], packaging: "None", releaseDate: { - day: 19, - month: 4, - year: 2024, + date: { + day: 19, + month: 4, + year: 2024, + }, + quality: "assumed-valid", }, status: "Official", title: "My Boy Only Breaks His Favorite Toys (Lyric Video)", diff --git a/providers/Tidal/v1/lookup.ts b/providers/Tidal/v1/lookup.ts index 3290caf7..6823ad03 100644 --- a/providers/Tidal/v1/lookup.ts +++ b/providers/Tidal/v1/lookup.ts @@ -108,7 +108,7 @@ export class TidalV1ReleaseLookup extends ReleaseApiLookup types: this.provider.getLinkTypesForEntity(), }], media, - releaseDate: parseHyphenatedDate(rawRelease.releaseDate), + releaseDate: this.convertReleaseDate(parseHyphenatedDate(rawRelease.releaseDate)), copyright: rawRelease.copyright ? formatCopyrightSymbols(rawRelease.copyright) : undefined, status: 'Official', types: [capitalizeReleaseType(rawRelease.type)], diff --git a/providers/Tidal/v2/lookup.ts b/providers/Tidal/v2/lookup.ts index 3db43c64..f2a84132 100644 --- a/providers/Tidal/v2/lookup.ts +++ b/providers/Tidal/v2/lookup.ts @@ -152,7 +152,7 @@ export class TidalV2ReleaseLookup extends ReleaseApiLookup; + protected convertReleaseDate(date?: PartialDate): ReleaseDate { + const launchDate = this.provider.launchDate; + if ( + date === undefined || + (launchDate.day === undefined && launchDate.month === undefined && launchDate.year === undefined) + ) { + return { + date, + quality: 'none-found', + }; + } + + if ( + !date?.year || date.year < launchDate.year! || (date.year === launchDate.year && date.month! < launchDate.month!) + ) { + return { + date, + quality: 'assumed-invalid', + note: 'Release predates platform launch date.', + }; + } + + return { + date, + quality: 'assumed-valid', + }; + } + /** Adds a message to the generated release info. */ protected addMessage(text: string, type: MessageType = 'info'): void { this.messages.push({ diff --git a/providers/iTunes/__snapshots__/mod.test.ts.snap b/providers/iTunes/__snapshots__/mod.test.ts.snap index 24e2e965..88375e63 100644 --- a/providers/iTunes/__snapshots__/mod.test.ts.snap +++ b/providers/iTunes/__snapshots__/mod.test.ts.snap @@ -811,9 +811,13 @@ snapshot[`iTunes provider > release lookup > multi-disc download with video trac ], packaging: "None", releaseDate: { - day: 21, - month: 11, - year: 1975, + date: { + day: 21, + month: 11, + year: 1975, + }, + note: "Release predates platform launch date.", + quality: "assumed-invalid", }, status: "Official", title: "A Night at the Opera (Deluxe Edition)", diff --git a/providers/iTunes/mod.ts b/providers/iTunes/mod.ts index 17541ffe..1758407a 100644 --- a/providers/iTunes/mod.ts +++ b/providers/iTunes/mod.ts @@ -193,7 +193,7 @@ export class iTunesReleaseLookup extends ReleaseApiLookup Release date - {formatPartialDate(release.releaseDate ?? {}) || '[unknown]'} + {formatPartialDate(release.releaseDate?.date ?? {}) || '[unknown]'} release.releaseDate} + property={(release) => release.releaseDate?.date} display={formatPartialDate} identifier={formatPartialDate} /> diff --git a/utils/date.ts b/utils/date.ts index 516e1f3d..da443f11 100644 --- a/utils/date.ts +++ b/utils/date.ts @@ -6,6 +6,12 @@ export type PartialDate = Partial<{ year: number; }>; +export interface ReleaseDate { + date?: PartialDate; + quality: 'assumed-invalid' | 'assumed-valid' | 'none-found'; + note?: string; +} + export function formatPartialDate(date: PartialDate) { const dateComponents: string[] = [];