YT Music-koppling i settings: Googles device-flöde inbyggt
All checks were successful
build-and-push / build (push) Successful in 16s

- POST /api/ytmusic/start + /poll: kör device-flödet mot Google och
  skriver ytmusicapi-kompatibel oauth.json till Youtubarrs datamapp
  (mounten byts till rw i stacken)
- Settings-sektion: Client ID/secret, 'Spara & koppla' visar kod + länk
  och pollar tills godkänt; 'Testa kopplingen' verifierar med en
  token-förnyelse mot Google
- 4 nya tester, totalt 21

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01824ZrvG2mDYYrmwqypLNup
This commit is contained in:
2026-08-01 01:32:48 +02:00
parent aea7ec3ca7
commit 81a824f005
6 changed files with 337 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ import { mbClient } from './musicbrainz.js'
import { mapLidarrSearch } from './mapping.js'
import { settingsStore } from './settings.js'
import { makeTesters } from './testers.js'
import { ytmusicFlow } from './ytmusic.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -28,6 +29,7 @@ export function buildApp(config, deps = {}) {
const mb = deps.musicbrainz ?? mbClient()
const store = deps.store ?? settingsStore(config.dataDir ?? '/data')
const testers = deps.testers ?? makeTesters({ config, store })
const ytmusic = deps.ytmusic ?? ytmusicFlow({ config, store })
const app = Fastify({ logger: true, trustProxy: true })
app.register(fastifyCookie)
@@ -157,6 +159,11 @@ export function buildApp(config, deps = {}) {
userId: s.jellyfin?.userId ?? '',
userName: s.jellyfin?.userName ?? '',
},
ytmusic: {
clientIdSet: Boolean(s.ytmusic?.clientId),
clientSecretSet: Boolean(s.ytmusic?.clientSecret),
connected: existsSync(ytmusic.oauthPath),
},
}
})
@@ -171,10 +178,36 @@ export function buildApp(config, deps = {}) {
userId: body.jellyfin?.userId,
userName: body.jellyfin?.userName,
},
ytmusic: {
clientId: body.ytmusic?.clientId,
clientSecret: body.ytmusic?.clientSecret,
},
})
return { ok: true }
})
app.post('/api/ytmusic/start', async (req, reply) => {
try {
const out = await ytmusic.start()
if (out.error) return reply.code(400).send(out)
return out
} catch (err) {
req.log.error({ err }, 'device-flödet kunde inte startas')
return reply.code(502).send({ error: 'kunde inte nå Google' })
}
})
app.post('/api/ytmusic/poll', async (req, reply) => {
const deviceCode = req.body?.deviceCode
if (!deviceCode) return reply.code(400).send({ error: 'deviceCode saknas' })
try {
return await ytmusic.poll(deviceCode)
} catch (err) {
req.log.error({ err }, 'device-poll misslyckades')
return reply.code(502).send({ error: 'kunde inte nå Google' })
}
})
app.post('/api/settings/test', async (req, reply) => {
const service = req.body?.service
if (!testers[service]) return reply.code(400).send({ error: 'okänd tjänst' })