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