{"record":{"id":"1d0bb70244a79cfe","repo":"tonhowtf/omniget","slug":"track-sem-soundcloud-id","errorCode":null,"errorMessage":"Track sem soundcloud_id","messagePattern":"Track sem soundcloud_id","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/study-music/soundcloud-store.svelte.ts","lineNumber":442,"sourceCode":"    }\n    await pluginInvoke(\"study\", \"study:soundcloud:like:add\", { trackId });\n    return true;\n  }\n\n  async playTrack(track: ScTrack, queue?: ScTrack[]): Promise<void> {\n    const local = scTrackToMusicTrack(track);\n    const localQueue = (queue && queue.length > 0 ? queue : [track]).map(scTrackToMusicTrack);\n    await musicPlayer.play(local, localQueue);\n  }\n}\n\nexport const soundcloudStore = new SoundCloudStore();\n\nasync function resolveSoundcloudStream(\n  track: MusicTrack,\n): Promise<{ url: string; is_hls: boolean }> {\n  const scId = (track as any).soundcloud_id as number | undefined;\n  if (!scId) throw new Error(\"Track sem soundcloud_id\");\n  const res = await pluginInvoke<{ url: string; is_hls: boolean }>(\n    \"study\",\n    \"study:soundcloud:stream:resolve\",\n    { trackId: scId, quality: \"progressive\" },\n  );\n  if (!res.url) throw new Error(\"SoundCloud nao retornou URL\");\n  return res;\n}\n\nif (typeof window !== \"undefined\") {\n  (musicPlayer as any).soundcloudResolver = resolveSoundcloudStream;\n}\n","sourceCodeStart":424,"sourceCodeEnd":455,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src/lib/study-music/soundcloud-store.svelte.ts#L424-L455","documentation":"Raised by fetch_access_token when the token GQL response is valid JSON but does not contain /data/clip/playbackAccessToken on the first array element. The response is expected to be an array of query results; without the playbackAccessToken object no download URLs can be signed.","triggerScenarios":"json.as_array() is not an array, first() is missing (empty array), or the first element lacks data.clip.playbackAccessToken — combined via the and_then chain into ok_or_else.","commonSituations":"Twitch changing the GQL response envelope (no longer an array); token request rejected silently for restricted/geo-blocked clips; schema change renaming playbackAccessToken; anonymous token requests denied for some clips.","solutions":["Log the full token response JSON and compare against the expected {data:{clip:{playbackAccessToken:{signature,value}}}} shape; update pointers if it changed.","Check whether the clip requires authentication (sub-only/age-gated) and add OAuth if so.","Handle the empty-array case explicitly to distinguish 'no response' from 'wrong shape'.","Test the same GQL query manually (curl with client-id) to confirm it still works."],"exampleFix":"// before\nlet token_obj = json\n    .as_array()\n    .and_then(|arr| arr.first())\n    .and_then(|r| r.pointer(\"/data/clip/playbackAccessToken\"))\n    .ok_or_else(|| anyhow!(\"Access token not available for clip: {}\", slug))?;\n// after\nlet token_obj = json.as_array()\n    .and_then(|arr| arr.first())\n    .and_then(|r| r.pointer(\"/data/clip/playbackAccessToken\"));\nlet token_obj = match token_obj {\n    Some(t) if !t.is_null() => t,\n    _ => anyhow::bail!(\"No playbackAccessToken for {} — response: {}\", slug, json),\n};","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn extract_token(json: &serde_json::Value) -> Option<&serde_json::Value> {\n    json.as_array()?\n        .first()?\n        .pointer(\"/data/clip/playbackAccessToken\")\n        .filter(|t| t.get(\"signature\").is_some() && t.get(\"value\").is_some())\n}","tryCatchPattern":"// Narrow the response before using it\nmatch extract_token(&json) {\n    Some(token) => build_url(token),\n    None => bail!(\"unexpected token response: {}\", json),\n}","preventionTips":["Log the full token response whenever shape assumptions break","Add a contract test on the GQL token query shape","Handle the empty-array response case explicitly","Check clip auth requirements (sub-only/age-gated clips may omit tokens)"],"tags":["twitch","token","api-response","rust"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}