spotify_cli/spotify/
devices.rs

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