Skip to content

Commit

Permalink
Add requested songs to the Spotify Queue instead of the internal one (f…
Browse files Browse the repository at this point in the history
…ixes #64)
  • Loading branch information
udoprog committed May 5, 2020
1 parent 9647e01 commit 59e99a5
Show file tree
Hide file tree
Showing 17 changed files with 768 additions and 435 deletions.
390 changes: 173 additions & 217 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ futures-option = "0.2.0"
futures-cache = "0.9.0"
anyhow = "1.0.28"
thiserror = "1.0.15"
async-injector = "0.5.0"
async-injector = "0.9.1"
async-trait = "0.1.30"
leaky-bucket = "0.7.3"
regex = "1.3.7"
Expand Down
96 changes: 48 additions & 48 deletions bot/src/api/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub struct RequestBuilder {
url: Url,
method: Method,
headers: Vec<(header::HeaderName, String)>,
body: Option<Bytes>,
body: Bytes,
/// Use Bearer header instead of OAuth for access tokens.
use_bearer: bool,
/// Add the client id to the specified header if configured.
Expand All @@ -63,7 +63,7 @@ impl RequestBuilder {
url,
method,
headers: Vec::new(),
body: None,
body: Bytes::new(),
use_bearer: true,
client_id_header: None,
}
Expand Down Expand Up @@ -91,7 +91,7 @@ impl RequestBuilder {

/// Change the body of the request.
pub fn body(mut self, body: impl Into<Bytes>) -> Self {
self.body = Some(body.into());
self.body = body.into();
self
}

Expand Down Expand Up @@ -124,57 +124,57 @@ impl RequestBuilder {
where
T: serde::de::DeserializeOwned,
{
let Response { status, body, .. } = self.execute().await?;
let Response {
method,
url,
status,
body,
..
} = self.execute().await?;

if let Some(output) = m(status, &body)? {
return Ok(output);
}

let body = String::from_utf8_lossy(body.as_ref());

bail!(
"Bad response: {}: {}: {}: {}",
self.method,
self.url,
status,
body
);
bail!("Bad response: {}: {}: {}: {}", method, url, status, body);
}

/// Execute the request.
pub async fn execute(&self) -> Result<Response<'_, Bytes>, Error> {
pub async fn execute(self) -> Result<Response<Bytes>, Error> {
// NB: scope to only lock the token over the request setup.
let req = {
log::trace!("Request: {}: {}", self.method, self.url);
let mut req = self.client.request(self.method.clone(), self.url.clone());

if let Some(body) = self.body.as_ref() {
req = req.body(body.clone());
}

for (key, value) in &self.headers {
req = req.header(key.clone(), value.clone());
}
log::trace!("Request: {}: {}", self.method, self.url);
let mut req = self.client.request(self.method.clone(), self.url.clone());

req = match &self.method {
&Method::GET => req,
&Method::HEAD => req,
_ => req
.header(header::CONTENT_LENGTH, self.body.len())
.body(self.body.clone()),
};

if let Some(token) = self.token.as_ref() {
let token = token.read()?;
let access_token = token.access_token().to_string();
for (key, value) in &self.headers {
req = req.header(key.clone(), value.clone());
}

if self.use_bearer {
req = req.header(header::AUTHORIZATION, format!("Bearer {}", access_token));
} else {
req = req.header(header::AUTHORIZATION, format!("OAuth {}", access_token));
}
if let Some(token) = self.token.as_ref() {
let token = token.read().await?;
let access_token = token.access_token().to_string();

if let Some(client_id_header) = self.client_id_header {
req = req.header(client_id_header, token.client_id())
}
if self.use_bearer {
req = req.header(header::AUTHORIZATION, format!("Bearer {}", access_token));
} else {
req = req.header(header::AUTHORIZATION, format!("OAuth {}", access_token));
}

req = req.header(header::USER_AGENT, USER_AGENT);
if let Some(client_id_header) = self.client_id_header {
req = req.header(client_id_header, token.client_id())
}
}

req
};
req = req.header(header::USER_AGENT, USER_AGENT);

let res = req.send().await.map_err(SendRequestError)?;
let status = res.status();
Expand All @@ -193,27 +193,27 @@ impl RequestBuilder {

if let Some(token) = self.token.as_ref() {
if status == StatusCode::UNAUTHORIZED {
token.force_refresh()?;
token.force_refresh().await?;
}
}

Ok(Response {
method: &self.method,
url: &self.url,
method: self.method,
url: self.url,
status,
body,
})
}
}

pub struct Response<'a, B> {
method: &'a Method,
url: &'a Url,
pub struct Response<B> {
method: Method,
url: Url,
status: StatusCode,
body: B,
}

impl Response<'_, Bytes> {
impl Response<Bytes> {
/// Expect a successful response.
pub fn ok(self) -> Result<(), Error> {
if self.status.is_success() {
Expand Down Expand Up @@ -264,7 +264,7 @@ impl Response<'_, Bytes> {
}
}

impl Response<'_, Option<Bytes>> {
impl Response<Option<Bytes>> {
/// Expect a JSON response of the given type.
pub fn json<T>(self) -> Result<Option<T>, Error>
where
Expand Down Expand Up @@ -324,12 +324,12 @@ impl Response<'_, Option<Bytes>> {
}
}

impl<'a, B> Response<'a, B>
impl<B> Response<B>
where
B: BodyHelper,
{
/// Handle as empty if we encounter the given status code.
pub fn empty_on_status(self, status: StatusCode) -> Response<'a, Option<B::Value>> {
pub fn empty_on_status(self, status: StatusCode) -> Response<Option<B::Value>> {
let body = if self.status == status {
None
} else {
Expand All @@ -345,7 +345,7 @@ where
}

/// Test if the underlying status is not found.
pub fn not_found(self) -> Response<'a, Option<B::Value>> {
pub fn not_found(self) -> Response<Option<B::Value>> {
self.empty_on_status(StatusCode::NOT_FOUND)
}
}
27 changes: 27 additions & 0 deletions bot/src/api/spotify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,33 @@ impl Spotify {
}
}

/// Enqueue the specified track.
pub async fn me_player_queue(
&self,
device_id: Option<String>,
track_uri: String,
) -> Result<bool, Error> {
let r = self
.request(Method::POST, &["me", "player", "queue"])
.query_param("uri", &track_uri)
.optional_query_param("device_id", device_id)
.header(header::CONTENT_TYPE, "application/json")
.header(header::ACCEPT, "application/json");

r.json_map(device_control).await
}

/// Skip to the next song.
pub async fn me_player_next(&self, device_id: Option<String>) -> Result<bool, Error> {
let r = self
.request(Method::POST, &["me", "player", "next"])
.optional_query_param("device_id", device_id)
.header(header::CONTENT_TYPE, "application/json")
.header(header::ACCEPT, "application/json");

r.json_map(device_control).await
}

/// Get my playlists.
pub async fn my_playlists(&self) -> Result<Page<SimplifiedPlaylist>, Error> {
let req = self.request(Method::GET, &["me", "playlists"]);
Expand Down
4 changes: 2 additions & 2 deletions bot/src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ impl Global {
track_id: None,
elapsed: 0,
duration: 0,
}
};
}
};

Expand All @@ -221,7 +221,7 @@ impl Global {
is_playing: false,
elapsed: 0,
duration: 0,
})
});
}
};

Expand Down
6 changes: 3 additions & 3 deletions bot/src/db/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ impl Commands {
first: Option<&'a str>,
it: &'a utils::Words,
) -> Option<(Arc<Command>, db::Captures<'a>)> {
self.inner
.read()
.await
let inner = self.inner.read().await;

inner
.resolve(channel, first, it)
.map(|(command, captures)| (command.clone(), captures))
}
Expand Down
Loading

0 comments on commit 59e99a5

Please sign in to comment.