Skip to content

feat(Melon): Implement Melon provider - #234

Open
Maxr1998 wants to merge 1 commit into
kellnerd:mainfrom
Maxr1998:melon-provider
Open

feat(Melon): Implement Melon provider#234
Maxr1998 wants to merge 1 commit into
kellnerd:mainfrom
Maxr1998:melon-provider

Conversation

@Maxr1998

Copy link
Copy Markdown
Contributor

Melon is a South Korean digital streaming service. This provider uses the mobile app's API, which is publicly accessible without authentication. The API returns album metadata and track listings via separate endpoints. The brand color and icon have been taken from the website.

To implement this change, I used the help of an AI tool (namely Claude Code using the Sonnet 4.6 model), which created the Melon API types from HTTP responses caputured with curl using the endpoints described in #203, and wrote the initial provider implementation (using the previous Bugs! provider as a reference). Regardless, all code changes have been personally reviewed by me, and I take full responsibility for them. I also selected and wrote the test cases myself.

Assisted-by: Claude:claude-sonnet-4.6

Closes #203.

Melon is a South Korean digital streaming service.
This provider uses the mobile app's API, which is publicly accessible without authentication. The API returns album metadata and track listings via separate endpoints.
The brand color and icon have been taken from the website.

To implement this change, I used the help of an AI tool (namely Claude Code using the Sonnet 4.6 model), which created the Melon API types from HTTP responses caputured with curl using the endpoints described in kellnerd#203, and wrote the initial provider implementation (using the previous Bugs! provider as a reference). Regardless, all code changes have been personally reviewed by me, and I take full responsibility for them. I also selected and wrote the test cases myself.

Assisted-by: Claude:claude-sonnet-4.6
@Maxr1998

Copy link
Copy Markdown
Contributor Author

Still plan to do some fine-tuning on the provider implementation and fix the ToDo I left there, but I think it is mostly ready to review already.

@kellnerd kellnerd added feature New feature or request provider Metadata provider labels Jul 20, 2026

@kellnerd kellnerd left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for giving this a shot. Sorry that it took quite a while till I came back to reviewing it, I needed a short break after your last PR and then it got too hot and my motivation was melting like ice in the sun.

Without having tried the provider so far, here is some initial feedback.

Comment thread providers/Melon/mod.ts
);
return {
url: originalUrl,
thumbUrl: largeUrl,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider using the 500px image for the thumbnail, most of the other provider thumbnails are even only half of that..

Comment thread providers/Melon/mod.ts

readonly supportedUrls = new URLPattern({
hostname: 'www.melon.com',
pathname: String.raw`/:type(album|song|artist)/detail.htm\?\1Id=:id(\d+)`,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I wouldn't have thought that this works, the search query is a separate property of the URLPattern usually. I have an unpublished provider which extracts the ID from the query that way, but this surely wouldn't allow us to have a regex back reference.

Comment thread providers/Melon/mod.ts
MelonSongListResponse,
} from './api_types.ts';

const API_BASE = 'https://m2.melon.com/m6';

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const API_BASE = 'https://m2.melon.com/m6';
const API_BASE = 'https://m2.melon.com/m6/';

allows us to use the two argument URL constructor.

Comment thread providers/Melon/mod.ts

export class MelonReleaseLookup extends ReleaseApiLookup<MelonProvider, MelonRawRelease> {
constructReleaseApiUrl(): URL {
const url = new URL(`${API_BASE}/v3/album/info.json`);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const url = new URL(`${API_BASE}/v3/album/info.json`);
const url = new URL('v3/album/info.json', API_BASE);

Comment thread providers/Melon/mod.ts
const albumId = this.lookup.value;

const infoUrl = this.constructReleaseApiUrl();
const songListUrl = new URL(`${API_BASE}/v2/album/song/list.json`);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const songListUrl = new URL(`${API_BASE}/v2/album/song/list.json`);
const songListUrl = new URL('v2/album/song/list.json', API_BASE);

Comment on lines +51 to +54
ISSERVICE: boolean;
/** Whether the song is marked as a title song, which is usually actively promoted, has an MV, and/or was previously released as a single. */
ISTITLESONG: boolean;
ISHOLDBACK: boolean;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you seen examples where ISSERVICE and ISHOLDBACK indicate unavailable tracks/releases or something else which is of interest?
There is also an ISFREE attribute in the testdata which you haven't used here.

Comment thread providers/Melon/mod.ts

private extractLabels(planCnpy?: string): Label[] {
if (!planCnpy) return [];
return planCnpy.split(', ').map((name) => ({ name: name.trim() }));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a utility function splitLabels which should be reused. Feel free to enhance its test cases with a Korean script example.

Comment thread providers/Melon/mod.ts
cdList: MelonDisc[];
}

function parseISSUEDATE(date: string): PartialDate {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should revisit this helper as part of #169 one day 😇

Comment thread providers/Melon/mod.ts
if (albumType === 'EP') return ['EP'];
if (albumType === '옴니버스') return ['Compilation'];
if (albumType === 'OST') return ['Soundtrack'];
if (albumType === '리믹스') return ['Single', 'Remix'];

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm the theory that this is only used for remix singles and not remix albums?

>
<path
fillRule='evenodd'
d='M3.89 15.25A8.0 7.95 0 1 0 19.89 15.25A8.0 7.95 0 1 0 3.89 15.25ZM8.34 15.25A3.55 3.84 0 1 0 15.44 15.25A3.55 3.84 0 1 0 8.34 15.25ZM15.3 3.7A2.95 2.95 0 1 0 21.2 3.7A2.95 2.95 0 1 0 15.3 3.7Z'

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice and simple logo.
I do roughly understand SVG path syntax and wonder whether it is intentional that the logo does not consist of perfect circles (A8.0 7.95 is x and y radius for example) or a conversion/rounding error at some point.
Not worth changing that right now, one day I may make Tabler-style icons for some of the providers and submit them upstream like I did for the MusicBrainz icon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request provider Metadata provider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Melon

2 participants