아카이브 / 개발

flo-genie-playlist-sync — FLO·지니뮤직 플레이리스트 동기화 PoC

유형개발
기간2026.06
역할설계·구현
구분개인 프로젝트
언어JavaScript (Node.js)
주요 기술Playwright · Node.js 20

목차프로젝트 요약 · 담당 범위 · 문제와 구현 접근 · 이 작업에서 한 일 · 결과물과 원문 · 구현 상세

프로젝트 요약

공개된 플레이리스트 API가 확인되지 않는 FLO와 지니뮤직 사이의 동기화를 위해, 로그인한 브라우저 세션의 요청을 캡처·분석하고 메타데이터로 곡을 매칭해 추가 전용 동기화 계획을 만드는 탐색 단계 도구.

담당 범위

설계·구현을 맡은 개인 프로젝트다. 공개 저장소에 코드와 사용 방법을 남겼다.

문제와 구현 접근

Playwright 전용 브라우저 프로필에서 XHR/fetch 요청을 기록하고, 기록된 요청을 점수화해 플레이리스트 CRUD와 곡 검색 API 후보를 찾는다. 캡처는 로컬에만 두고 git에서 제외하며, 인증 헤더와 민감해 보이는 요청 본문 키는 best-effort로 가린다. 가려지지 않는 항목은 README의 안전 모델(Safety Model) 절에 따로 적어 두었다.

서비스 간 곡 ID가 호환되지 않아 제목·아티스트·앨범을 정규화하고 ISRC·재생 시간을 함께 비교해 후보를 순위화하는 매칭 로직을 테스트와 함께 구현했다. 정규화한 플레이리스트 JSON으로 양방향·추가 전용 병합 계획을 만드는 드라이런 플래너를 두었다.

라이브 어댑터는 아직 스텁이고, 교차 서비스 쓰기는 캡처한 엔드포인트를 검토하기 전까지 의도적으로 막아 두었다. 비공식 연구용 도구로 본인 계정에서만 쓰고, DRM·CAPTCHA·로그인 확인을 우회하지 않으며, 각 서비스의 약관을 따라야 한다는 이용 조건을 달았다.

이 작업에서 한 일

요청 캡처·분류부터 곡 매칭과 드라이런 계획까지 작동하는 탐색 도구를 만들고, 어떤 스크립트가 실제 계정의 플레이리스트를 바꾸는지와 이용 조건을 문서로 남겼다.

결과물과 원문

관련 등록공보·논문·저장소는 아래 링크에서 볼 수 있다.

구현 상세

README공개 저장소의 구조·기능·실행 문서

FLO <-> Genie Playlist Sync

Design docs and local discovery tooling for syncing playlists between FLO and Genie Music.

FLO and Genie do not appear to publish user playlist CRUD APIs (see docs/OFFICIAL_API_CHECK.md). So this project starts with capture, analysis, and dry-run planning. It records the XHR/fetch traffic of a logged-in browser session, scores the captured requests to find playlist and search endpoints, matches tracks across services by metadata, and builds an add-only sync plan. Live mutation stays disabled until the captured endpoint map has been reviewed and configured.

Status

This is a discovery-stage proof of concept, not a finished sync tool.

What works today:

  • Network capture of XHR/fetch traffic from a Playwright persistent browser

profile. The JSONL log applies best-effort masking to a fixed list of auth headers and to request-body keys that look sensitive. See the Safety Model section for what is not masked.

  • A classifier that scores captured requests as playlist CRUD and

track-search candidates.

  • Headless FLO and Genie scripts. Each one searches for a track by title and

artist, ranks the results by metadata, and tries to add the best match to your own playlist. The FLO script then checks the playlist page to confirm the add.

  • A track matcher that compares normalized title, artist, and album, plus ISRC

and duration. It scores candidates with token Jaccard and Dice similarity and assigns confidence bands. It is covered by tests.

  • A dry-run planner that builds a two-way, add-only merge plan from normalized

playlist JSON.

What does not work yet:

  • The adapters in src/adapters/* are stubs. Every method throws, and live

apply refuses to run.

  • config/captured-endpoints.sample.json contains only TODO_FROM_CAPTURE

placeholders.

  • The sync CLI only writes a plan. Asking it for a live run (--no-dry-run)

fails on purpose.

config/services.json and package.json also contain capture configs for Melon, VIBE, Bugs, Spotify, and YouTube Music. These exist for research only. Their capture: and analyze: commands just record a browser session that you drive by hand. None of these services has its own automation script. Their capture host lists leave out the shared NAVER and Google login hosts (nid.naver.com, accounts.google.com), so traffic to those hosts is not recorded unless you pass --all-hosts.

Disclaimer / Terms of Use

  • This is an unofficial project. It is not affiliated with, endorsed by, or

supported by FLO, Genie Music, or any other service named in this repository.

  • No official API is used. The tools rely on the first-party websites' own

web traffic and UI, which can change or break without notice.

  • This is a personal-use research tool. Use it only with your own accounts and

your own playlist data.

  • Each service's terms of use apply, and they may restrict automated access.

You are responsible for complying with them.

  • Keep request volume low, and use a temporary playlist for capture runs.
  • The tools do not download or stream audio. They do not bypass DRM, CAPTCHA,

phone verification, payment gates, or login checks. You always complete login yourself, on the service's own page.

  • Live cross-service sync is disabled.

Requirements

  • Node.js 20 or later
  • Playwright Chromium (npx playwright install chromium)

Quick Start

npm install
npx playwright install chromium

npm test
npm run check
npm run sync:dry-run
  • npm test runs the diff-planner and track-matcher tests.
  • npm run check runs node --check on every .mjs file.
  • npm run sync:dry-run builds a plan from examples/*.json and writes it

to state/sync-plan.json.

Capture and analysis need a logged-in browser profile. Before running the FLO headless script, create a temporary playlist in FLO yourself (for example playlist-sync-smoke-YYYYMMDD) and pass its exact name with --playlist:

npm run capture:flo
npm run analyze:flo

npm run capture:flo:crud:headless -- --playlist "playlist-sync-smoke-YYYYMMDD" --title "Ditto" --artist "NewJeans"

npm run auth:genie:login
npm run auth:genie:check
npm run capture:genie:manual
npm run capture:genie:crud:headless -- --title "Ditto" --artist "NewJeans"

npm run capture:genie
npm run analyze:genie

For a first login or an expired session, run npm run capture:flo:manual and log in to FLO manually. After that, npm run capture:flo:crud:headless reuses profiles/flo and runs the FLO search/add/verify capture without opening a visible browser window.

Both headless scripts write to your account. If you pass none of --title, --artist, --album, --isrc, --duration, --duration-ms, or --keyword, they search for the default track Ditto by NewJeans and try to add it. See What the headless scripts change in your account.

The capture profiles are separate from your normal Chrome profile. If you are logged in to Genie or FLO in regular Chrome, the Playwright scripts cannot use that login. Reusing an existing Chrome login would need a Chrome Extension or a local browser bridge, because a hosted web page cannot read another site's Chrome cookies.

For services without OAuth, the connection flow opens the first-party login screen and keeps only the resulting browser session:

npm run auth:flo:login
npm run auth:genie:login

After login, run npm run auth:flo:check or npm run auth:genie:check to check the saved session without opening a visible browser. These commands write only safe status signals under state/auth/. They do not write passwords, cookies, or auth headers.

Track lookup is based on metadata, because a track ID from one service cannot be sent to the other. Use --title, --artist, and optionally --album to find the target service's track ID before adding:

npm run capture:flo:crud:headless -- --playlist "playlist-sync-smoke-YYYYMMDD" --title "Hype Boy" --artist "NewJeans"
What the headless scripts change in your account

The headless scripts write to your account:

  • capture:flo:crud:headless adds the matched track to the playlist you name

with --playlist "<name>". If you leave that flag out, or no playlist has exactly that name, it falls back to the first playlist in your FLO library without asking. It never creates a playlist with the --playlist name, so create the temporary playlist in FLO before the run. Only when your library has no playlist at all does it create one; pass --no-create-playlist to prevent this. It then opens the playlist to verify that the track was added.

  • capture:genie:crud:headless clicks Genie's add-to-my-album button for the

matched song, but only when the saved profile is logged in. It has no --playlist option. If the profile is not logged in, it captures the search metadata and stops with reauth_required.

  • Without track flags, both scripts use the default track Ditto by

NewJeans.

Manual capture

During manual capture runs, a Chromium window opens. Log in manually, then perform these playlist actions on a temporary playlist:

  • list/open playlists
  • create playlist
  • rename playlist
  • search a song
  • add a song
  • remove a song
  • delete the temporary playlist

Press Enter in the terminal to stop capture. Artifacts are written under captures/<service>/<timestamp>/.

Current Architecture

  • src/capture/browser-capture.mjs records XHR/fetch traffic from a headed

or headless Playwright browser session.

  • scripts/service-login.mjs opens the first-party login page in the saved

profile and writes only session status signals to state/auth/<service>.json.

  • scripts/flo-headless-crud-capture.mjs reuses the saved FLO profile and

runs a headless title/artist search/add/verify capture.

  • scripts/genie-headless-crud-capture.mjs reuses the saved Genie profile,

parses title/artist search results from Genie's HTML, and attempts an add only after the profile is logged in.

  • src/sync/track-match.mjs ranks the target service's search candidates, so

that a sync write can map a source track to a target track ID before adding.

  • src/analyze/classify-playlist-apis.mjs scores captured requests and writes

playlist API candidates.

  • src/sync/diff.mjs computes a two-way union sync plan from normalized

playlist state files.

  • src/adapters/* are placeholders for live adapters, for use once the

captured endpoints are mapped.

  • docs/PRD.md describes the target web settings UI, the authentication

model, and the end-state user stories.

  • docs/SERVICE_COVERAGE_RESEARCH.md covers market priority, GitHub findings,

and capture feasibility for each service.

  • docs/ADAPTER_ARCHITECTURE.md describes the common service adapter and the

capability-gated execution model.

  • docs/API_STRATEGY_BY_SERVICE.md compares official OAuth with browser-capture

adapters for each service.

  • docs/CAPTURE_CAMPAIGN_PLAN.md describes the capture campaign for each

target service, from signup to playlist CRUD.

  • docs/CAPTURE_RUNBOOK.md gives the step-by-step capture procedure for FLO

and Genie.

  • docs/SESSION_AND_SEARCH_ARCHITECTURE.md covers session keepalive and

recovery, and the requirements for searching tracks on the target service.

  • docs/OFFICIAL_API_CHECK.md records the result of the official API check.

Some docs, including docs/PRD.md, are written in Korean.

Safety Model

  • Captures stay local, and git ignores them.
  • Capture records only XHR/fetch traffic to the hosts listed under

allowedHosts for the service in config/services.json. The --all-hosts flag removes that filter.

  • Masking in network.jsonl is best-effort. Only these request and response

headers are replaced with <redacted>: authorization, cookie, set-cookie, x-csrf-token, x-xsrf-token, x-auth-token, and proxy-authorization. JSON and form-encoded request bodies are masked by key: a key is masked when its name contains authorization, cookie, password, passwd, pwd, token, session, email, phone, tel, name, or birth, or when it is exactly memberId, loginId, userId, user_id, accountId, or account_id (case-insensitive).

  • What is not masked: service-specific auth headers with other names (for

example a custom x-...-access-token), body parameters whose names do not match the list above, and the full request and frame URLs, including query strings. Treat every network.jsonl as sensitive and never share it.

  • The capture:* commands (src/capture/browser-capture.mjs) store response

bodies unmasked, as raw text of up to 250 KB each. To skip them, pass --no-response-body, for example npm run capture:flo -- --no-response-body. The headless scripts mask response bodies by key, but those bodies can still contain private account data.

  • Every capture run writes captures/<service>/<timestamp>/storage-state.json.

This applies to the capture: commands and to both headless scripts. The file holds the full live session cookies and site storage. The capture: commands also accept an optional --har flag, which writes network.har with unredacted headers.

  • profiles/<service> is a logged-in Chromium profile. Anyone who has this

directory can act as you on that service. Deleting it removes the saved login from this machine.

  • Never commit or share captures/, profiles/, or state/.
  • Live sync apply is blocked until the endpoint map for each service has been

added and verified.

GitHub에서 최신 문서 보기 ↗