openlibspot_core/
apresolve.rs1use std::collections::VecDeque;
2
3use hyper::{Body, Method, Request};
4use serde::Deserialize;
5
6use crate::Error;
7
8pub type SocketAddress = (String, u16);
9
10#[derive(Default)]
11pub struct AccessPoints {
12 accesspoint: VecDeque<SocketAddress>,
13 dealer: VecDeque<SocketAddress>,
14 spclient: VecDeque<SocketAddress>,
15}
16
17#[derive(Deserialize, Default)]
18pub struct ApResolveData {
19 accesspoint: Vec<String>,
20 dealer: Vec<String>,
21 spclient: Vec<String>,
22}
23
24impl ApResolveData {
25 fn fallback() -> Self {
29 Self {
30 accesspoint: vec![String::from("ap.spotify.com:443")],
31 dealer: vec![String::from("dealer.spotify.com:443")],
32 spclient: vec![String::from("spclient.wg.spotify.com:443")],
33 }
34 }
35}
36
37impl AccessPoints {
38 fn is_any_empty(&self) -> bool {
39 self.accesspoint.is_empty() || self.dealer.is_empty() || self.spclient.is_empty()
40 }
41}
42
43component! {
44 ApResolver : ApResolverInner {
45 data: AccessPoints = AccessPoints::default(),
46 }
47}
48
49impl ApResolver {
50 pub fn port_config(&self) -> Option<u16> {
53 if self.session().config().proxy.is_some() || self.session().config().ap_port.is_some() {
54 Some(self.session().config().ap_port.unwrap_or(443))
55 } else {
56 None
57 }
58 }
59
60 fn process_ap_strings(&self, data: Vec<String>) -> VecDeque<SocketAddress> {
61 let filter_port = self.port_config();
62 data.into_iter()
63 .filter_map(|ap| {
64 let mut split = ap.rsplitn(2, ':');
65 let port = split.next()?;
66 let port: u16 = port.parse().ok()?;
67 let host = split.next()?.to_owned();
68 match filter_port {
69 Some(filter_port) if filter_port != port => None,
70 _ => Some((host, port)),
71 }
72 })
73 .collect()
74 }
75
76 fn parse_resolve_to_access_points(&self, resolve: ApResolveData) -> AccessPoints {
77 AccessPoints {
78 accesspoint: self.process_ap_strings(resolve.accesspoint),
79 dealer: self.process_ap_strings(resolve.dealer),
80 spclient: self.process_ap_strings(resolve.spclient),
81 }
82 }
83
84 pub async fn try_apresolve(&self) -> Result<ApResolveData, Error> {
85 let req = Request::builder()
86 .method(Method::GET)
87 .uri("https://apresolve.spotify.com/?type=accesspoint&type=dealer&type=spclient")
88 .body(Body::empty())?;
89
90 let body = self.session().http_client().request_body(req).await?;
91 let data: ApResolveData = serde_json::from_slice(body.as_ref())?;
92
93 Ok(data)
94 }
95
96 async fn apresolve(&self) {
97 let result = self.try_apresolve().await;
98
99 self.lock(|inner| {
100 let (data, error) = match result {
101 Ok(data) => (data, None),
102 Err(e) => (ApResolveData::default(), Some(e)),
103 };
104
105 inner.data = self.parse_resolve_to_access_points(data);
106
107 if inner.data.is_any_empty() {
108 warn!("Failed to resolve all access points, using fallbacks");
109 if let Some(error) = error {
110 warn!("Resolve access points error: {}", error);
111 }
112
113 let fallback = self.parse_resolve_to_access_points(ApResolveData::fallback());
114 inner.data.accesspoint.extend(fallback.accesspoint);
115 inner.data.dealer.extend(fallback.dealer);
116 inner.data.spclient.extend(fallback.spclient);
117 }
118 })
119 }
120
121 fn is_any_empty(&self) -> bool {
122 self.lock(|inner| inner.data.is_any_empty())
123 }
124
125 pub async fn resolve(&self, endpoint: &str) -> Result<SocketAddress, Error> {
126 if self.is_any_empty() {
127 self.apresolve().await;
128 }
129
130 self.lock(|inner| {
131 let access_point = match endpoint {
132 "accesspoint" => inner.data.accesspoint.pop_front(),
136 "dealer" => inner.data.dealer.pop_front(),
137 "spclient" => inner.data.spclient.pop_front(),
138 _ => {
139 return Err(Error::unimplemented(format!(
140 "No implementation to resolve access point {endpoint}"
141 )))
142 }
143 };
144
145 let access_point = access_point.ok_or_else(|| {
146 Error::unavailable(format!("No access point available for endpoint {endpoint}"))
147 })?;
148
149 Ok(access_point)
150 })
151 }
152}