1use std::{fmt::Debug, marker::PhantomData};
2
3use reqwest::Method;
4use serde::Serialize;
5use serde_json::{json, Value};
6
7use crate::{
8 auth::{AuthFlow, Authorised},
9 client::Body,
10 error::Result,
11 model::{
12 player::{CurrentlyPlayingItem, Device, Devices, PlayHistory, PlaybackState, Queue},
13 CursorPage,
14 },
15 Nil,
16};
17
18use super::{Client, Endpoint};
19
20impl Endpoint for TransferPlaybackEndpoint {}
21impl Endpoint for StartPlaybackEndpoint {}
22impl Endpoint for SeekToPositionEndpoint {}
23impl Endpoint for SetRepeatModeEndpoint {}
24impl Endpoint for SetPlaybackVolumeEndpoint {}
25impl Endpoint for ToggleShuffleEndpoint {}
26impl<T: TimestampMarker> Endpoint for RecentlyPlayedTracksEndpoint<T> {
27 fn endpoint_url(&self) -> &'static str {
28 "/me/player/recently-played"
29 }
30}
31impl Endpoint for AddItemToQueueEndpoint {}
32
33pub async fn get_playback_state(
34 market: Option<&str>,
35 spotify: &Client<impl AuthFlow + Authorised>,
36) -> Result<PlaybackState> {
37 let market = market.map(|m| [("market", m)]);
38 spotify
39 .get::<[(&str, &str); 1], _>("/me/player".to_owned(), market)
40 .await
41}
42
43pub fn transfer_playback(device_id: impl Into<String>) -> TransferPlaybackEndpoint {
44 TransferPlaybackEndpoint {
45 device_ids: vec![device_id.into()],
46 play: None,
47 }
48}
49
50pub async fn get_available_devices(
51 spotify: &Client<impl AuthFlow + Authorised>,
52) -> Result<Vec<Device>> {
53 spotify
54 .get::<(), _>("/me/player/devices".to_owned(), None)
55 .await
56 .map(|d: Devices| d.devices)
57}
58
59pub async fn get_currently_playing_track(
60 market: Option<&str>,
61 spotify: &Client<impl AuthFlow + Authorised>,
62) -> Result<CurrentlyPlayingItem> {
63 let market = market.map(|m| [("market", m)]);
64 spotify
65 .get::<Option<[(&str, &str); 1]>, _>("/me/player/currently-playing".to_owned(), market)
66 .await
67}
68
69pub fn start_playback() -> StartPlaybackEndpoint {
70 StartPlaybackEndpoint::default()
71}
72
73pub async fn pause_playback(
74 device_id: Option<&str>,
75 spotify: &Client<impl AuthFlow + Authorised>,
76) -> Result<Nil> {
77 let device_id = device_id.map(|d| [("device_id", d)]);
78 spotify
79 .request(Method::PUT, "/me/player/pause".to_owned(), device_id, None)
80 .await
81}
82
83pub async fn skip_to_next(
84 device_id: Option<&str>,
85 spotify: &Client<impl AuthFlow + Authorised>,
86) -> Result<Nil> {
87 let device_id = device_id.map(|d| [("device_id", d)]);
88 spotify
89 .request(Method::POST, "/me/player/next".to_owned(), device_id, None)
90 .await
91}
92
93pub async fn skip_to_previous(
94 device_id: Option<&str>,
95 spotify: &Client<impl AuthFlow + Authorised>,
96) -> Result<Nil> {
97 let device_id = device_id.map(|d| [("device_id", d)]);
98 spotify
99 .request(
100 Method::POST,
101 "/me/player/previous".to_owned(),
102 device_id,
103 None,
104 )
105 .await
106}
107
108pub fn seek_to_position(position: u32) -> SeekToPositionEndpoint {
109 SeekToPositionEndpoint {
110 position_ms: position,
111 device_id: None,
112 }
113}
114
115pub fn set_repeat_mode(repeat_mode: RepeatMode) -> SetRepeatModeEndpoint {
116 SetRepeatModeEndpoint {
117 state: repeat_mode,
118 device_id: None,
119 }
120}
121
122pub fn set_playback_volume(volume: u32) -> SetPlaybackVolumeEndpoint {
123 SetPlaybackVolumeEndpoint {
124 volume_percent: volume,
125 device_id: None,
126 }
127}
128
129pub fn toggle_playback_shuffle(shuffle: bool) -> ToggleShuffleEndpoint {
130 ToggleShuffleEndpoint {
131 state: shuffle,
132 device_id: None,
133 }
134}
135
136pub fn recently_played_tracks() -> RecentlyPlayedTracksEndpoint {
137 RecentlyPlayedTracksEndpoint::default()
138}
139
140pub async fn get_user_queue(spotify: &Client<impl AuthFlow + Authorised>) -> Result<Queue> {
141 spotify
142 .get::<(), _>("/me/player/queue".to_owned(), None)
143 .await
144}
145
146pub fn add_item_to_queue(uri: impl Into<String>) -> AddItemToQueueEndpoint {
147 AddItemToQueueEndpoint {
148 uri: uri.into(),
149 device_id: None,
150 }
151}
152
153#[derive(Clone, Copy, Debug, Default, Serialize)]
154#[serde(rename_all = "snake_case")]
155pub enum RepeatMode {
156 Track,
157 Context,
158 #[default]
159 Off,
160}
161
162mod private {
163 use super::{After, Before, Unspecified};
164
165 pub trait Sealed {}
166
167 impl Sealed for After {}
168 impl Sealed for Before {}
169 impl Sealed for Unspecified {}
170}
171
172pub trait TimestampMarker: private::Sealed + Debug {}
173impl TimestampMarker for Before {}
174impl TimestampMarker for After {}
175impl TimestampMarker for Unspecified {}
176
177#[derive(Clone, Copy, Debug, Default)]
178pub struct After;
179
180#[derive(Clone, Copy, Debug, Default)]
181pub struct Before;
182
183#[derive(Clone, Copy, Debug, Default)]
184pub struct Unspecified;
185
186#[derive(Clone, Debug, Default, Serialize)]
187pub struct TransferPlaybackEndpoint {
188 pub(crate) device_ids: Vec<String>,
189 pub(crate) play: Option<bool>,
190}
191
192impl TransferPlaybackEndpoint {
193 pub fn play(mut self, play: bool) -> Self {
196 self.play = Some(play);
197 self
198 }
199
200 #[doc = include_str!("../docs/send.md")]
201 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
202 spotify.put("/me/player".to_owned(), Body::Json(self)).await
203 }
204}
205
206#[derive(Clone, Debug, Default, Serialize)]
207pub struct StartPlaybackEndpoint {
208 #[serde(skip)]
209 pub(crate) device_id: Option<String>,
210 pub(crate) context_uri: Option<String>,
211 pub(crate) uris: Option<Vec<String>>,
212 pub(crate) offset: Option<Value>,
213 pub(crate) position_ms: Option<u32>,
214}
215
216impl StartPlaybackEndpoint {
217 #[doc = include_str!("../docs/device_id.md")]
218 pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
219 self.device_id = Some(device_id.into());
220 self
221 }
222
223 pub fn context_uri(mut self, context_uri: impl Into<String>) -> Self {
225 self.context_uri = Some(context_uri.into());
226 self
227 }
228
229 pub fn uris(mut self, uris: &[&str]) -> Self {
231 self.uris = Some(uris.iter().map(ToString::to_string).collect());
232 self
233 }
234
235 #[doc = include_str!("../docs/offset.md")]
236 pub fn offset(mut self, offset: u32) -> Self {
237 self.offset = Some(json!({ "position": offset }));
238 self
239 }
240
241 pub fn offset_uri(mut self, uri: &str) -> Self {
244 self.offset = Some(json!({ "uri": uri }));
245 self
246 }
247
248 pub fn position_ms(mut self, position_ms: u32) -> Self {
250 self.position_ms = Some(position_ms);
251 self
252 }
253
254 #[doc = include_str!("../docs/send.md")]
255 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
256 let endpoint = match self.device_id {
257 Some(ref id) => format!("/me/player/play?device_id={id}"),
258 None => "/me/player/play".to_owned(),
259 };
260
261 spotify.put(endpoint, Body::Json(self)).await
262 }
263}
264
265#[derive(Clone, Debug, Default, Serialize)]
266pub struct SeekToPositionEndpoint {
267 pub(crate) position_ms: u32,
268 pub(crate) device_id: Option<String>,
269}
270
271impl SeekToPositionEndpoint {
272 #[doc = include_str!("../docs/device_id.md")]
273 pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
274 self.device_id = Some(device_id.into());
275 self
276 }
277
278 #[doc = include_str!("../docs/send.md")]
279 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
280 spotify
281 .request(Method::PUT, "/me/player/seek".to_owned(), self.into(), None)
282 .await
283 }
284}
285
286#[derive(Clone, Debug, Default, Serialize)]
287pub struct SetRepeatModeEndpoint {
288 pub(crate) state: RepeatMode,
289 pub(crate) device_id: Option<String>,
290}
291
292impl SetRepeatModeEndpoint {
293 #[doc = include_str!("../docs/device_id.md")]
294 pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
295 self.device_id = Some(device_id.into());
296 self
297 }
298
299 #[doc = include_str!("../docs/send.md")]
300 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
301 spotify
302 .request(
303 Method::PUT,
304 "/me/player/repeat".to_owned(),
305 self.into(),
306 None,
307 )
308 .await
309 }
310}
311
312#[derive(Clone, Debug, Default, Serialize)]
313pub struct SetPlaybackVolumeEndpoint {
314 pub(crate) volume_percent: u32,
315 pub(crate) device_id: Option<String>,
316}
317
318impl SetPlaybackVolumeEndpoint {
319 #[doc = include_str!("../docs/device_id.md")]
320 pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
321 self.device_id = Some(device_id.into());
322 self
323 }
324
325 #[doc = include_str!("../docs/send.md")]
326 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
327 spotify
328 .request(
329 Method::PUT,
330 "/me/player/volume".to_owned(),
331 self.into(),
332 None,
333 )
334 .await
335 }
336}
337
338#[derive(Clone, Debug, Default, Serialize)]
339pub struct ToggleShuffleEndpoint {
340 pub(crate) state: bool,
341 pub(crate) device_id: Option<String>,
342}
343
344impl ToggleShuffleEndpoint {
345 #[doc = include_str!("../docs/device_id.md")]
346 pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
347 self.device_id = Some(device_id.into());
348 self
349 }
350
351 #[doc = include_str!("../docs/send.md")]
352 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
353 spotify
354 .request(
355 Method::PUT,
356 "/me/player/shuffle".to_owned(),
357 self.into(),
358 None,
359 )
360 .await
361 }
362}
363
364#[derive(Clone, Debug, Default, Serialize)]
365pub struct RecentlyPlayedTracksEndpoint<T: TimestampMarker = Unspecified> {
366 pub(crate) limit: Option<u32>,
367 pub(crate) after: Option<u64>,
368 pub(crate) before: Option<u64>,
369 marker: PhantomData<T>,
370}
371
372impl RecentlyPlayedTracksEndpoint<Unspecified> {
373 pub fn after(self, after: u64) -> RecentlyPlayedTracksEndpoint<After> {
375 RecentlyPlayedTracksEndpoint {
376 limit: self.limit,
377 after: Some(after),
378 before: self.before,
379 marker: PhantomData,
380 }
381 }
382
383 pub fn before(self, before: u64) -> RecentlyPlayedTracksEndpoint<Before> {
385 RecentlyPlayedTracksEndpoint {
386 limit: self.limit,
387 after: self.after,
388 before: Some(before),
389 marker: PhantomData,
390 }
391 }
392}
393
394impl<T: TimestampMarker + Default> RecentlyPlayedTracksEndpoint<T> {
395 #[doc = include_str!("../docs/limit.md")]
396 pub fn limit(mut self, limit: u32) -> Self {
397 self.limit = Some(limit);
398 self
399 }
400
401 #[doc = include_str!("../docs/send.md")]
402 pub async fn get(
403 self,
404 spotify: &Client<impl AuthFlow + Authorised>,
405 ) -> Result<CursorPage<PlayHistory, Self>> {
406 spotify
407 .get("/me/player/recently-played".to_owned(), self)
408 .await
409 }
410}
411
412#[derive(Clone, Debug, Default, Serialize)]
413pub struct AddItemToQueueEndpoint {
414 pub(crate) uri: String,
415 pub(crate) device_id: Option<String>,
416}
417
418impl AddItemToQueueEndpoint {
419 #[doc = include_str!("../docs/device_id.md")]
420 pub fn device_id(mut self, device_id: impl Into<String>) -> Self {
421 self.device_id = Some(device_id.into());
422 self
423 }
424
425 #[doc = include_str!("../docs/send.md")]
426 pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
427 spotify
428 .request(
429 Method::POST,
430 "/me/player/queue".to_owned(),
431 self.into(),
432 None,
433 )
434 .await
435 }
436}