spotify_cli/spotify/
devices.rs

1use anyhow::bail;
2use reqwest::blocking::Client as HttpClient;
3use serde_json::json;
4use serde::Deserialize;
5
6use crate::domain::device::Device;
7use crate::error::Result;
8use crate::spotify::auth::AuthService;
9use crate::spotify::base::api_base;
10use crate::spotify::error::format_api_error;
11
12
13/// Spotify devices API client.
14#[derive(Debug, Clone)]
15pub struct DevicesClient {
16    http: HttpClient,
17    auth: AuthService,
18}
19
20impl DevicesClient {
21    pub fn new(http: HttpClient, auth: AuthService) -> Self {
22        Self { http, auth }
23    }
24
25    pub fn list(&self) -> Result<Vec<Device>> {
26        let token = self.auth.token()?;
27        let url = format!("{}/me/player/devices", api_base());
28
29        let response = self
30            .http
31            .get(url)
32            .bearer_auth(token.access_token)
33            .send()?;
34
35        if !response.status().is_success() {
36            let status = response.status();
37            let body = response.text().unwrap_or_else(|_| "<no body>".to_string());
38            bail!(format_api_error("spotify devices request failed", status, &body));
39        }
40
41        let payload: DevicesResponse = response.json()?;
42        Ok(payload
43            .devices
44            .into_iter()
45            .map(|device| Device {
46                id: device.id,
47                name: device.name,
48                volume_percent: device.volume_percent,
49            })
50            .collect())
51    }
52
53    pub fn set_active(&self, device_id: &str) -> Result<()> {
54        let token = self.auth.token()?;
55        let url = format!("{}/me/player", api_base());
56        let body = json!({ "device_ids": [device_id], "play": true });
57
58        let response = self
59            .http
60            .put(url)
61            .bearer_auth(token.access_token)
62            .json(&body)
63            .send()?;
64
65        if !response.status().is_success() {
66            let status = response.status();
67            let body = response.text().unwrap_or_else(|_| "<no body>".to_string());
68            bail!(format_api_error("spotify device transfer failed", status, &body));
69        }
70        Ok(())
71    }
72}
73
74#[derive(Debug, Deserialize)]
75struct DevicesResponse {
76    devices: Vec<SpotifyDevice>,
77}
78
79#[derive(Debug, Deserialize)]
80struct SpotifyDevice {
81    id: String,
82    name: String,
83    volume_percent: Option<u32>,
84}