Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,853 changes: 1,853 additions & 0 deletions providers/Vibe/__snapshots__/mod.test.ts.snap

Large diffs are not rendered by default.

263 changes: 263 additions & 0 deletions providers/Vibe/api_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// Sourced from https://github.com/Lioncat6/SAMBL-React/blob/db2affebfff0b2614c0c839a3c6ef3043688b4b4/lib/providers/naver.ts
// License TBD

// Response Wrapper
export interface NaverResponse<TResult> {
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[];
serviceStatusMsg: string;
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;
};
};
}
130 changes: 130 additions & 0 deletions providers/Vibe/mod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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';
import { Tracklist } from '../../server/components/Tracklist.tsx';

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');
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',
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`,
);
});
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',
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`,
);
});
assert(release.externalLinks.find((link) => !link.types?.includes('paid streaming') && !link.types?.includes('paid download')), 'Release should not be streamable or downloadable')
},
}],
});

afterAll(() => {
lookupStub.restore();
});
});
Loading