MyGo 云音乐
Go와 MyGo로 만든 서드파티 네이버 클라우드 뮤직 데스크톱 클라이언트
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Social DJ rooms on Spotify. Friends gather in a tiny pixel nightclub, take turns DJing from their Spotify playlists, and vote each track Hype or Skip. Spinroom never streams audio: every listener plays the same track at the same position on their own Spotify Premium account, in a browser speaker tab. Spinroom owns the room, the rotation, the votes and the sync.
The same room is reachable from three surfaces on one API:README 전문 보기 ↗Social DJ rooms on Spotify. Friends gather in a tiny pixel nightclub, take turns DJing from their Spotify playlists, and vote each track Hype or Skip. Spinroom never streams audio: every listener plays the same track at the same position on their own Spotify Premium account, in a browser speaker tab. Spinroom owns the room, the rotation, the votes and the sync.
The same room is reachable from three surfaces on one API:
The product spec is docs/PRD.md, the build plan is docs/BUILD_PLAN.md, and the scene design system is docs/design/pixel-neon-dj.md.
┌──────────── one public origin (web + /v1) ─────────────┐
browser ──▶│ apps/web (Vite/React SPA, speaker tab) │
│ │ /v1 REST + WebSocket /v1/rooms/{slug}/live │
│ ▼ │
│ apps/api (Fastify) ── room runtime ── packages/room-engine (pure rules)
│ │ │ │
│ Postgres Redis (state, single-writer lock, pub/sub events)
└──────────────────────────┬──────────────────────────────┘
│ events:room:* + token exchange
apps/mcp (MCP over HTTP+OAuth / stdio) ┘└ apps/slack (Bolt, HTTP mode)
| Path | What it is |
|---|---|
packages/contracts | Zod schemas for every REST route, WebSocket event, error code and room setting. openapi.json is generated from them. |
packages/room-engine | Pure TypeScript rules: rotation, voting, auto-skip, bounce, presence, pause. It has no I/O and is fully unit-tested, including a 50-bot simulation. |
packages/sdk | Typed REST client, live-room socket (seq-gap resync, 1–30 s backoff), server clock and drift controller. Used by web, MCP and Slack. |
packages/db | Drizzle schema, Postgres client and AES-GCM sealer shared by the API and Slack services. |
apps/api | Auth (Spotify PKCE with your own Client ID), rooms, sets, votes, chat, moderation, speakers, avatars, the OAuth 2.1 server for MCP, and the realtime socket. |
apps/web | Lobby, room (stage, player panel, rail), speaker, avatar studio, settings, Integrations, OAuth consent. |
apps/mcp | spinroom-mcp: 17 tools, a now-playing resource with subscriptions, and the spinroom_session prompt. |
apps/slack | Install, account linking, /spinroom commands, live card, buttons, modal. |
apps/server | The API, MCP server and Slack app in one process on one port, for a single free instance (see Deploying). |
tools/art | All built-in pixel art, generated in code (pnpm art). |
Key guarantees:
startedAtServerMs. Speakers play at now + offset − startedAtServerMs, using the median of the last 5 clock samples. They check drift every 5 s, seek when it exceeds 500 ms, and reload after two checks over 3 s. The server owns track transitions: a timer fires at the end plus a 2 s grace, with a tick safety net behind it./v1 endpoints. A contract test fails if any registry route is missing, and responses are validated against their schemas outside production.Prerequisites: Node 22, pnpm 10, Postgres 16 and Redis 7 (docker compose up -d starts them, or use local installs).
pnpm install
cp .env.example .env # defaults run in SPOTIFY_MODE=fake
pnpm db:migrate && pnpm seed # 3 fake users, 2 rooms
pnpm --filter @spinroom/api dev # API on :8080
pnpm --filter @spinroom/web dev # web on http://127.0.0.1:5173 (proxies /v1 to the API)
Open http://127.0.0.1:5173, click Sign in with Spotify → Continue to test sign-in, and pick alice. Open a second browser profile as bob. In the room, click Start speaker, add tracks under My set, and press Q to step up to the booth. Fake mode uses a fictional catalog and a silent simulated player, so sync, votes and rotation all work without audio.
Optional services:
pnpm --filter spinroom-mcp dev # remote MCP on :8090/mcp
pnpm --filter @spinroom/slack dev # Slack app on :8070
Spotify caps development-mode apps at 5 users, and extended quota requires 250,000 MAU. So Spinroom is built for option B: each user creates a free Spotify developer app and pastes its Client ID. Login is PKCE, so no client secret is ever stored.
SPOTIFY_MODE=real and PUBLIC_ORIGIN to your origin. Locally, use http://127.0.0.1:5173: Spotify requires loopback redirect URIs to use 127.0.0.1, not localhost.<origin>/v1/auth/spotify/callback);SPOTIFY_DEV_CLIENT_ID to use one shared app when a user has none.The Spotify client targets the 2026 development-mode endpoints, falling back to the legacy paths if those fail:
POST /me/playlists;/playlists/{id}/items;429 QUOTA_EXCEEDED mapped to quota_exceeded for that user only.If playlist writes are refused (403), that member's set falls back to a Spinroom-side list, and they are told why.
Spotify branding: the player panel shows Spotify's metadata and art unaltered, with attribution and Open in Spotify links. Replace the placeholder mark in apps/web/src/room/PlayerPanel.tsx (SpotifyMark) with the official logo from Spotify's brand kit before launch.
Every variable is listed with comments in .env.example. The most important:
| Variable | Purpose |
|---|---|
PUBLIC_ORIGIN | Public origin of web + /v1 (also the OAuth issuer and the Spotify redirect base). |
DATABASE_URL, REDIS_URL | Postgres 16 and Redis 7. |
SESSION_SECRET | HMAC key for session JWTs (required in production). |
ENCRYPTION_KEY | 32-byte base64 key sealing Spotify and Slack tokens at rest (required in production). |
SPOTIFY_MODE | real or fake (fake is refused in production unless ALLOW_FAKE_SPOTIFY=1). |
DB_POOL_MAX | Postgres pool size (keep the total under the database's connection limit). |
WEB_ORIGINS | Extra web origins allowed to open the live socket (PUBLIC_ORIGIN always is). |
VITE_LIVE_ORIGIN (web build) | Live socket origin when the web app is hosted apart from the API (wss://api.host). |
SERVICE_SECRET_SLACK, SERVICE_SECRET_MCP | Credentials the Slack and MCP services use for token exchange. |
MCP_RESOURCE_URL | Public URL of the MCP endpoint (the audience of MCP tokens). |
STORAGE_DRIVER, S3_*, ASSET_BASE_URL | Avatar storage: filesystem in dev, any S3-compatible store behind a CDN in production. |
AVATAR_SAFETY | manual review queue (default) or auto_approve (development only). |
ADMIN_SPOTIFY_IDS | Spotify user IDs who can review avatars at /admin. |
SLACK_SIGNING_SECRET, SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_STATE_SECRET | Slack app credentials. |
The browser tab is the speaker; the agent is the remote. Agents never see Spotify tokens. Profile → Integrations shows copy-ready snippets and a one-time link code.
Remote server (OAuth 2.1, recommended). Deploy apps/mcp at https://mcp.<domain>/mcp. Clients discover the authorization server through /.well-known/oauth-protected-resource, register dynamically, and sign in with Spotify in a browser window. Tokens are audience-bound and can be revoked from the Profile page.
| Client | Setup |
|---|---|
| Claude Code | claude mcp add --transport http spinroom https://mcp.<domain>/mcp |
| Claude Desktop / claude.ai | Settings → Connectors → Add custom connector → paste the URL |
Codex (~/.codex/config.toml) | [mcp_servers.spinroom] with url = "https://mcp.<domain>/mcp" |
Cursor (.cursor/mcp.json) | { "mcpServers": { "spinroom": { "url": "https://mcp.<domain>/mcp" } } } |
Local stdio (personal token). For clients without remote MCP or OAuth support, including Grok and older clients:
SPINROOM_URL=https://<domain> npx -y spinroom-mcp login ABCD-1234 # code from Profile → Integrations → Local server
{ "mcpServers": { "spinroom": { "command": "npx", "args": ["-y", "spinroom-mcp"], "env": { "SPINROOM_URL": "https://<domain>" } } } }
Tools: list_rooms, join_room, leave_room, now_playing, vote, search_tracks, crate_add, crate_list, crate_remove, crate_move, dj_queue_join, dj_queue_leave, skip_my_spin, create_room, invite, chat_send, room_history.
spinroom://room/{slug}/now-playing (subscribable).spinroom_session.speaker_status, plus a speaker link when no speaker is live.apps/slack/manifest.yml after replacing spinroom.example.com with your origin. Then set the SLACK_* variables and run apps/slack.<origin>/v1/integrations/slack/install./spinroom link <room>. A now-playing card appears and is edited in place, at most once every 3 s per channel. It is reposted after 50 newer messages.now, hype, skip, add <search>, dj, undj, invite @user, button [room] (posts a standalone Join room button), speaker, unlink, help./spinroom moments off|on (owners and moderators)./spinroom recap posts it now; /spinroom recap off|on turns the Friday post off or on.The manifest adds channels:history and groups:history to the PRD's scopes. Spinroom only uses them to count newer messages so it can repost the card. Before promoting a workplace "office radio", check Spotify's personal, non-commercial use terms.
pnpm lint && pnpm typecheck
pnpm test # unit + integration + contract tests (needs Postgres + Redis)
pnpm e2e # Playwright journeys (starts its own API + web on :8081/:5174, fake Spotify)
pnpm size # initial JS budget, after `pnpm --filter @spinroom/web build`
pnpm art # rebuild the pixel art (fails if over the 400 KB budget)
Test databases: spinroom_test, spinroom_test_mcp, spinroom_test_slack and spinroom_e2e. scripts/init-test-db.sql creates the first one for Docker Compose; create the others the same way. CI (.github/workflows/ci.yml) runs everything above on every push.
What the automated checks cover, mapped to the PRD's acceptance checks:
| Phase | Check | Status |
|---|---|---|
| 1 | Login and profile; Free accounts refused at sign-in; named setup failures | Automated (API + Playwright) |
| 2 | Every FR-D / FR-V rule, including ties, DJ leaving mid-spin, empty booth and cooldowns; 50 bots × 100 spins | Automated (34 engine tests) |
| 3 | Late joiner starts at the live position; background tab keeps the speaker live; drift logic | Automated (fake player) |
| 4 | Journeys 1 and 2; bundle budget (117.6 KB of 300 KB); reduced motion; avatar import plus each invalid case | Automated |
| 5 | All tools; OAuth discovery, registration, PKCE, refresh and revocation; stdio login | Automated (in-memory and HTTP clients) |
| 6 | Card posting and updates; Slack vote reaches the web in < 1 s; ≤ 1 edit per 3 s under 20 votes in 10 s | Automated (fake Slack API) |
These still need real accounts or people:
Each service deploys separately (Dockerfile targets api, mcp, slack, web); the default target server holds the Node services and picks one by command, including node server/dist/main.js, which runs all three in one process. The layouts below all work.
Route one public origin as follows; deploy/nginx.conf is an example:
| Path | Service |
|---|---|
/v1/* (including the WebSocket /v1/rooms/{slug}/live) | api |
/oauth/(authorize|token|register|revoke), /.well-known/oauth-authorization-server | api |
/v1/integrations/slack/* | slack |
| everything else | web (SPA) |
mcp.<domain>/mcp, mcp.<domain>/.well-known/oauth-protected-resource | mcp |
Everything runs on free plans. The static web app is on Vercel; one free Render web service runs the API, the remote MCP server and the Slack app in one process (apps/server), with a free Render Key Value for Redis; Postgres and avatar storage are on Supabase's free plan. Vercel rewrites /v1/* (Slack included) and the OAuth paths to Render (apps/web/vercel.json), so REST calls, cookies, CSRF and the Spotify callback stay on the web origin. Rewrites can't carry WebSocket upgrades, so the room's live socket connects straight to Render (VITE_LIVE_ORIGIN) with a one-time ticket from POST /v1/rooms/{slug}/live-ticket in place of the cookie.
Supabase. Create a free project. Copy the Session pooler connection string (Connect → Session pooler; it works over IPv4) and append ?sslmode=require. In Storage, create a private bucket avatars, enable the S3 connection (Storage → Settings) and create an access key; note the endpoint (https://<ref>.storage.supabase.co/storage/v1/s3) and region. Then, in the SQL editor, close Supabase's auto-generated REST API over Spinroom's tables (the server connects to Postgres directly as their owner and never uses it; by default every new table in public is readable and writable with the public anon key):
alter default privileges for role postgres in schema public revoke all on tables from anon, authenticated;
alter default privileges for role postgres in schema public revoke all on sequences from anon, authenticated;
alter default privileges for role postgres in schema public revoke all on functions from anon, authenticated;
revoke usage on schema public from anon, authenticated;
Render. New → Blueprint → this repository (render.yaml). Enter PUBLIC_ORIGIN (the Vercel URL you'll use, e.g. https://spinroom-web.vercel.app), DATABASE_URL, the S3_* values, and (later) the Slack app credentials. It starts in fake Spotify mode for a smoke test; migrations run on boot. For deploy-on-push, install Render's GitHub app (github.com/apps/render → Configure) on the account or organization that owns the repository and give it access to this repo; without it Render can still build the repo, but only when a deploy is started by hand.
Vercel. New project from this repository, root directory apps/web (framework and commands come from vercel.json). Set VITE_LIVE_ORIGIN=wss://spinroom.onrender.com. If Render gave the service another hostname, update the rewrites in apps/web/vercel.json and MCP_RESOURCE_URL in render.yaml.
Keep it awake. Add a free uptime monitor (UptimeRobot, cron-job.org…) for https://spinroom.onrender.com/healthz?deep=1 every 5–10 minutes. It keeps the Render instance from sleeping (one always-on instance fits the 750 free hours a month) and gives Supabase the daily database activity that stops a free project from pausing.
Smoke test at the Vercel URL: fake sign-in, create a room, join from a second browser, vote; the live socket should update within a second. should report the Vercel URL as issuer. Upload a custom avatar.
Limits of the free layout. It suits a group of friends (a few rooms, dozens of listeners), not a public launch:
Preview deployments get their own *.vercel.app URL; add each one you want to use to WEB_ORIGINS (pages on other origins can't open the live socket).
deploy/render-scaled.yaml runs the API, MCP and Slack as separate paid, always-on services with Render Postgres and Key Value (New → Blueprint, file path deploy/render-scaled.yaml). Point the Vercel rewrites at spinroom-api (and /v1/integrations/slack/* at spinroom-slack, listed before /v1/*), set VITE_LIVE_ORIGIN=wss://spinroom-api.onrender.com, and use https://spinroom-mcp.onrender.com/mcp for MCP clients.
MIGRATE_ON_BOOT=0 disables this).GET /.well-known/oauth-authorization-serverGo live. In your Spotify app, add <PUBLIC_ORIGIN>/v1/auth/spotify/callback as a redirect URI; then on Render set SPOTIFY_MODE=real and delete ALLOW_FAKE_SPOTIFY. Point the Slack app's request URLs at <PUBLIC_ORIGIN>/v1/integrations/slack/{events,interactivity,commands,options} and add https://spinroom.onrender.com/mcp to your MCP clients.