spotify_cli/spotify/
devices.rs1use 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#[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}