feat(Melon): Implement Melon provider - #234
Conversation
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
|
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
left a comment
There was a problem hiding this comment.
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.
|
|
||
| readonly supportedUrls = new URLPattern({ | ||
| hostname: 'www.melon.com', | ||
| pathname: String.raw`/:type(album|song|artist)/detail.htm\?\1Id=:id(\d+)`, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I admit that part was clanker output, but I didn't question it since it worked perfectly. The idea is that Melon always uses the same keyword for the path and the query parameter, and they should never mismatch. We don't really have to enforce that, though, so I can rewrite that part to eliminate the backreference if you like.
There was a problem hiding this comment.
No, I actually like the fact that the back-reference makes the pattern stricter.
I would have to have a deeper look into the specs or docs for URLPattern to see if the behavior of "query as part of the path" is expected or depending on the implementation, but I guess it is fine.
| 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I've been looking into this for a bit after submitting the PR, but couldn't find any release with the ISHOLDBACK set to false. I also couldn't find any release that wasn't available both for streaming and purchase. I updated the code for now to simply mark every release as streamable and downloadable, since that's also the default used with URL normalization in MusicBrainz right now.
|
|
||
| private extractLabels(planCnpy?: string): Label[] { | ||
| if (!planCnpy) return []; | ||
| return planCnpy.split(', ').map((name) => ({ name: name.trim() })); |
There was a problem hiding this comment.
We already have a utility function splitLabels which should be reused. Feel free to enhance its test cases with a Korean script example.
There was a problem hiding this comment.
It appears that splitLabels only works for splitting labels separated by slashes, not commas like Melon uses. Should I make the split character customizable in splitLabels then?
There was a problem hiding this comment.
Oh, sorry, I had overlooked that. If Melon specifically uses the comma as a separator, then it makes sense to keep this separate.
P.S. We could probably split on slash and comma in the general utility function even, given that this seems to be fine in case of my copyright parser script and release label fields are even simpler than copyright texts (where the comma is also used to structure the sentence).
There was a problem hiding this comment.
Sure! Should I make the required changes within this PR, or should that be a separate change later on? I can also do this on a separate branch/PR and then rebase this PR.
| cdList: MelonDisc[]; | ||
| } | ||
|
|
||
| function parseISSUEDATE(date: string): PartialDate { |
There was a problem hiding this comment.
We should revisit this helper as part of #169 one day 😇
| if (albumType === 'EP') return ['EP']; | ||
| if (albumType === '옴니버스') return ['Compilation']; | ||
| if (albumType === 'OST') return ['Soundtrack']; | ||
| if (albumType === '리믹스') return ['Single', 'Remix']; |
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I retraced the icon from their full name logo, which doesn't use perfect circles. However, I'll look if I can find an actual icon SVG in their Android app. That one seems more circle-ish to me when checking again.
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.