librespot_core/dealer/protocol/
request.rs

1use crate::{
2    deserialize_with::*,
3    protocol::{
4        context::Context,
5        context_player_options::ContextPlayerOptionOverrides,
6        player::{PlayOrigin, ProvidedTrack},
7        transfer_state::TransferState,
8    },
9};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::fmt::{Display, Formatter};
13
14#[derive(Clone, Debug, Deserialize)]
15pub struct Request {
16    pub message_id: u32,
17    // todo: did only send target_alias_id: null so far, maybe we just ignore it, will see
18    // pub target_alias_id: Option<()>,
19    pub sent_by_device_id: String,
20    pub command: Command,
21}
22
23#[derive(Clone, Debug, Deserialize)]
24#[serde(tag = "endpoint", rename_all = "snake_case")]
25pub enum Command {
26    Transfer(TransferCommand),
27    #[serde(deserialize_with = "boxed")]
28    Play(Box<PlayCommand>),
29    Pause(PauseCommand),
30    SeekTo(SeekToCommand),
31    SetShufflingContext(SetValueCommand),
32    SetRepeatingTrack(SetValueCommand),
33    SetRepeatingContext(SetValueCommand),
34    AddToQueue(AddToQueueCommand),
35    SetQueue(SetQueueCommand),
36    SetOptions(SetOptionsCommand),
37    UpdateContext(UpdateContextCommand),
38    SkipNext(SkipNextCommand),
39    // commands that don't send any context (at least not usually...)
40    SkipPrev(GenericCommand),
41    Resume(GenericCommand),
42    // catch unknown commands, so that we can implement them later
43    #[serde(untagged)]
44    Unknown(Value),
45}
46
47impl Display for Command {
48    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        use Command::*;
50
51        write!(
52            f,
53            "endpoint: {}{}",
54            matches!(self, Unknown(_))
55                .then_some("unknown ")
56                .unwrap_or_default(),
57            match self {
58                Transfer(_) => "transfer",
59                Play(_) => "play",
60                Pause(_) => "pause",
61                SeekTo(_) => "seek_to",
62                SetShufflingContext(_) => "set_shuffling_context",
63                SetRepeatingContext(_) => "set_repeating_context",
64                SetRepeatingTrack(_) => "set_repeating_track",
65                AddToQueue(_) => "add_to_queue",
66                SetQueue(_) => "set_queue",
67                SetOptions(_) => "set_options",
68                UpdateContext(_) => "update_context",
69                SkipNext(_) => "skip_next",
70                SkipPrev(_) => "skip_prev",
71                Resume(_) => "resume",
72                Unknown(json) => {
73                    json.as_object()
74                        .and_then(|obj| obj.get("endpoint").map(|v| v.as_str()))
75                        .flatten()
76                        .unwrap_or("???")
77                }
78            }
79        )
80    }
81}
82
83#[derive(Clone, Debug, Deserialize)]
84pub struct TransferCommand {
85    #[serde(default, deserialize_with = "base64_proto")]
86    pub data: Option<TransferState>,
87    pub options: TransferOptions,
88    pub from_device_identifier: String,
89    pub logging_params: LoggingParams,
90}
91
92#[derive(Clone, Debug, Deserialize)]
93pub struct PlayCommand {
94    #[serde(deserialize_with = "json_proto")]
95    pub context: Context,
96    #[serde(deserialize_with = "json_proto")]
97    pub play_origin: PlayOrigin,
98    pub options: PlayOptions,
99    pub logging_params: LoggingParams,
100}
101
102#[derive(Clone, Debug, Deserialize)]
103pub struct PauseCommand {
104    // does send options with it, but seems to be empty, investigate which options are send here
105    pub logging_params: LoggingParams,
106}
107
108#[derive(Clone, Debug, Deserialize)]
109pub struct SeekToCommand {
110    pub value: u32,
111    pub position: u32,
112    pub logging_params: LoggingParams,
113}
114
115#[derive(Clone, Debug, Deserialize)]
116pub struct SkipNextCommand {
117    #[serde(default, deserialize_with = "option_json_proto")]
118    pub track: Option<ProvidedTrack>,
119    pub logging_params: LoggingParams,
120}
121
122#[derive(Clone, Debug, Deserialize)]
123pub struct SetValueCommand {
124    pub value: bool,
125    pub logging_params: LoggingParams,
126}
127
128#[derive(Clone, Debug, Deserialize)]
129pub struct AddToQueueCommand {
130    #[serde(deserialize_with = "json_proto")]
131    pub track: ProvidedTrack,
132    pub logging_params: LoggingParams,
133}
134
135#[derive(Clone, Debug, Deserialize)]
136pub struct SetQueueCommand {
137    #[serde(deserialize_with = "vec_json_proto")]
138    pub next_tracks: Vec<ProvidedTrack>,
139    #[serde(deserialize_with = "vec_json_proto")]
140    pub prev_tracks: Vec<ProvidedTrack>,
141    // this queue revision is actually the last revision, so using it will not update the web ui
142    // might be that internally they use the last revision to create the next revision
143    pub queue_revision: String,
144    pub logging_params: LoggingParams,
145}
146
147#[derive(Clone, Debug, Deserialize)]
148pub struct SetOptionsCommand {
149    pub shuffling_context: Option<bool>,
150    pub repeating_context: Option<bool>,
151    pub repeating_track: Option<bool>,
152    pub options: Option<OptionsOptions>,
153    pub logging_params: LoggingParams,
154}
155
156#[derive(Clone, Debug, Deserialize)]
157pub struct UpdateContextCommand {
158    #[serde(deserialize_with = "json_proto")]
159    pub context: Context,
160    pub session_id: Option<String>,
161}
162
163#[derive(Clone, Debug, Deserialize)]
164pub struct GenericCommand {
165    pub logging_params: LoggingParams,
166}
167
168#[derive(Clone, Debug, Default, Deserialize, Serialize)]
169pub struct TransferOptions {
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub restore_paused: Option<String>,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub restore_position: Option<String>,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub restore_track: Option<String>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub retain_session: Option<String>,
178}
179
180#[derive(Clone, Debug, Deserialize)]
181pub struct PlayOptions {
182    pub skip_to: Option<SkipTo>,
183    #[serde(default, deserialize_with = "option_json_proto")]
184    pub player_options_override: Option<ContextPlayerOptionOverrides>,
185    pub license: Option<String>,
186    // possible to send wie web-api
187    pub seek_to: Option<u32>,
188    // mobile
189    pub always_play_something: Option<bool>,
190    pub audio_stream: Option<String>,
191    pub initially_paused: Option<bool>,
192    pub prefetch_level: Option<String>,
193    pub system_initiated: Option<bool>,
194}
195
196#[derive(Clone, Debug, Deserialize)]
197pub struct OptionsOptions {
198    only_for_local_device: bool,
199    override_restrictions: bool,
200    system_initiated: bool,
201}
202
203#[derive(Clone, Debug, Deserialize, Default)]
204pub struct SkipTo {
205    pub track_uid: Option<String>,
206    pub track_uri: Option<String>,
207    pub track_index: Option<u32>,
208}
209
210#[derive(Clone, Debug, Deserialize)]
211pub struct LoggingParams {
212    pub interaction_ids: Option<Vec<String>>,
213    pub device_identifier: Option<String>,
214    pub command_initiated_time: Option<i64>,
215    pub page_instance_ids: Option<Vec<String>>,
216    pub command_id: Option<String>,
217}