From 54520f1a7af6d2a3a64e032adf6784f25e34bdb3 Mon Sep 17 00:00:00 2001 From: Lioncat6 <95449321+Lioncat6@users.noreply.github.com> Date: Sun, 5 Apr 2026 02:18:08 -0500 Subject: [PATCH 1/9] feat(vibe): Add Naver VIBE provider --- providers/Vibe/__snapshots__/mod.test.ts.snap | 187 ++++++++++++ providers/Vibe/api_types.ts | 262 +++++++++++++++++ providers/Vibe/mod.test.ts | 57 ++++ providers/Vibe/mod.ts | 278 ++++++++++++++++++ providers/mod.ts | 2 + server/components/ProviderIcon.tsx | 1 + server/icons/BrandVibeOutline.tsx | 19 ++ server/routes/icon-sprite.svg.tsx | 2 + server/static/harmony.css | 4 + .../vibeWeb/musicapiweb/album/34923420.json | 1 + .../musicapiweb/album/34923420/tracks.json | 1 + .../musicapiweb/track/96143271/credits.json | 1 + .../musicapiweb/track/96143273/credits.json | 1 + 13 files changed, 816 insertions(+) create mode 100644 providers/Vibe/__snapshots__/mod.test.ts.snap create mode 100644 providers/Vibe/api_types.ts create mode 100644 providers/Vibe/mod.test.ts create mode 100644 providers/Vibe/mod.ts create mode 100644 server/icons/BrandVibeOutline.tsx create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420/tracks.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143271/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143273/credits.json diff --git a/providers/Vibe/__snapshots__/mod.test.ts.snap b/providers/Vibe/__snapshots__/mod.test.ts.snap new file mode 100644 index 00000000..bd7cc9a1 --- /dev/null +++ b/providers/Vibe/__snapshots__/mod.test.ts.snap @@ -0,0 +1,187 @@ +export const snapshot = {}; + +snapshot[`Naver provider > release lookup > single with a featuring artist 1`] = ` +{ + artists: [ + { + creditedName: "ivycomb", + externalIds: [ + { + id: "8956811", + provider: "vibe", + type: "artist", + }, + ], + name: "ivycomb", + }, + ], + externalLinks: [ + { + types: [ + "paid streaming", + "paid download", + ], + url: "https://vibe.naver.com/album/34923420", + }, + ], + images: [ + { + thumbUrl: "https://musicmeta-phinf.pstatic.net/album/034/923/34923420.jpg?type=r480Fll&v=20250810043508", + types: [ + "front", + ], + url: "https://musicmeta-phinf.pstatic.net/album/034/923/34923420.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: "https://apis.naver.com/vibeWeb/musicapiweb/album/34923420.json", + id: "34923420", + internalName: "vibe", + lookup: { + method: "id", + value: "34923420", + }, + name: "Naver VIBE", + url: "https://vibe.naver.com/album/34923420", + }, + ], + }, + labels: [ + { + name: "Honeycomb Records", + }, + { + name: "Kanjian", + }, + ], + media: [ + { + format: "Digital Media", + number: 1, + tracklist: [ + { + artists: [ + { + creditedName: "ivycomb", + externalIds: [ + { + id: "8956811", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "ivycomb", + }, + { + creditedName: "OodleZz", + externalIds: [ + { + id: "9943775", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "OodleZz", + }, + ], + length: 224000, + number: 1, + recording: { + externalIds: [ + { + id: "96143271", + provider: "vibe", + type: "track", + }, + ], + }, + title: "THE RINGMASTER (Feat. OodleZz)", + }, + { + artists: [ + { + creditedName: "ivycomb", + externalIds: [ + { + id: "8956811", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "ivycomb", + }, + { + creditedName: "OodleZz", + externalIds: [ + { + id: "9943775", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "OodleZz", + }, + ], + length: 224000, + number: 2, + recording: { + externalIds: [ + { + id: "96143273", + provider: "vibe", + type: "track", + }, + ], + }, + title: "THE RINGMASTER - YouTube Mix (Feat. OodleZz)", + }, + { + artists: [ + { + creditedName: "ivycomb", + externalIds: [ + { + id: "8956811", + provider: "vibe", + type: "artist", + }, + ], + name: "ivycomb", + }, + ], + length: 224000, + number: 3, + recording: { + externalIds: [ + { + id: "96143275", + provider: "vibe", + type: "track", + }, + ], + }, + title: "THE RINGMASTER - Instrumental", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + date: { + day: 13, + month: 6, + year: 2025, + }, + quality: "assumed-valid", + }, + status: "Official", + title: "THE RINGMASTER", +} +`; diff --git a/providers/Vibe/api_types.ts b/providers/Vibe/api_types.ts new file mode 100644 index 00000000..cf9671b0 --- /dev/null +++ b/providers/Vibe/api_types.ts @@ -0,0 +1,262 @@ +// Sourced from https://github.com/Lioncat6/SAMBL-React/blob/db2affebfff0b2614c0c839a3c6ef3043688b4b4/lib/providers/naver.ts +// License TBD + +// Response Wrapper +export interface NaverResponse { + response: { + result: TResult; + }; +} + +// Shared Artist Types +export interface NaverPartialArtist { + artistId: number; + artistName: string; + imageUrl?: string; +} + +export interface NaverArtist extends NaverPartialArtist { + debutDate?: string; + genreNames: string; + likeCount: number; + isGroup: boolean; +} + +export interface NaverArtistResult { + artist: NaverArtist; +} + +// Artist Search +export interface NaverSearchResult { + originalQuery: string; +} + +export interface NaverArtistSearchResult extends NaverSearchResult { + artistTotalCount: number; + artists?: NaverArtist[]; +} + +// Artist Detail (artistEnd) +export interface NaverArtistPhoto { + musicianPhotoType: string; + thumbnailImageUrl: string; + bodyImageUrl: string; + originalImageUrl: string; + imageUrlKey: string; + photoViewerImgId: string; + photoViewerIndex: number; +} + +export interface NaverArtistMember { + artistId: number; + artistName: string; + imageUrl?: string; + likeCount: number; +} + +export interface NaverArtistEnd { + artistId: number; + artistName: string; + debutDate: string; + gender: string; + isGroup: boolean; + activePeriod: string; + managementName: string; + genreNames: string; + memberGroupArtistIds: string; + memberGroupArtistNames: string; + imageUrl: string; + biography?: string; + likeCount: number; + photoCount: number; + photoList: NaverArtistPhoto[]; + memberList: NaverArtistMember[]; +} + +// Album Types +export interface NaverAlbumBase { + albumId: number; + albumTitle: string; + releaseDate: string; + imageUrl: string; + isDolbyAtmos: boolean; + hasDolbyAtmos: boolean; + isVariousArtists: boolean; +} + +export interface NaverPartialAlbum extends NaverAlbumBase { + artists: NaverPartialArtist[]; +} + +export interface NaverAlbum extends NaverAlbumBase { + isRegular: boolean; + isAdult: boolean; + agencyName: string; + productionName: string; + artists: NaverPartialArtist[]; + sizeAndDuration: string; + trackTotalCount: number; + artistTotalCount: number; + albumGenres: string; + albumGenreList: string[]; + shareUrl: string; + likeCount: number; + playtime: number; +} + +export interface NaverAlbumResult { + album: NaverAlbum; +} + +// Track Credits (writers, composers, arrangers) +export interface NaverCreditArtist { + artistId: number; + artistName: string; + isDisplay: boolean; +} + +export type NaverLyricWriter = NaverCreditArtist; +export type NaverComposer = NaverCreditArtist; +export type NaverArranger = NaverCreditArtist; + +// Track Information (/track/{id}/info.json) +export interface NaverTrackInformation { + trackId: number; + lyricWriters: NaverLyricWriter[]; + composers: NaverComposer[]; + arrangers: NaverArranger[]; + hasLyric: string; + hasSyncLyric: string; + syncLyric: string; + lyricSourceTypeCd: string; + lyricRegisterUserId: number | null; + lyricUpdateUserId: number | null; +} + +// Track Credits (/track/{id}/credits.json) +export interface NaverTrackCreditsResult { + trackCredits: NaverTrackCredits +} + +export interface NaverTrackCredits { + trackId: number + trackName: string + artistIds: string + artistNames: string + releaseDate: string + participantGroupList: NaverParticipantGroup[] +} + +export interface NaverParticipantGroup { + roleName: string + participantList: NaverParticipant[] +} + +export interface NaverParticipant { + id: number + name: string + likeCount: number + imageUrl: string | null +} + +// Track Detail (/track/{id}.json) +export interface NaverTrack { + trackId: number; + trackTitle: string; + represent: boolean; + discNumber: number; + trackNumber: number; + artists: NaverPartialArtist[]; + album: NaverPartialAlbum; + hasLyric: boolean; + hasSyncLyric: boolean; + isStreaming: boolean; + isDownload: boolean; + isMobileDownload: boolean; + isAdult: boolean; + representDownloadPrice: number; + isPrdd: boolean; + isAodd: boolean; + isOversea: boolean; + playTime: string; + isKaraokeEnabled: boolean; + isDolbyAtmos: boolean; + hasDolbyAtmos: boolean; +} + +export interface NaverTrackResult { + track: NaverTrack; +} + +// Search-All (/searchall.json) +export interface NaverSearchAllTrackResult { + trackTotalCount: number; + tracks: NaverTrack[]; +} + +export interface NaverSearchAllAlbumResult { + albumTotalCount: number; + albums: NaverPartialAlbum[]; +} + +export interface NaverSearchAllArtistResult { + artistTotalCount: number; + artists: NaverArtist[]; +} + +export interface NaverSearchAllResult extends NaverSearchResult { + lyricResult: { trackTotalCount: number }; + trackResult: NaverSearchAllTrackResult; + albumResult: NaverSearchAllAlbumResult; + artistResult: NaverSearchAllArtistResult; + videoResult: { videoTotalCount: number }; + playlistResult: { playlistTotalCount: number }; + userPlaylistResult: { playlistTotalCount: number }; + newAudioResult: { audioTotalCount: number }; + popularResult: { searchType: number }; +} + +// Artist Albums +export interface NaverArtistAlbumsResult { + albumTotalCount: number; + albums: NaverPartialAlbum[]; +} + +// Artist Tracks +export interface NaverArtistTracksResult { + trackTotalCount: number; + tracks: NaverTrack[]; +} + +// Album Tracks +export interface NaverAlbumTrack extends NaverTrack { + likeCount?: number; + score?: number; + isTopPopular?: boolean; +} + +export interface NaverAlbumTracksResult { + trackTotalCount: number; + tracks: NaverAlbumTrack[]; +} + +// Custom Types +export interface NaverAlbumWithTracks extends NaverAlbum { + tracks?: NaverAlbumTrack[]; +} + +export interface NaverPartialAlbumWithTracks extends NaverPartialAlbum { + tracks?: NaverAlbumTrack[]; + trackTotalCount?: number; +} + +// Harmony Types + +export interface ApiError { + response: { + message: { + apiStatusCode: string // Seems to be either `NO_SUCH_RESOURCE` or `PROCESS_FAIL` + text: string + } + } +} diff --git a/providers/Vibe/mod.test.ts b/providers/Vibe/mod.test.ts new file mode 100644 index 00000000..0e6bb29c --- /dev/null +++ b/providers/Vibe/mod.test.ts @@ -0,0 +1,57 @@ +import type { ReleaseOptions } from '@/harmonizer/types.ts'; +import { describeProvider, makeProviderOptions } from '@/providers/test_spec.ts'; +import { stubProviderLookups } from '@/providers/test_stubs.ts'; +import { assert } from 'std/assert/assert.ts'; +import { afterAll, describe } from '@std/testing/bdd'; +import { assertSnapshot } from '@std/testing/snapshot'; + +import VibeProvider from './mod.ts'; +import { assertEquals } from 'std/assert/assert_equals.ts'; + +describe('Naver provider', () => { + const vibe = new VibeProvider(makeProviderOptions()); + const lookupStub = stubProviderLookups(vibe); + + // Standard options which have an effect for Naver VIBE. + const releaseOptions: ReleaseOptions = { + withISRC: false, + withAllTrackArtists: true, + }; + + describeProvider(vibe, { + urls: [{ + description: 'vibe album page', + url: new URL('https://vibe.naver.com/album/34923420'), + id: { type: 'album', id: '34923420' }, + isCanonical: true, + }, { + description: 'vibe track page', + url: new URL('https://vibe.naver.com/track/96143273'), + id: { type: 'track', id: '96143273' }, + }, { + description: 'vibe artist page', + url: new URL('https://vibe.naver.com/artist/8956811'), + id: { type: 'artist', id: '8956811' }, + isCanonical: true, + }, { + description: 'playlist page', + url: new URL('https://vibe.naver.com/mylist/64211346'), + id: undefined, + }], + invalidIds: ['according to all known laws of aviation...'], + releaseLookup: [{ + description: 'single with a featuring artist', + release: new URL('https://vibe.naver.com/album/34923420'), + options: releaseOptions, + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + const allTracks = release.media.flatMap((medium) => medium.tracklist); + assert(allTracks[0].artists?.length === 2, 'Main track should have two artists'); + }, + }], + }); + + afterAll(() => { + lookupStub.restore(); + }); +}); diff --git a/providers/Vibe/mod.ts b/providers/Vibe/mod.ts new file mode 100644 index 00000000..5415f81c --- /dev/null +++ b/providers/Vibe/mod.ts @@ -0,0 +1,278 @@ +import { type ApiQueryOptions, type CacheEntry, MetadataApiProvider, ReleaseApiLookup } from '@/providers/base.ts'; +import { DurationPrecision, FeatureQuality, FeatureQualityMap } from '@/providers/features.ts'; +import { parseHyphenatedDate, PartialDate } from '@/utils/date.ts'; +import { ProviderError, ResponseError } from '@/utils/errors.ts'; +import { + ArtistCreditName, + Artwork, + EntityId, + HarmonyMedium, + HarmonyRelease, + HarmonyTrack, + Label, + LinkType, +} from '@/harmonizer/types.ts'; +import { ApiError, NaverAlbum, NaverAlbumResult, NaverArtistTracksResult, NaverPartialArtist, NaverParticipant, NaverResponse, NaverTrack, NaverTrackCreditsResult } from './api_types.ts'; + +export default class VibeProvider extends MetadataApiProvider { + readonly name = 'Naver VIBE'; + + override get internalName(): string { + return 'vibe' + } + + readonly supportedUrls = new URLPattern({ + hostname: 'vibe.naver.com', + pathname: '/:type(artist|album|track)/:id', + }); + + override readonly features: FeatureQualityMap = { + 'cover size': 3000, + 'duration precision': DurationPrecision.SECONDS, + 'GTIN lookup': FeatureQuality.MISSING, + 'MBID resolving': FeatureQuality.GOOD, + 'release label': FeatureQuality.PRESENT, + }; + + readonly entityTypeMap = { + artist: ['artist', 'interpreter'], + release: 'album', + recording: 'track', + label: 'label', + }; + + readonly releaseLookup = VibeReleaseLookup; + + override readonly launchDate: PartialDate = { + year: 2007, + month: 8, + }; + + readonly apiBaseUrl = 'https://apis.naver.com/vibeWeb/musicapiweb/'; + + constructUrl(entity: EntityId): URL { + return new URL([entity.type, entity.id].join('/'), 'https://vibe.naver.com/'); + } + + override getLinkTypesForEntity(): LinkType[] { + return ['paid streaming', 'paid download']; + } + + async query(apiUrl: URL, options: ApiQueryOptions): Promise> { + const cacheEntry = await this.fetchJSON(apiUrl, { + policy: { maxTimestamp: options.snapshotMaxTimestamp }, + }); + const error = cacheEntry.content as ApiError; + if (error.response.message) { + throw new QobuzResponseError(error, apiUrl); + } + return cacheEntry; + } +} + +export class VibeReleaseLookup extends ReleaseApiLookup { + + constructReleaseApiUrl(): URL { + return new URL(`album/${this.lookup.value}.json`, this.provider.apiBaseUrl); + } + + protected async getRawRelease(): Promise { + const apiUrl = this.constructReleaseApiUrl(); + if (this.lookup.method === 'gtin') { + throw new ProviderError(this.provider.name, 'GTIN lookups are not supported'); + } else { + const { content: naverResult, timestamp } = await this.provider.query>(apiUrl, { + snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, + }); + this.updateCacheTime(timestamp); + const release = naverResult.response.result.album; + return release; + } + } + + protected async convertRawRelease(rawRelease: NaverAlbum): Promise { + this.entity = { + id: String(rawRelease.albumId), + type: 'album', + }; + + return { + title: rawRelease.albumTitle, + artists: rawRelease.artists.map((artist) => this.convertRawArtist(artist)), + media: await this.getAlbumMedium(rawRelease), + releaseDate: this.convertReleaseDate(parseHyphenatedDate(this.formatAlbumDate(rawRelease.releaseDate))), + status: 'Official', + packaging: 'None', + images: this.getAlbumImage(rawRelease.imageUrl), + labels: this.getAlbumLabels(rawRelease), + externalLinks: [{ + url: this.provider.constructUrl(this.entity).toString(), + types: this.provider.getLinkTypesForEntity(), + }], + info: this.generateReleaseInfo(), + }; + } + + private getAlbumLabels(album: NaverAlbum): Label[] { + const rawLabels = [album.agencyName, album.productionName].filter((label) => label != undefined && label!=null); + return rawLabels.map((label) => ({ + name: label, + })); + } + + private formatAlbumDate(date: string): string { + return date.split(".").join("-"); + } + + private getAlbumImage(url: string | undefined): Artwork[] | undefined { + if (!url) return undefined; + const imgRegex = /https:\/\/musicmeta-phinf\.pstatic\.net\/[^?]*/ + return [{ + url: url.match(imgRegex)?.[0] || url, + thumbUrl: url, + types: ['front'], + }]; + } + + private convertRawArtist(rawArtist: NaverPartialArtist): ArtistCreditName { + return { + name: rawArtist.artistName, + creditedName: rawArtist.artistName, + externalIds: this.provider.makeExternalIds({ type: 'artist', id: String(rawArtist.artistId) }), + }; + } + + private async getAlbumMedium(album: NaverAlbum): Promise { + const result: HarmonyMedium[] = []; + let medium: HarmonyMedium = { + number: 1, + format: 'Digital Media', + tracklist: [], + }; + + const tracks = (await this.getRawTracklist(album.albumId)).response.result.tracks; + for (const track of tracks) { + if (track.discNumber !== medium.number) { + result.push(medium); + medium = { + number: track.discNumber, + format: 'Digital Media', + tracklist: [], + }; + } + medium.tracklist.push(await this.convertRawTrack(track)); + } + result.push(medium); + return result; + } + + private async getRawTracklist(id: number): Promise> { + const apiUrl = new URL(`album/${id}/tracks.json`, this.provider.apiBaseUrl); + const { content: naverResult, timestamp } = await this.provider.query>(apiUrl, { + snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, + }); + this.updateCacheTime(timestamp); + return naverResult; + } + + private async convertRawTrack(rawTrack: NaverTrack): Promise { + return { + title: rawTrack.trackTitle, + // artists: rawTrack.artists.map((artist) => this.convertRawArtist(artist)), <- Misses featuring artist info + artists: await this.getTrackArtists(rawTrack), + number: rawTrack.trackNumber, + length: this.getTrackDuration(rawTrack.playTime), + recording: { + externalIds: this.provider.makeExternalIds({ type: 'track', id: String(rawTrack.trackId) }), + }, + }; + } + + private toParticipants(arists: NaverPartialArtist[]): NaverParticipant[] { + return arists.map((artist) => { + return ({ + name: artist.artistName, + id: artist.artistId, + likeCount: 0, + imageUrl: artist.imageUrl || null + }) + }) + } + + private async getTrackArtists(rawTrack: NaverTrack): Promise { + const trackFeatVariations = ['feat.', 'ft.', 'with'] + if (!trackFeatVariations.some((fv) => rawTrack.trackTitle.toLocaleLowerCase().includes(fv))) { + // Skip fetching credits if track name doesn't have some variation of 'feat.' since harmony can't use anything else at the moment + // From what I can tell, all tracks on naver with featuring artists have one of the above variations in the title, even the ones with titles in other languages + return rawTrack.artists.map((artist) => this.convertRawArtist(artist)); + } + // Fetching credits is pretty inexpensive, so maybe ^ isn't neccesary to avoid extra calls + const trackCredits = (await this.getRawTrackCredits(rawTrack.trackId)).response.result.trackCredits; + const roles = trackCredits.participantGroupList; + const featuringArtists = roles.filter((role) => role.roleName === "피쳐링").flatMap((role) => role.participantList); + // const otherArtists = roles.flatMap((role) => role.participantList); + // ^ other artist roles: + // 작사 (Lyricist) + // 작곡 (Composer) + // 편곡 (Arranger) + // 마스터링 엔지니어 (Mastering Engineer) + // 믹싱 엔지니어 (Mixing Engineer) + // 프로듀서 (Producer) + // 지휘 (Conductor / Musical Director) + // --performance credits -- + // 백코러스 (Backing Vocals) + // 트럼펫 (Trumpet) + // 스트링 (Strings) + // 피아노 (Piano) + // ... + const featuringIds = featuringArtists.map((artist) => artist.id); + const allArtists = [... this.toParticipants(rawTrack.artists), ...featuringArtists]; + const uniqueArtists = Array.from(new Map(allArtists.map(artist => [artist.id, artist])).values()); + const credits: ArtistCreditName[] = [] + let hasMarkedFeat = false; + uniqueArtists.forEach((aritst, index) => { + const nextFeaturing = (index < uniqueArtists.length - 1 && featuringIds.includes(uniqueArtists[index+1].id)); + const secondNextFeaturing = (index < uniqueArtists.length - 2 && featuringIds.includes(uniqueArtists[index+2].id)); + credits.push(this.convertRawParticipant(aritst, nextFeaturing, hasMarkedFeat, secondNextFeaturing)) + if (nextFeaturing) hasMarkedFeat = true; + }) + return credits; + } + + private convertRawParticipant(rawArtist: NaverParticipant, nextFeaturing = false, hasMarkedFeat = false, secondNextFeaturing = false): ArtistCreditName { + return { + name: rawArtist.name, + creditedName: rawArtist.name, + externalIds: this.provider.makeExternalIds({ type: 'artist', id: String(rawArtist.id) }), + joinPhrase: !hasMarkedFeat ? nextFeaturing ? ' feat. ' : secondNextFeaturing ? ' & ': undefined: undefined + }; + } + + private async getRawTrackCredits(id: number): Promise> { + const apiUrl = new URL(`track/${id}/credits.json`, this.provider.apiBaseUrl); + const { content: naverResult, timestamp } = await this.provider.query>(apiUrl, { + snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, + }); + this.updateCacheTime(timestamp); + return naverResult; + } + + private getTrackDuration(duration: string): number { + const segments = duration.split(":"); + let ms = 0; + if (segments.length == 2) { + ms += Number(segments[0]) * 60 * 1000; + ms += Number(segments[1]) * 1000; + } else if (segments.length == 1){ + ms += Number(segments[1]) * 1000; + } + return ms; + } + +} + +class QobuzResponseError extends ResponseError { + constructor(readonly details: ApiError, url: URL) { + super('Qobuz', `${details.response.message.text} (${details.response.message.apiStatusCode})`, url); + } +} diff --git a/providers/mod.ts b/providers/mod.ts index a32d4184..6930314a 100644 --- a/providers/mod.ts +++ b/providers/mod.ts @@ -12,6 +12,7 @@ import OtotoyProvider from './Ototoy/mod.ts'; import SpotifyProvider from './Spotify/mod.ts'; import TidalProvider from './Tidal/mod.ts'; import MoraProvider from './Mora/mod.ts'; +import VibeProvider from './Vibe/mod.ts'; /** Registry with all supported providers. */ export const providers = new ProviderRegistry({ @@ -30,6 +31,7 @@ providers.addMultiple( BeatportProvider, MoraProvider, OtotoyProvider, + VibeProvider, ); /** Internal names of providers which are enabled by default (for GTIN lookups). */ diff --git a/server/components/ProviderIcon.tsx b/server/components/ProviderIcon.tsx index 4a059e2d..ed3f0d41 100644 --- a/server/components/ProviderIcon.tsx +++ b/server/components/ProviderIcon.tsx @@ -9,6 +9,7 @@ const providerIconMap: Record = { itunes: 'brand-apple', musicbrainz: 'brand-metabrainz', mora: 'brand-mora', + vibe: 'brand-vibe', ototoy: 'brand-ototoy', spotify: 'brand-spotify', tidal: 'brand-tidal', diff --git a/server/icons/BrandVibeOutline.tsx b/server/icons/BrandVibeOutline.tsx new file mode 100644 index 00000000..5567d9b3 --- /dev/null +++ b/server/icons/BrandVibeOutline.tsx @@ -0,0 +1,19 @@ +export default function IconBrandVibe({ + size = 24, + color = 'currentColor', + ...props +}) { + return ( + + + + ); +} diff --git a/server/routes/icon-sprite.svg.tsx b/server/routes/icon-sprite.svg.tsx index a53ac988..803a1be7 100644 --- a/server/routes/icon-sprite.svg.tsx +++ b/server/routes/icon-sprite.svg.tsx @@ -9,6 +9,7 @@ import IconBrandDeezer from 'tabler-icons/brand-deezer.tsx'; import IconBrandGit from 'tabler-icons/brand-git.tsx'; import IconBrandSpotify from 'tabler-icons/brand-spotify.tsx'; import IconBrandTidal from 'tabler-icons/brand-tidal.tsx'; +import IconBrandVibe from '../icons/BrandVibeOutline.tsx'; import IconAlertTriangle from 'tabler-icons/alert-triangle.tsx'; import IconBarcode from 'tabler-icons/barcode.tsx'; import IconBug from 'tabler-icons/bug.tsx'; @@ -62,6 +63,7 @@ const icons: Icon[] = [ IconBrandIfpi, IconBrandMetaBrainz, IconBrandMora, + IconBrandVibe, IconBrandOtotoy, IconBrandSpotify, IconBrandTidal, diff --git a/server/static/harmony.css b/server/static/harmony.css index 67c1522b..ff5cb163 100644 --- a/server/static/harmony.css +++ b/server/static/harmony.css @@ -33,6 +33,7 @@ --deezer: #a238ff; --mora: #02082a; --musicbrainz: #ba478f; + --vibe: #e404aa; --ototoy: #e07e01; --spotify: #1db954; --tidal: #000000; @@ -469,6 +470,9 @@ label.mora, td.mora { label.musicbrainz, td.musicbrainz { background-color: var(--musicbrainz); } +label.vibe, td.vibe { + background-color: var(--vibe); +} label.ototoy, td.ototoy { background-color: var(--ototoy); } diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420.json new file mode 100644 index 00000000..f09176c0 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420.json @@ -0,0 +1 @@ +{"response":{"result":{"album":{"albumId":34923420,"albumTitle":"THE RINGMASTER","isRegular":false,"isAdult":false,"agencyName":"Honeycomb Records","isDolbyAtmos":false,"hasDolbyAtmos":false,"productionName":"Kanjian","releaseDate":"2025.06.13","artists":[{"artistId":8956811,"artistName":"ivycomb"}],"imageUrl":"https://musicmeta-phinf.pstatic.net/album/034/923/34923420.jpg?type=r480Fll&v=20250810043508","serviceStatusMsg":"STREAMING_DRM_AODD","sizeAndDuration":"3곡, 11분 12초","trackTotalCount":3,"artistTotalCount":1,"albumGenres":"기타","albumGenreList":["기타"],"shareUrl":"https://vibe.naver.com/album/34923420","likeCount":0,"playtime":672}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420/tracks.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420/tracks.json new file mode 100644 index 00000000..b1c19fde --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/34923420/tracks.json @@ -0,0 +1 @@ +{"response":{"result":{"trackTotalCount":3,"tracks":[{"trackId":96143271,"trackTitle":"THE RINGMASTER (Feat. OodleZz)","represent":false,"discNumber":1,"trackNumber":1,"artists":[{"artistId":8956811,"artistName":"ivycomb"}],"album":{"albumId":34923420,"albumTitle":"THE RINGMASTER","releaseDate":"2025.06.13","imageUrl":"https://musicmeta-phinf.pstatic.net/album/034/923/34923420.jpg?type=r480Fll&v=20250810043508","artists":[{"artistId":8956811,"artistName":"ivycomb"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":false,"isAodd":true,"isOversea":true,"playTime":"03:44","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":1,"score":0,"isTopPopular":false},{"trackId":96143273,"trackTitle":"THE RINGMASTER - YouTube Mix (Feat. OodleZz)","represent":false,"discNumber":1,"trackNumber":2,"artists":[{"artistId":8956811,"artistName":"ivycomb"}],"album":{"albumId":34923420,"albumTitle":"THE RINGMASTER","releaseDate":"2025.06.13","imageUrl":"https://musicmeta-phinf.pstatic.net/album/034/923/34923420.jpg?type=r480Fll&v=20250810043508","artists":[{"artistId":8956811,"artistName":"ivycomb"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":false,"isAodd":true,"isOversea":true,"playTime":"03:44","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":1,"score":0,"isTopPopular":false},{"trackId":96143275,"trackTitle":"THE RINGMASTER - Instrumental","represent":false,"discNumber":1,"trackNumber":3,"artists":[{"artistId":8956811,"artistName":"ivycomb"}],"album":{"albumId":34923420,"albumTitle":"THE RINGMASTER","releaseDate":"2025.06.13","imageUrl":"https://musicmeta-phinf.pstatic.net/album/034/923/34923420.jpg?type=r480Fll&v=20250810043508","artists":[{"artistId":8956811,"artistName":"ivycomb"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":false,"isAodd":true,"isOversea":true,"playTime":"03:44","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false}]}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143271/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143271/credits.json new file mode 100644 index 00000000..dd56e2d7 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143271/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":96143271,"trackName":"THE RINGMASTER (Feat. OodleZz)","artistIds":"8956811","artistNames":"ivycomb","releaseDate":"2025.6.13","participantGroupList":[{"roleName":"작사","participantList":[{"id":9925032,"name":"Vivian Graves","likeCount":0,"imageUrl":null}]},{"roleName":"작곡","participantList":[{"id":9925032,"name":"Vivian Graves","likeCount":0,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":9943775,"name":"OodleZz","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143273/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143273/credits.json new file mode 100644 index 00000000..45c6fef4 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/96143273/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":96143273,"trackName":"THE RINGMASTER - YouTube Mix (Feat. OodleZz)","artistIds":"8956811","artistNames":"ivycomb","releaseDate":"2025.6.13","participantGroupList":[{"roleName":"작사","participantList":[{"id":9925032,"name":"Vivian Graves","likeCount":0,"imageUrl":null}]},{"roleName":"작곡","participantList":[{"id":9925032,"name":"Vivian Graves","likeCount":0,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":9943775,"name":"OodleZz","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file From ba1fd300cf17dd2905ef43dcf38ef555d83d6dac Mon Sep 17 00:00:00 2001 From: Lioncat6 <95449321+Lioncat6@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:13:25 -0500 Subject: [PATCH 2/9] chore(vibe): Resolve some basic issues --- providers/Vibe/mod.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/providers/Vibe/mod.ts b/providers/Vibe/mod.ts index 5415f81c..db1a7f9d 100644 --- a/providers/Vibe/mod.ts +++ b/providers/Vibe/mod.ts @@ -23,29 +23,29 @@ export default class VibeProvider extends MetadataApiProvider { readonly supportedUrls = new URLPattern({ hostname: 'vibe.naver.com', - pathname: '/:type(artist|album|track)/:id', + pathname: '/:type(artist|album|track)/:id(\\d+)', }); override readonly features: FeatureQualityMap = { 'cover size': 3000, 'duration precision': DurationPrecision.SECONDS, 'GTIN lookup': FeatureQuality.MISSING, - 'MBID resolving': FeatureQuality.GOOD, + 'MBID resolving': FeatureQuality.PRESENT, 'release label': FeatureQuality.PRESENT, }; readonly entityTypeMap = { - artist: ['artist', 'interpreter'], + artist: 'artist', release: 'album', recording: 'track', - label: 'label', }; readonly releaseLookup = VibeReleaseLookup; override readonly launchDate: PartialDate = { - year: 2007, - month: 8, + year: 2018, + month: 6, + day: 11 }; readonly apiBaseUrl = 'https://apis.naver.com/vibeWeb/musicapiweb/'; @@ -64,7 +64,7 @@ export default class VibeProvider extends MetadataApiProvider { }); const error = cacheEntry.content as ApiError; if (error.response.message) { - throw new QobuzResponseError(error, apiUrl); + throw new VibeResponseError(error, apiUrl); } return cacheEntry; } @@ -81,12 +81,11 @@ export class VibeReleaseLookup extends ReleaseApiLookup>(apiUrl, { + const { content, timestamp } = await this.provider.query>(apiUrl, { snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, }); this.updateCacheTime(timestamp); - const release = naverResult.response.result.album; - return release; + return content.response.result.album; } } @@ -271,8 +270,8 @@ export class VibeReleaseLookup extends ReleaseApiLookup Date: Sun, 5 Apr 2026 15:14:28 -0500 Subject: [PATCH 3/9] chore(vibe): Formatting --- providers/Vibe/api_types.ts | 34 +++++++------- providers/Vibe/mod.ts | 94 ++++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 54 deletions(-) diff --git a/providers/Vibe/api_types.ts b/providers/Vibe/api_types.ts index cf9671b0..dfacf819 100644 --- a/providers/Vibe/api_types.ts +++ b/providers/Vibe/api_types.ts @@ -135,28 +135,28 @@ export interface NaverTrackInformation { // Track Credits (/track/{id}/credits.json) export interface NaverTrackCreditsResult { - trackCredits: NaverTrackCredits + trackCredits: NaverTrackCredits; } export interface NaverTrackCredits { - trackId: number - trackName: string - artistIds: string - artistNames: string - releaseDate: string - participantGroupList: NaverParticipantGroup[] + trackId: number; + trackName: string; + artistIds: string; + artistNames: string; + releaseDate: string; + participantGroupList: NaverParticipantGroup[]; } export interface NaverParticipantGroup { - roleName: string - participantList: NaverParticipant[] + roleName: string; + participantList: NaverParticipant[]; } export interface NaverParticipant { - id: number - name: string - likeCount: number - imageUrl: string | null + id: number; + name: string; + likeCount: number; + imageUrl: string | null; } // Track Detail (/track/{id}.json) @@ -255,8 +255,8 @@ export interface NaverPartialAlbumWithTracks extends NaverPartialAlbum { export interface ApiError { response: { message: { - apiStatusCode: string // Seems to be either `NO_SUCH_RESOURCE` or `PROCESS_FAIL` - text: string - } - } + apiStatusCode: string; // Seems to be either `NO_SUCH_RESOURCE` or `PROCESS_FAIL` + text: string; + }; + }; } diff --git a/providers/Vibe/mod.ts b/providers/Vibe/mod.ts index db1a7f9d..c158db61 100644 --- a/providers/Vibe/mod.ts +++ b/providers/Vibe/mod.ts @@ -12,13 +12,23 @@ import { Label, LinkType, } from '@/harmonizer/types.ts'; -import { ApiError, NaverAlbum, NaverAlbumResult, NaverArtistTracksResult, NaverPartialArtist, NaverParticipant, NaverResponse, NaverTrack, NaverTrackCreditsResult } from './api_types.ts'; +import { + ApiError, + NaverAlbum, + NaverAlbumResult, + NaverArtistTracksResult, + NaverPartialArtist, + NaverParticipant, + NaverResponse, + NaverTrack, + NaverTrackCreditsResult, +} from './api_types.ts'; export default class VibeProvider extends MetadataApiProvider { readonly name = 'Naver VIBE'; override get internalName(): string { - return 'vibe' + return 'vibe'; } readonly supportedUrls = new URLPattern({ @@ -45,7 +55,7 @@ export default class VibeProvider extends MetadataApiProvider { override readonly launchDate: PartialDate = { year: 2018, month: 6, - day: 11 + day: 11, }; readonly apiBaseUrl = 'https://apis.naver.com/vibeWeb/musicapiweb/'; @@ -71,7 +81,6 @@ export default class VibeProvider extends MetadataApiProvider { } export class VibeReleaseLookup extends ReleaseApiLookup { - constructReleaseApiUrl(): URL { return new URL(`album/${this.lookup.value}.json`, this.provider.apiBaseUrl); } @@ -113,19 +122,19 @@ export class VibeReleaseLookup extends ReleaseApiLookup label != undefined && label!=null); + const rawLabels = [album.agencyName, album.productionName].filter((label) => label != undefined && label != null); return rawLabels.map((label) => ({ name: label, })); } private formatAlbumDate(date: string): string { - return date.split(".").join("-"); + return date.split('.').join('-'); } private getAlbumImage(url: string | undefined): Artwork[] | undefined { if (!url) return undefined; - const imgRegex = /https:\/\/musicmeta-phinf\.pstatic\.net\/[^?]*/ + const imgRegex = /https:\/\/musicmeta-phinf\.pstatic\.net\/[^?]*/; return [{ url: url.match(imgRegex)?.[0] || url, thumbUrl: url, @@ -167,9 +176,12 @@ export class VibeReleaseLookup extends ReleaseApiLookup> { const apiUrl = new URL(`album/${id}/tracks.json`, this.provider.apiBaseUrl); - const { content: naverResult, timestamp } = await this.provider.query>(apiUrl, { - snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, - }); + const { content: naverResult, timestamp } = await this.provider.query>( + apiUrl, + { + snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, + }, + ); this.updateCacheTime(timestamp); return naverResult; } @@ -193,13 +205,13 @@ export class VibeReleaseLookup extends ReleaseApiLookup { - const trackFeatVariations = ['feat.', 'ft.', 'with'] + const trackFeatVariations = ['feat.', 'ft.', 'with']; if (!trackFeatVariations.some((fv) => rawTrack.trackTitle.toLocaleLowerCase().includes(fv))) { // Skip fetching credits if track name doesn't have some variation of 'feat.' since harmony can't use anything else at the moment // From what I can tell, all tracks on naver with featuring artists have one of the above variations in the title, even the ones with titles in other languages @@ -208,7 +220,7 @@ export class VibeReleaseLookup extends ReleaseApiLookup role.roleName === "피쳐링").flatMap((role) => role.participantList); + const featuringArtists = roles.filter((role) => role.roleName === '피쳐링').flatMap((role) => role.participantList); // const otherArtists = roles.flatMap((role) => role.participantList); // ^ other artist roles: // 작사 (Lyricist) @@ -225,49 +237,57 @@ export class VibeReleaseLookup extends ReleaseApiLookup artist.id); - const allArtists = [... this.toParticipants(rawTrack.artists), ...featuringArtists]; - const uniqueArtists = Array.from(new Map(allArtists.map(artist => [artist.id, artist])).values()); - const credits: ArtistCreditName[] = [] + const allArtists = [...this.toParticipants(rawTrack.artists), ...featuringArtists]; + const uniqueArtists = Array.from(new Map(allArtists.map((artist) => [artist.id, artist])).values()); + const credits: ArtistCreditName[] = []; let hasMarkedFeat = false; uniqueArtists.forEach((aritst, index) => { - const nextFeaturing = (index < uniqueArtists.length - 1 && featuringIds.includes(uniqueArtists[index+1].id)); - const secondNextFeaturing = (index < uniqueArtists.length - 2 && featuringIds.includes(uniqueArtists[index+2].id)); - credits.push(this.convertRawParticipant(aritst, nextFeaturing, hasMarkedFeat, secondNextFeaturing)) + const nextFeaturing = index < uniqueArtists.length - 1 && featuringIds.includes(uniqueArtists[index + 1].id); + const secondNextFeaturing = index < uniqueArtists.length - 2 && + featuringIds.includes(uniqueArtists[index + 2].id); + credits.push(this.convertRawParticipant(aritst, nextFeaturing, hasMarkedFeat, secondNextFeaturing)); if (nextFeaturing) hasMarkedFeat = true; - }) + }); return credits; } - private convertRawParticipant(rawArtist: NaverParticipant, nextFeaturing = false, hasMarkedFeat = false, secondNextFeaturing = false): ArtistCreditName { + private convertRawParticipant( + rawArtist: NaverParticipant, + nextFeaturing = false, + hasMarkedFeat = false, + secondNextFeaturing = false, + ): ArtistCreditName { return { name: rawArtist.name, creditedName: rawArtist.name, externalIds: this.provider.makeExternalIds({ type: 'artist', id: String(rawArtist.id) }), - joinPhrase: !hasMarkedFeat ? nextFeaturing ? ' feat. ' : secondNextFeaturing ? ' & ': undefined: undefined + joinPhrase: !hasMarkedFeat ? nextFeaturing ? ' feat. ' : secondNextFeaturing ? ' & ' : undefined : undefined, }; } private async getRawTrackCredits(id: number): Promise> { const apiUrl = new URL(`track/${id}/credits.json`, this.provider.apiBaseUrl); - const { content: naverResult, timestamp } = await this.provider.query>(apiUrl, { - snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, - }); + const { content: naverResult, timestamp } = await this.provider.query>( + apiUrl, + { + snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, + }, + ); this.updateCacheTime(timestamp); return naverResult; } private getTrackDuration(duration: string): number { - const segments = duration.split(":"); - let ms = 0; - if (segments.length == 2) { - ms += Number(segments[0]) * 60 * 1000; - ms += Number(segments[1]) * 1000; - } else if (segments.length == 1){ - ms += Number(segments[1]) * 1000; - } - return ms; + const segments = duration.split(':'); + let ms = 0; + if (segments.length == 2) { + ms += Number(segments[0]) * 60 * 1000; + ms += Number(segments[1]) * 1000; + } else if (segments.length == 1) { + ms += Number(segments[1]) * 1000; + } + return ms; } - } class VibeResponseError extends ResponseError { From ee1e023a68bedac74340c45a8efa5947b4591d60 Mon Sep 17 00:00:00 2001 From: Lioncat6 <95449321+Lioncat6@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:19:52 -0500 Subject: [PATCH 4/9] chore(vibe): Unwrap raw API responses --- providers/Vibe/mod.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/providers/Vibe/mod.ts b/providers/Vibe/mod.ts index c158db61..45b82211 100644 --- a/providers/Vibe/mod.ts +++ b/providers/Vibe/mod.ts @@ -21,6 +21,7 @@ import { NaverParticipant, NaverResponse, NaverTrack, + NaverTrackCredits, NaverTrackCreditsResult, } from './api_types.ts'; @@ -158,7 +159,7 @@ export class VibeReleaseLookup extends ReleaseApiLookup> { + private async getRawTracklist(id: number): Promise { const apiUrl = new URL(`album/${id}/tracks.json`, this.provider.apiBaseUrl); - const { content: naverResult, timestamp } = await this.provider.query>( + const { content, timestamp } = await this.provider.query>( apiUrl, { snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, }, ); this.updateCacheTime(timestamp); - return naverResult; + return content.response.result.tracks; } private async convertRawTrack(rawTrack: NaverTrack): Promise { @@ -218,7 +219,7 @@ export class VibeReleaseLookup extends ReleaseApiLookup this.convertRawArtist(artist)); } // Fetching credits is pretty inexpensive, so maybe ^ isn't neccesary to avoid extra calls - const trackCredits = (await this.getRawTrackCredits(rawTrack.trackId)).response.result.trackCredits; + const trackCredits = await this.getRawTrackCredits(rawTrack.trackId); const roles = trackCredits.participantGroupList; const featuringArtists = roles.filter((role) => role.roleName === '피쳐링').flatMap((role) => role.participantList); // const otherArtists = roles.flatMap((role) => role.participantList); @@ -265,16 +266,16 @@ export class VibeReleaseLookup extends ReleaseApiLookup> { + private async getRawTrackCredits(id: number): Promise { const apiUrl = new URL(`track/${id}/credits.json`, this.provider.apiBaseUrl); - const { content: naverResult, timestamp } = await this.provider.query>( + const { content, timestamp } = await this.provider.query>( apiUrl, { snapshotMaxTimestamp: this.options.snapshotMaxTimestamp, }, ); this.updateCacheTime(timestamp); - return naverResult; + return content.response.result.trackCredits; } private getTrackDuration(duration: string): number { From f4125c91a9271f763c793194e0dd0dc0f52b754b Mon Sep 17 00:00:00 2001 From: Lioncat6 <95449321+Lioncat6@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:02:24 -0500 Subject: [PATCH 5/9] chore(vibe): Use parseDuration for track length --- providers/Vibe/mod.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/providers/Vibe/mod.ts b/providers/Vibe/mod.ts index 45b82211..e3bb8fd2 100644 --- a/providers/Vibe/mod.ts +++ b/providers/Vibe/mod.ts @@ -1,6 +1,7 @@ import { type ApiQueryOptions, type CacheEntry, MetadataApiProvider, ReleaseApiLookup } from '@/providers/base.ts'; import { DurationPrecision, FeatureQuality, FeatureQualityMap } from '@/providers/features.ts'; import { parseHyphenatedDate, PartialDate } from '@/utils/date.ts'; +import { parseDuration } from '@/utils/time.ts'; import { ProviderError, ResponseError } from '@/utils/errors.ts'; import { ArtistCreditName, @@ -193,7 +194,7 @@ export class VibeReleaseLookup extends ReleaseApiLookup this.convertRawArtist(artist)), <- Misses featuring artist info artists: await this.getTrackArtists(rawTrack), number: rawTrack.trackNumber, - length: this.getTrackDuration(rawTrack.playTime), + length: parseDuration(rawTrack.playTime) * 1000, recording: { externalIds: this.provider.makeExternalIds({ type: 'track', id: String(rawTrack.trackId) }), }, @@ -277,18 +278,6 @@ export class VibeReleaseLookup extends ReleaseApiLookup Date: Sun, 5 Apr 2026 17:29:54 -0500 Subject: [PATCH 6/9] test(vibe): Add more comprehensive tests for artist credits / join phrases --- providers/Vibe/__snapshots__/mod.test.ts.snap | 1670 +++++++++++++++++ providers/Vibe/mod.test.ts | 58 + .../vibeWeb/musicapiweb/album/10304443.json | 1 + .../musicapiweb/album/10304443/tracks.json | 1 + .../vibeWeb/musicapiweb/album/3034414.json | 1 + .../musicapiweb/album/3034414/tracks.json | 1 + .../musicapiweb/track/27139986/credits.json | 1 + .../musicapiweb/track/27139987/credits.json | 1 + .../musicapiweb/track/27139988/credits.json | 1 + .../musicapiweb/track/27139989/credits.json | 1 + .../musicapiweb/track/27139990/credits.json | 1 + .../musicapiweb/track/27139991/credits.json | 1 + .../musicapiweb/track/27139992/credits.json | 1 + .../musicapiweb/track/27139994/credits.json | 1 + .../musicapiweb/track/27139995/credits.json | 1 + .../musicapiweb/track/27139997/credits.json | 1 + .../musicapiweb/track/27139999/credits.json | 1 + .../musicapiweb/track/27140000/credits.json | 1 + .../musicapiweb/track/67155383/credits.json | 1 + .../musicapiweb/track/67155387/credits.json | 1 + .../musicapiweb/track/67155388/credits.json | 1 + .../musicapiweb/track/67155389/credits.json | 1 + .../musicapiweb/track/67155392/credits.json | 1 + .../musicapiweb/track/67155396/credits.json | 1 + .../musicapiweb/track/67155403/credits.json | 1 + .../musicapiweb/track/67155410/credits.json | 1 + .../musicapiweb/track/67155417/credits.json | 1 + .../musicapiweb/track/67155422/credits.json | 1 + 28 files changed, 1754 insertions(+) create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443/tracks.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414/tracks.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139986/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139987/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139988/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139989/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139990/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139991/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139992/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139994/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139995/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139997/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139999/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27140000/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155383/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155387/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155388/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155389/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155392/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155396/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155403/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155410/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155417/credits.json create mode 100644 testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155422/credits.json diff --git a/providers/Vibe/__snapshots__/mod.test.ts.snap b/providers/Vibe/__snapshots__/mod.test.ts.snap index bd7cc9a1..11eb04c9 100644 --- a/providers/Vibe/__snapshots__/mod.test.ts.snap +++ b/providers/Vibe/__snapshots__/mod.test.ts.snap @@ -185,3 +185,1673 @@ snapshot[`Naver provider > release lookup > single with a featuring artist 1`] = title: "THE RINGMASTER", } `; + +snapshot[`Naver provider > release lookup > album with many featuring artists 1`] = ` +{ + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + name: "Sebastián Orellana", + }, + ], + externalLinks: [ + { + types: [ + "paid streaming", + "paid download", + ], + url: "https://vibe.naver.com/album/10304443", + }, + ], + images: [ + { + thumbUrl: "https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521", + types: [ + "front", + ], + url: "https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: "https://apis.naver.com/vibeWeb/musicapiweb/album/10304443.json", + id: "10304443", + internalName: "vibe", + lookup: { + method: "id", + value: "10304443", + }, + name: "Naver VIBE", + url: "https://vibe.naver.com/album/10304443", + }, + ], + }, + labels: [ + { + name: "Happy Place Records", + }, + { + name: "오차드", + }, + ], + media: [ + { + format: "Digital Media", + number: 1, + tracklist: [ + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 190000, + number: 1, + recording: { + externalIds: [ + { + id: "67155383", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Amorcito Corazón (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 156000, + number: 2, + recording: { + externalIds: [ + { + id: "67155387", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Sin Amor (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 193000, + number: 3, + recording: { + externalIds: [ + { + id: "67155388", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Desesperación (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 173000, + number: 4, + recording: { + externalIds: [ + { + id: "67155389", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Caribe (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 202000, + number: 5, + recording: { + externalIds: [ + { + id: "67155392", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Escríbeme (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 202000, + number: 6, + recording: { + externalIds: [ + { + id: "67155396", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Una Copa Más (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 181000, + number: 7, + recording: { + externalIds: [ + { + id: "67155403", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Dos Almas (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 180000, + number: 8, + recording: { + externalIds: [ + { + id: "67155410", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Te Engañaron Corazon (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 208000, + number: 9, + recording: { + externalIds: [ + { + id: "67155417", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'Aunque Me Cueste la Vida (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + { + artists: [ + { + creditedName: "Jose guapacha", + externalIds: [ + { + id: "4475421", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Jose guapacha", + }, + { + creditedName: "Sebastián Orellana", + externalIds: [ + { + id: "2578826", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Sebastián Orellana", + }, + { + creditedName: 'Fernando "Percu" García', + externalIds: [ + { + id: "5644012", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: 'Fernando "Percu" García', + }, + { + creditedName: "Javier Mora", + externalIds: [ + { + id: "829948", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Javier Mora", + }, + { + creditedName: "Martín Benavides", + externalIds: [ + { + id: "3798873", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Martín Benavides", + }, + ], + length: 203000, + number: 10, + recording: { + externalIds: [ + { + id: "67155422", + provider: "vibe", + type: "track", + }, + ], + }, + title: 'En el Juego de la Vida (Feat. Fernando "Percu" García, Javier Mora, Martín Benavides)', + }, + ], + }, + ], + packaging: "None", + releaseDate: { + date: { + day: 2, + month: 9, + year: 2022, + }, + quality: "assumed-valid", + }, + status: "Official", + title: "Sebastian Orellana Presenta a José Guapachá", +} +`; + +snapshot[`Naver provider > release lookup > album with differing numbers of featuring artists 1`] = ` +{ + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + name: "Steve Aoki", + }, + ], + externalLinks: [ + { + types: [ + "paid streaming", + "paid download", + ], + url: "https://vibe.naver.com/album/3034414", + }, + ], + images: [ + { + thumbUrl: "https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012", + types: [ + "front", + ], + url: "https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg", + }, + ], + info: { + messages: [], + providers: [ + { + apiUrl: "https://apis.naver.com/vibeWeb/musicapiweb/album/3034414.json", + id: "3034414", + internalName: "vibe", + lookup: { + method: "id", + value: "3034414", + }, + name: "Naver VIBE", + url: "https://vibe.naver.com/album/3034414", + }, + ], + }, + labels: [ + { + name: "Ultra Records, LLC", + }, + { + name: "소니뮤직", + }, + ], + media: [ + { + format: "Digital Media", + number: 1, + tracklist: [ + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + name: "Steve Aoki", + }, + ], + length: 256000, + number: 1, + recording: { + externalIds: [ + { + id: "27139984", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Neon Future III (Intro)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + name: "Steve Aoki", + }, + { + creditedName: "Louis Tomlinson(One Direction)", + externalIds: [ + { + id: "200191", + provider: "vibe", + type: "artist", + }, + ], + name: "Louis Tomlinson(One Direction)", + }, + ], + length: 199000, + number: 2, + recording: { + externalIds: [ + { + id: "27139985", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Just Hold On", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "방탄소년단", + externalIds: [ + { + id: "143179", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "방탄소년단", + }, + ], + length: 192000, + number: 3, + recording: { + externalIds: [ + { + id: "27139986", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Waste It On Me (Feat. 방탄소년단)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Steve Aoki", + }, + { + creditedName: "Nicky Romero", + externalIds: [ + { + id: "155746", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Nicky Romero", + }, + { + creditedName: "Kiiara", + externalIds: [ + { + id: "463128", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Kiiara", + }, + ], + length: 205000, + number: 4, + recording: { + externalIds: [ + { + id: "27139987", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Be Somebody (Feat. Kiiara)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Lil Yachty", + externalIds: [ + { + id: "498506", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Lil Yachty", + }, + { + creditedName: "AJR", + externalIds: [ + { + id: "327998", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "AJR", + }, + ], + length: 189000, + number: 5, + recording: { + externalIds: [ + { + id: "27139994", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Pretender (Feat. Lil Yachty, AJR)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Mike Posner", + externalIds: [ + { + id: "134549", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Mike Posner", + }, + ], + length: 208000, + number: 6, + recording: { + externalIds: [ + { + id: "27139989", + provider: "vibe", + type: "track", + }, + ], + }, + title: "A Lover And A Memory (Feat. Mike Posner)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "blink-182", + externalIds: [ + { + id: "1529", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "blink-182", + }, + ], + length: 228000, + number: 7, + recording: { + externalIds: [ + { + id: "27139995", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Why Are We So Broken (Feat. blink-182)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Jim Adkins", + externalIds: [ + { + id: "86032", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Jim Adkins", + }, + ], + length: 203000, + number: 8, + recording: { + externalIds: [ + { + id: "27139988", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Golden Days (Feat. Jim Adkins)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Lady Antebellum", + externalIds: [ + { + id: "125978", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Lady Antebellum", + }, + ], + length: 173000, + number: 9, + recording: { + externalIds: [ + { + id: "27139997", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Our Love Glows (Feat. Lady Antebellum)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Era Istrefi", + externalIds: [ + { + id: "484250", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Era Istrefi", + }, + ], + length: 179000, + number: 10, + recording: { + externalIds: [ + { + id: "27139991", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Anything More (Feat. Era Istrefi)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + name: "Steve Aoki", + }, + { + creditedName: "Lauren Jauregui", + externalIds: [ + { + id: "1409759", + provider: "vibe", + type: "artist", + }, + ], + name: "Lauren Jauregui", + }, + ], + length: 205000, + number: 11, + recording: { + externalIds: [ + { + id: "27139996", + provider: "vibe", + type: "track", + }, + ], + }, + title: "All Night", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Bella Thorne", + externalIds: [ + { + id: "166213", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Bella Thorne", + }, + ], + length: 163000, + number: 12, + recording: { + externalIds: [ + { + id: "27139990", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Do Not Disturb (Feat. Bella Thorne)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Ina Wroldsen", + externalIds: [ + { + id: "350183", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Ina Wroldsen", + }, + ], + length: 179000, + number: 13, + recording: { + externalIds: [ + { + id: "27139999", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Lie To Me (Feat. Ina Wroldsen)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + name: "Steve Aoki", + }, + { + creditedName: "Daddy Yankee", + externalIds: [ + { + id: "12634", + provider: "vibe", + type: "artist", + }, + ], + name: "Daddy Yankee", + }, + { + creditedName: "Play-N-Skillz", + externalIds: [ + { + id: "537644", + provider: "vibe", + type: "artist", + }, + ], + name: "Play-N-Skillz", + }, + { + creditedName: "Elvis Crespo", + externalIds: [ + { + id: "3139", + provider: "vibe", + type: "artist", + }, + ], + name: "Elvis Crespo", + }, + ], + length: 226000, + number: 14, + recording: { + externalIds: [ + { + id: "27139993", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Azukita", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + name: "Steve Aoki", + }, + { + creditedName: "TWIIG", + externalIds: [ + { + id: "2383832", + provider: "vibe", + type: "artist", + }, + ], + name: "TWIIG", + }, + ], + length: 207000, + number: 15, + recording: { + externalIds: [ + { + id: "27139998", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Hoovela", + }, + { + artists: [ + { + creditedName: "Don Diablo", + externalIds: [ + { + id: "15765", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Don Diablo", + }, + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Steve Aoki", + }, + { + creditedName: "Lush", + externalIds: [ + { + id: "6656", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " & ", + name: "Lush", + }, + { + creditedName: "Simon", + externalIds: [ + { + id: "347341", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Simon", + }, + { + creditedName: "Bullysongs", + externalIds: [ + { + id: "349221", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Bullysongs", + }, + ], + length: 203000, + number: 16, + recording: { + externalIds: [ + { + id: "27139992", + provider: "vibe", + type: "track", + }, + ], + }, + title: "What We Started (Feat. Bullysongs)", + }, + { + artists: [ + { + creditedName: "Steve Aoki", + externalIds: [ + { + id: "134560", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: " feat. ", + name: "Steve Aoki", + }, + { + creditedName: "Bill Nye", + externalIds: [ + { + id: "1829984", + provider: "vibe", + type: "artist", + }, + ], + joinPhrase: undefined, + name: "Bill Nye", + }, + ], + length: 219000, + number: 17, + recording: { + externalIds: [ + { + id: "27140000", + provider: "vibe", + type: "track", + }, + ], + }, + title: "Noble Gas (Feat. Bill Nye)", + }, + ], + }, + ], + packaging: "None", + releaseDate: { + date: { + day: 9, + month: 11, + year: 2018, + }, + quality: "assumed-valid", + }, + status: "Official", + title: "Neon Future III", +} +`; diff --git a/providers/Vibe/mod.test.ts b/providers/Vibe/mod.test.ts index 0e6bb29c..0cb14951 100644 --- a/providers/Vibe/mod.test.ts +++ b/providers/Vibe/mod.test.ts @@ -7,6 +7,7 @@ import { assertSnapshot } from '@std/testing/snapshot'; import VibeProvider from './mod.ts'; import { assertEquals } from 'std/assert/assert_equals.ts'; +import { Tracklist } from '../../server/components/Tracklist.tsx'; describe('Naver provider', () => { const vibe = new VibeProvider(makeProviderOptions()); @@ -48,6 +49,63 @@ describe('Naver provider', () => { const allTracks = release.media.flatMap((medium) => medium.tracklist); assert(allTracks[0].artists?.length === 2, 'Main track should have two artists'); }, + }, { + description: 'album with many featuring artists', + release: new URL('https://vibe.naver.com/album/10304443'), + options: releaseOptions, + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + const allTracks = release.media.flatMap((medium) => medium.tracklist); + const creditsMatrix = [ + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined], + [' & ', ' feat. ', undefined, undefined, undefined] + ] + assert(allTracks.length == creditsMatrix.length, `Album has the correct number of tracks`); + allTracks.forEach((track, index) => { + assert(track.artists?.length == creditsMatrix[index].length, `Track ${index+1} has the right number of artists`); + assert(track.artists?.every((artist, aIndex) => artist.joinPhrase == creditsMatrix[index][aIndex]), `Track ${index+1} artists match join phrases`); + }) + }, + }, { + description: 'album with differing numbers of featuring artists', + release: new URL('https://vibe.naver.com/album/3034414'), + options: releaseOptions, + assert: async (release, ctx) => { + await assertSnapshot(ctx, release); + const allTracks = release.media.flatMap((medium) => medium.tracklist); + const creditsMatrix = [ + [undefined], + [undefined, undefined], + [' feat. ', undefined], + [' & ', ' feat. ', undefined], + [' feat. ', undefined, undefined], + [' feat. ', undefined], + [' feat. ', undefined], + [' feat. ', undefined], + [' feat. ', undefined], + [' feat. ', undefined], + [undefined, undefined], + [' feat. ', undefined], + [' feat. ', undefined], + [undefined, undefined, undefined, undefined], + [undefined, undefined], + [undefined, undefined, ' & ', ' feat. ', undefined], + [' feat. ', undefined], + ] + assert(allTracks.length == creditsMatrix.length, `Album has the correct number of tracks`); + allTracks.forEach((track, index) => { + assert(track.artists?.length == creditsMatrix[index].length, `Track ${index+1} has the right number of artists`); + assert(track.artists?.every((artist, aIndex) => artist.joinPhrase == creditsMatrix[index][aIndex]), `Track ${index+1} matches join phrases`); + }) + }, }], }); diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443.json new file mode 100644 index 00000000..da94708b --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443.json @@ -0,0 +1 @@ +{"response":{"result":{"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","isRegular":true,"isAdult":false,"agencyName":"Happy Place Records","isDolbyAtmos":false,"hasDolbyAtmos":false,"productionName":"오차드","releaseDate":"2022.09.02","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","serviceStatusMsg":"STREAMING_DRM_PRDD_AODD","sizeAndDuration":"10곡, 31분 28초","trackTotalCount":10,"artistTotalCount":2,"albumGenres":"라틴","albumGenreList":["라틴"],"shareUrl":"https://vibe.naver.com/album/10304443","likeCount":0,"playtime":1888}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443/tracks.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443/tracks.json new file mode 100644 index 00000000..e45f8e2d --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/10304443/tracks.json @@ -0,0 +1 @@ +{"response":{"result":{"trackTotalCount":10,"tracks":[{"trackId":67155383,"trackTitle":"Amorcito Corazón (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":1,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:10","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155387,"trackTitle":"Sin Amor (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":2,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"02:36","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155388,"trackTitle":"Desesperación (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":3,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:13","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155389,"trackTitle":"Caribe (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":4,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"02:53","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155392,"trackTitle":"Escríbeme (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":5,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:22","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155396,"trackTitle":"Una Copa Más (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":6,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:22","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155403,"trackTitle":"Dos Almas (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":7,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:01","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155410,"trackTitle":"Te Engañaron Corazon (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":8,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:00","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155417,"trackTitle":"Aunque Me Cueste la Vida (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":9,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:28","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false},{"trackId":67155422,"trackTitle":"En el Juego de la Vida (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","represent":false,"discNumber":1,"trackNumber":10,"artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"album":{"albumId":10304443,"albumTitle":"Sebastian Orellana Presenta a José Guapachá","releaseDate":"2022.09.02","imageUrl":"https://musicmeta-phinf.pstatic.net/album/010/304/10304443.jpg?type=r480Fll&v=20230921043521","artists":[{"artistId":4475421,"artistName":"Jose guapacha"},{"artistId":2578826,"artistName":"Sebastián Orellana"}],"artistTotalCount":2},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:23","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":0,"score":0,"isTopPopular":false}]}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414.json new file mode 100644 index 00000000..892e831c --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414.json @@ -0,0 +1 @@ +{"response":{"result":{"album":{"albumId":3034414,"albumTitle":"Neon Future III","isRegular":true,"isAdult":false,"agencyName":"Ultra Records, LLC","isDolbyAtmos":false,"hasDolbyAtmos":false,"productionName":"소니뮤직","releaseDate":"2018.11.09","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","serviceStatusMsg":"PARTIAL_AVAILABLE","sizeAndDuration":"17곡, 57분 14초","trackTotalCount":17,"artistTotalCount":1,"albumGenres":"댄스","albumGenreList":["댄스"],"shareUrl":"https://vibe.naver.com/album/3034414","likeCount":102,"playtime":3434}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414/tracks.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414/tracks.json new file mode 100644 index 00000000..e955936c --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/album/3034414/tracks.json @@ -0,0 +1 @@ +{"response":{"result":{"trackTotalCount":17,"tracks":[{"trackId":27139984,"trackTitle":"Neon Future III (Intro)","represent":false,"discNumber":1,"trackNumber":1,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"04:16","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":11,"score":140,"isTopPopular":false},{"trackId":27139985,"trackTitle":"Just Hold On","represent":true,"discNumber":1,"trackNumber":2,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"},{"artistId":200191,"artistName":"Louis Tomlinson(One Direction)","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/200/200191.jpg?type=r300&v=20231101021154"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":true,"hasSyncLyric":true,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:19","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":36,"score":2139,"isTopPopular":false},{"trackId":27139986,"trackTitle":"Waste It On Me (Feat. 방탄소년단)","represent":true,"discNumber":1,"trackNumber":3,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":true,"hasSyncLyric":true,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:12","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":600,"score":110113,"isTopPopular":true},{"trackId":27139987,"trackTitle":"Be Somebody (Feat. Kiiara)","represent":false,"discNumber":1,"trackNumber":4,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"},{"artistId":155746,"artistName":"Nicky Romero","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/155/155746.jpg?type=r300&v=20231101024230"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:25","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":15,"score":418,"isTopPopular":false},{"trackId":27139994,"trackTitle":"Pretender (Feat. Lil Yachty, AJR)","represent":false,"discNumber":1,"trackNumber":5,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:09","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":32,"score":794,"isTopPopular":false},{"trackId":27139989,"trackTitle":"A Lover And A Memory (Feat. Mike Posner)","represent":false,"discNumber":1,"trackNumber":6,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:28","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":8,"score":46,"isTopPopular":false},{"trackId":27139995,"trackTitle":"Why Are We So Broken (Feat. blink-182)","represent":false,"discNumber":1,"trackNumber":7,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:48","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":9,"score":76,"isTopPopular":false},{"trackId":27139988,"trackTitle":"Golden Days (Feat. Jim Adkins)","represent":false,"discNumber":1,"trackNumber":8,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:23","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":9,"score":27,"isTopPopular":false},{"trackId":27139997,"trackTitle":"Our Love Glows (Feat. Lady Antebellum)","represent":false,"discNumber":1,"trackNumber":9,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"02:53","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":6,"score":36,"isTopPopular":false},{"trackId":27139991,"trackTitle":"Anything More (Feat. Era Istrefi)","represent":false,"discNumber":1,"trackNumber":10,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"02:59","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":20,"score":250,"isTopPopular":false},{"trackId":27139996,"trackTitle":"All Night","represent":false,"discNumber":1,"trackNumber":11,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"},{"artistId":1409759,"artistName":"Lauren Jauregui","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/001/409/1409759.jpg?type=r300&v=20231220150803"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:25","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":9,"score":91,"isTopPopular":false},{"trackId":27139990,"trackTitle":"Do Not Disturb (Feat. Bella Thorne)","represent":false,"discNumber":1,"trackNumber":12,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"02:43","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":15,"score":188,"isTopPopular":false},{"trackId":27139999,"trackTitle":"Lie To Me (Feat. Ina Wroldsen)","represent":false,"discNumber":1,"trackNumber":13,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"02:59","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":12,"score":165,"isTopPopular":false},{"trackId":27139993,"trackTitle":"Azukita","represent":false,"discNumber":1,"trackNumber":14,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"},{"artistId":12634,"artistName":"Daddy Yankee","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/012/12634.jpg?type=r300&v=20240513193244"},{"artistId":537644,"artistName":"Play-N-Skillz"},{"artistId":3139,"artistName":"Elvis Crespo","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/003/3139.jpg?type=r300&v=20240513195843"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:46","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":33,"score":2726,"isTopPopular":false},{"trackId":27139998,"trackTitle":"Hoovela","represent":false,"discNumber":1,"trackNumber":15,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"},{"artistId":2383832,"artistName":"TWIIG"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:27","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":7,"score":38,"isTopPopular":false},{"trackId":27139992,"trackTitle":"What We Started (Feat. Bullysongs)","represent":false,"discNumber":1,"trackNumber":16,"artists":[{"artistId":15765,"artistName":"Don Diablo","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/015/15765.jpg?type=r300&v=20250909093049"},{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"},{"artistId":6656,"artistName":"Lush","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/006/6656.jpg?type=r300&v=20240513154655"},{"artistId":347341,"artistName":"Simon"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":false,"isDownload":false,"isMobileDownload":false,"isAdult":false,"representDownloadPrice":700,"isPrdd":false,"isAodd":false,"isOversea":true,"playTime":"03:23","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":13,"score":55,"isTopPopular":false},{"trackId":27140000,"trackTitle":"Noble Gas (Feat. Bill Nye)","represent":false,"discNumber":1,"trackNumber":17,"artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"album":{"albumId":3034414,"albumTitle":"Neon Future III","releaseDate":"2018.11.09","imageUrl":"https://musicmeta-phinf.pstatic.net/album/003/034/3034414.jpg?type=r480Fll&v=20260324011012","artists":[{"artistId":134560,"artistName":"Steve Aoki","imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134560.jpg?type=r300&v=20251014181323"}],"artistTotalCount":1},"hasLyric":false,"hasSyncLyric":false,"isStreaming":true,"isDownload":true,"isMobileDownload":true,"isAdult":false,"representDownloadPrice":700,"isPrdd":true,"isAodd":true,"isOversea":true,"playTime":"03:39","isKaraokeEnabled":true,"isDolbyAtmos":false,"hasDolbyAtmos":false,"likeCount":8,"score":79,"isTopPopular":false}]}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139986/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139986/credits.json new file mode 100644 index 00000000..23f83bdb --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139986/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139986,"trackName":"Waste It On Me (Feat. 방탄소년단)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":2439493,"name":"Jeff Halavacs","likeCount":0,"imageUrl":null},{"id":446256,"name":"Ryan Ogren","likeCount":1,"imageUrl":null},{"id":2790385,"name":"Michael Gazzo","likeCount":0,"imageUrl":null},{"id":1621026,"name":"Nate Cyphert","likeCount":0,"imageUrl":null},{"id":135375,"name":"Sean Foreman","likeCount":2,"imageUrl":null},{"id":274317,"name":"RM","likeCount":12756,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/274/274317.jpg?type=r300&v=20260320130002"}]},{"roleName":"피쳐링","participantList":[{"id":143179,"name":"방탄소년단","likeCount":194573,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/143/143179.jpg?type=r300&v=20260320130001"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139987/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139987/credits.json new file mode 100644 index 00000000..ee502a19 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139987/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139987,"trackName":"Be Somebody (Feat. Kiiara)","artistIds":"134560,155746","artistNames":"Steve Aoki,Nicky Romero","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":463128,"name":"Kiiara","likeCount":1020,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/463/463128.jpg?type=r300&v=20240513175145"},{"id":2468408,"name":"Mike Gazzo","likeCount":0,"imageUrl":null},{"id":1294447,"name":"Ryan McMahon","likeCount":1,"imageUrl":null},{"id":1420691,"name":"Ben Berger","likeCount":0,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/001/420/1420691.jpg?type=r300&v=20260106141255"},{"id":481939,"name":"Georgia Ku","likeCount":80,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/481/481939.jpg?type=r300&v=20240513185836"},{"id":1677745,"name":"Simon Wilcox","likeCount":1,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":463128,"name":"Kiiara","likeCount":1020,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/463/463128.jpg?type=r300&v=20240513175145"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139988/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139988/credits.json new file mode 100644 index 00000000..6183dcb6 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139988/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139988,"trackName":"Golden Days (Feat. Jim Adkins)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":38467,"name":"Mark Hoppus","likeCount":3,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/038/38467.jpg?type=r300&v=20231101022748"},{"id":2251872,"name":"Travis Barker","likeCount":50,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/002/251/2251872.jpg?type=r300&v=20230223003006"},{"id":38647,"name":"John Feldmann","likeCount":2,"imageUrl":null},{"id":323558,"name":"Calum Hood","likeCount":4,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":86032,"name":"Jim Adkins","likeCount":23,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/086/86032.jpg?type=r300&v=20231220133125"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139989/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139989/credits.json new file mode 100644 index 00000000..b0470866 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139989/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139989,"trackName":"A Lover And A Memory (Feat. Mike Posner)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":167223,"name":"Tom Barnes","likeCount":1,"imageUrl":null},{"id":304726,"name":"Pete Kelleher","likeCount":1,"imageUrl":null},{"id":167226,"name":"Ben Kohn","likeCount":1,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":134549,"name":"Mike Posner","likeCount":376,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134549.jpg?type=r300&v=20240513155314"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139990/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139990/credits.json new file mode 100644 index 00000000..beb9a911 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139990/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139990,"trackName":"Do Not Disturb (Feat. Bella Thorne)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":1854679,"name":"Reid Stefanick","likeCount":0,"imageUrl":null},{"id":447541,"name":"Steve Shebby","likeCount":0,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":166213,"name":"Bella Thorne","likeCount":62,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/166/166213.jpg?type=r300&v=20230222231856"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139991/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139991/credits.json new file mode 100644 index 00000000..33ec4c45 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139991/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139991,"trackName":"Anything More (Feat. Era Istrefi)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":2463789,"name":"Henry Durham","likeCount":0,"imageUrl":null},{"id":329781,"name":"Cara Salimando","likeCount":0,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":484250,"name":"Era Istrefi","likeCount":118,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/484/484250.jpg?type=r300&v=20240721013721"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139992/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139992/credits.json new file mode 100644 index 00000000..4e1256d4 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139992/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139992,"trackName":"What We Started (Feat. Bullysongs)","artistIds":"15765,134560,6656,347341","artistNames":"Don Diablo,Steve Aoki,Lush,Simon","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":2550930,"name":"Alessandro Miselli","likeCount":0,"imageUrl":null},{"id":433784,"name":"Andrew Bullimore","likeCount":0,"imageUrl":null},{"id":2550931,"name":"Simone Privitera","likeCount":0,"imageUrl":null},{"id":277931,"name":"Josh Record","likeCount":21,"imageUrl":null},{"id":223815,"name":"Jason Walker","likeCount":69,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/223/223815.jpg?type=r300&v=20240425071955"}]},{"roleName":"피쳐링","participantList":[{"id":349221,"name":"Bullysongs","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139994/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139994/credits.json new file mode 100644 index 00000000..798b979d --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139994/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139994,"trackName":"Pretender (Feat. Lil Yachty, AJR)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":2695723,"name":"Ryan Metzger","likeCount":0,"imageUrl":null},{"id":2695730,"name":"Jack Metzger","likeCount":0,"imageUrl":null},{"id":1677578,"name":"Miles Parks McCollum","likeCount":0,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":498506,"name":"Lil Yachty","likeCount":1090,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/498/498506.jpg?type=r300&v=20240723125227"},{"id":327998,"name":"AJR","likeCount":7565,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/327/327998.jpg?type=r300&v=20231220125424"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139995/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139995/credits.json new file mode 100644 index 00000000..4feb8bae --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139995/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139995,"trackName":"Why Are We So Broken (Feat. blink-182)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":38467,"name":"Mark Hoppus","likeCount":3,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/038/38467.jpg?type=r300&v=20231101022748"},{"id":1989689,"name":"Matt Skiba","likeCount":0,"imageUrl":null},{"id":2251872,"name":"Travis Barker","likeCount":50,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/002/251/2251872.jpg?type=r300&v=20230223003006"},{"id":38647,"name":"John Feldmann","likeCount":2,"imageUrl":null},{"id":439298,"name":"Ian Kirkpatrick","likeCount":3,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":1529,"name":"blink-182","likeCount":627,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/001/1529.jpg?type=r300&v=20240513192910"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139997/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139997/credits.json new file mode 100644 index 00000000..708bf6cc --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139997/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139997,"trackName":"Our Love Glows (Feat. Lady Antebellum)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":134703,"name":"Hillary Scott","likeCount":2,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/134/134703.jpg?type=r300&v=20240513155221"},{"id":1041465,"name":"Mark Trussell","likeCount":0,"imageUrl":null},{"id":1733674,"name":"Emily Weisband","likeCount":13,"imageUrl":null},{"id":379742,"name":"Max Styler","likeCount":19,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/379/379742.jpg?type=r300&v=20240513191036"}]},{"roleName":"피쳐링","participantList":[{"id":125978,"name":"Lady Antebellum","likeCount":673,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/125/125978.jpg?type=r300&v=20240513194414"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139999/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139999/credits.json new file mode 100644 index 00000000..a57b40a4 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27139999/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27139999,"trackName":"Lie To Me (Feat. Ina Wroldsen)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":350183,"name":"Ina Wroldsen","likeCount":145,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/350/350183.jpg?type=r300&v=20240513182537"},{"id":1778623,"name":"Trevor Dahl","likeCount":1,"imageUrl":null},{"id":2732992,"name":"Bård Mathias Bonsaksen","likeCount":0,"imageUrl":null}]},{"roleName":"피쳐링","participantList":[{"id":350183,"name":"Ina Wroldsen","likeCount":145,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/350/350183.jpg?type=r300&v=20240513182537"}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27140000/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27140000/credits.json new file mode 100644 index 00000000..4ef77532 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/27140000/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":27140000,"trackName":"Noble Gas (Feat. Bill Nye)","artistIds":"134560","artistNames":"Steve Aoki","releaseDate":"2018.11.9","participantGroupList":[{"roleName":"작곡","participantList":[{"id":465284,"name":"Julien Marchal(줄리안 마샬)","likeCount":14,"imageUrl":"https://musicmeta-phinf.pstatic.net/artist/000/465/465284.jpg?type=r300&v=20230222232900"}]},{"roleName":"피쳐링","participantList":[{"id":1829984,"name":"Bill Nye","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155383/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155383/credits.json new file mode 100644 index 00000000..cf5ebded --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155383/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155383,"trackName":"Amorcito Corazón (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155387/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155387/credits.json new file mode 100644 index 00000000..6ea6f52a --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155387/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155387,"trackName":"Sin Amor (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155388/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155388/credits.json new file mode 100644 index 00000000..3d2c56b9 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155388/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155388,"trackName":"Desesperación (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155389/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155389/credits.json new file mode 100644 index 00000000..51dee239 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155389/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155389,"trackName":"Caribe (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155392/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155392/credits.json new file mode 100644 index 00000000..3bb7ff1c --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155392/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155392,"trackName":"Escríbeme (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155396/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155396/credits.json new file mode 100644 index 00000000..9ccbe26a --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155396/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155396,"trackName":"Una Copa Más (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155403/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155403/credits.json new file mode 100644 index 00000000..21d93147 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155403/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155403,"trackName":"Dos Almas (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155410/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155410/credits.json new file mode 100644 index 00000000..b05cb87e --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155410/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155410,"trackName":"Te Engañaron Corazon (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155417/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155417/credits.json new file mode 100644 index 00000000..099e534a --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155417/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155417,"trackName":"Aunque Me Cueste la Vida (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file diff --git a/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155422/credits.json b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155422/credits.json new file mode 100644 index 00000000..8eabeb94 --- /dev/null +++ b/testdata/https!/com.naver.apis/vibeWeb/musicapiweb/track/67155422/credits.json @@ -0,0 +1 @@ +{"response":{"result":{"trackCredits":{"trackId":67155422,"trackName":"En el Juego de la Vida (Feat. Fernando \"Percu\" García, Javier Mora, Martín Benavides)","artistIds":"4475421,2578826","artistNames":"Jose guapacha,Sebastián Orellana","releaseDate":"2022.9.2","participantGroupList":[{"roleName":"피쳐링","participantList":[{"id":5644012,"name":"Fernando \"Percu\" García","likeCount":0,"imageUrl":null},{"id":829948,"name":"Javier Mora","likeCount":0,"imageUrl":null},{"id":3798873,"name":"Martín Benavides","likeCount":0,"imageUrl":null}]}]}}}} \ No newline at end of file From d70ead86f564736ff6291c7a7f3450a7c0c9c836 Mon Sep 17 00:00:00 2001 From: Lioncat6 <95449321+Lioncat6@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:30:43 -0500 Subject: [PATCH 7/9] test(vibe): Formatting --- providers/Vibe/mod.test.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/providers/Vibe/mod.test.ts b/providers/Vibe/mod.test.ts index 0cb14951..9592d187 100644 --- a/providers/Vibe/mod.test.ts +++ b/providers/Vibe/mod.test.ts @@ -66,13 +66,19 @@ describe('Naver provider', () => { [' & ', ' feat. ', undefined, undefined, undefined], [' & ', ' feat. ', undefined, undefined, undefined], [' & ', ' feat. ', undefined, undefined, undefined], - [' & ', ' feat. ', undefined, undefined, undefined] - ] + [' & ', ' feat. ', undefined, undefined, undefined], + ]; assert(allTracks.length == creditsMatrix.length, `Album has the correct number of tracks`); allTracks.forEach((track, index) => { - assert(track.artists?.length == creditsMatrix[index].length, `Track ${index+1} has the right number of artists`); - assert(track.artists?.every((artist, aIndex) => artist.joinPhrase == creditsMatrix[index][aIndex]), `Track ${index+1} artists match join phrases`); - }) + assert( + track.artists?.length == creditsMatrix[index].length, + `Track ${index + 1} has the right number of artists`, + ); + assert( + track.artists?.every((artist, aIndex) => artist.joinPhrase == creditsMatrix[index][aIndex]), + `Track ${index + 1} artists match join phrases`, + ); + }); }, }, { description: 'album with differing numbers of featuring artists', @@ -99,12 +105,18 @@ describe('Naver provider', () => { [undefined, undefined], [undefined, undefined, ' & ', ' feat. ', undefined], [' feat. ', undefined], - ] + ]; assert(allTracks.length == creditsMatrix.length, `Album has the correct number of tracks`); allTracks.forEach((track, index) => { - assert(track.artists?.length == creditsMatrix[index].length, `Track ${index+1} has the right number of artists`); - assert(track.artists?.every((artist, aIndex) => artist.joinPhrase == creditsMatrix[index][aIndex]), `Track ${index+1} matches join phrases`); - }) + assert( + track.artists?.length == creditsMatrix[index].length, + `Track ${index + 1} has the right number of artists`, + ); + assert( + track.artists?.every((artist, aIndex) => artist.joinPhrase == creditsMatrix[index][aIndex]), + `Track ${index + 1} matches join phrases`, + ); + }); }, }], }); From 03bf03dd986595d841cc0bd57cda25624f9aa304 Mon Sep 17 00:00:00 2001 From: Lioncat6 Date: Wed, 8 Apr 2026 13:49:39 -0500 Subject: [PATCH 8/9] chore(vibe): Determine release link types --- providers/Vibe/api_types.ts | 1 + providers/Vibe/mod.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/providers/Vibe/api_types.ts b/providers/Vibe/api_types.ts index dfacf819..6544d77f 100644 --- a/providers/Vibe/api_types.ts +++ b/providers/Vibe/api_types.ts @@ -94,6 +94,7 @@ export interface NaverAlbum extends NaverAlbumBase { agencyName: string; productionName: string; artists: NaverPartialArtist[]; + serviceStatusMsg: string; sizeAndDuration: string; trackTotalCount: number; artistTotalCount: number; diff --git a/providers/Vibe/mod.ts b/providers/Vibe/mod.ts index e3bb8fd2..31ccd740 100644 --- a/providers/Vibe/mod.ts +++ b/providers/Vibe/mod.ts @@ -106,6 +106,18 @@ export class VibeReleaseLookup extends ReleaseApiLookup this.convertRawArtist(artist)), @@ -117,7 +129,7 @@ export class VibeReleaseLookup extends ReleaseApiLookup Date: Wed, 8 Apr 2026 13:49:49 -0500 Subject: [PATCH 9/9] test(vibe): Test for link types --- providers/Vibe/__snapshots__/mod.test.ts.snap | 6 +----- providers/Vibe/mod.test.ts | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/providers/Vibe/__snapshots__/mod.test.ts.snap b/providers/Vibe/__snapshots__/mod.test.ts.snap index 11eb04c9..b26a29db 100644 --- a/providers/Vibe/__snapshots__/mod.test.ts.snap +++ b/providers/Vibe/__snapshots__/mod.test.ts.snap @@ -19,7 +19,6 @@ snapshot[`Naver provider > release lookup > single with a featuring artist 1`] = { types: [ "paid streaming", - "paid download", ], url: "https://vibe.naver.com/album/34923420", }, @@ -1053,10 +1052,7 @@ snapshot[`Naver provider > release lookup > album with differing numbers of feat ], externalLinks: [ { - types: [ - "paid streaming", - "paid download", - ], + types: [], url: "https://vibe.naver.com/album/3034414", }, ], diff --git a/providers/Vibe/mod.test.ts b/providers/Vibe/mod.test.ts index 9592d187..f4f1e796 100644 --- a/providers/Vibe/mod.test.ts +++ b/providers/Vibe/mod.test.ts @@ -48,6 +48,7 @@ describe('Naver provider', () => { await assertSnapshot(ctx, release); const allTracks = release.media.flatMap((medium) => medium.tracklist); assert(allTracks[0].artists?.length === 2, 'Main track should have two artists'); + assert(release.externalLinks.find((link) => link.types?.includes('paid streaming') && !link.types.includes('paid download')), 'Release should be streamable but not downloadable') }, }, { description: 'album with many featuring artists', @@ -79,6 +80,7 @@ describe('Naver provider', () => { `Track ${index + 1} artists match join phrases`, ); }); + assert(release.externalLinks.find((link) => link.types?.includes('paid streaming') && link.types.includes('paid download')), 'Release should be streamable and downloadable') }, }, { description: 'album with differing numbers of featuring artists', @@ -117,6 +119,7 @@ describe('Naver provider', () => { `Track ${index + 1} matches join phrases`, ); }); + assert(release.externalLinks.find((link) => !link.types?.includes('paid streaming') && !link.types?.includes('paid download')), 'Release should not be streamable or downloadable') }, }], });