import { writeFileSync } from 'node:fs' import path from 'node:path' const SCOPE = 'https://www.googleapis.com/auth/youtube' const FORM = { 'Content-Type': 'application/x-www-form-urlencoded' } // Googles device-flöde för YT Music (ytmusicapi-kompatibel oauth.json). // Klienten (settings-sidan) visar kod + länk och pollar tills godkänt. export function ytmusicFlow({ config, store, fetchImpl = fetch }) { const dataDir = config.youtubarr?.dataDir ?? '/youtubarr-data' function creds() { const s = store.load() return { clientId: s.ytmusic?.clientId ?? '', clientSecret: s.ytmusic?.clientSecret ?? '' } } return { oauthPath: path.join(dataDir, 'oauth.json'), async start() { const { clientId } = creds() if (!clientId) return { error: 'spara Client ID först' } const res = await fetchImpl('https://oauth2.googleapis.com/device/code', { method: 'POST', headers: FORM, body: new URLSearchParams({ client_id: clientId, scope: SCOPE }).toString(), }) const js = await res.json() if (!res.ok) return { error: js.error_description ?? js.error ?? 'okänt fel från Google' } return { deviceCode: js.device_code, userCode: js.user_code, url: js.verification_url ?? 'https://www.google.com/device', interval: js.interval ?? 5, expiresIn: js.expires_in, } }, async poll(deviceCode) { const { clientId, clientSecret } = creds() if (!clientId || !clientSecret) return { status: 'error', message: 'client id/secret saknas' } const res = await fetchImpl('https://oauth2.googleapis.com/token', { method: 'POST', headers: FORM, body: new URLSearchParams({ client_id: clientId, client_secret: clientSecret, device_code: deviceCode, grant_type: 'urn:ietf:params:oauth:grant-type:device_code', }).toString(), }) const js = await res.json() if (js.error === 'authorization_pending' || js.error === 'slow_down') return { status: 'pending' } if (js.error) return { status: 'error', message: js.error_description ?? js.error } const token = { scope: js.scope ?? SCOPE, token_type: js.token_type ?? 'Bearer', access_token: js.access_token, refresh_token: js.refresh_token, expires_in: js.expires_in, expires_at: Math.floor(Date.now() / 1000) + (js.expires_in ?? 0), } writeFileSync(this.oauthPath, JSON.stringify(token, null, 2)) return { status: 'klar' } }, } }