Skip to content

Commit

Permalink
Merge pull request #113 from tsirysndr/feat/scrobble
Browse files Browse the repository at this point in the history
feat(rocksky): setup rocksky scrobbling
  • Loading branch information
tsirysndr authored Feb 5, 2025
2 parents 28e03f4 + f160136 commit 4338bbc
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 6 deletions.
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ os-release = "0.1.0"
reqwest = { version = "0.12.5", features = [
"rustls-tls",
"json",

], default-features = false }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
Expand Down
1 change: 1 addition & 0 deletions crates/graphql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ once_cell = "1.20.2"
owo-colors = "4.1.0"
reqwest = {version = "0.12.5", features = ["rustls-tls", "json"], default-features = false}
rockbox-library = {path = "../library"}
rockbox-rocksky = {path = "../rocksky"}
rockbox-search = {path = "../search"}
rockbox-sys = {path = "../sys"}
rockbox-types = {path = "../types"}
Expand Down
27 changes: 26 additions & 1 deletion crates/graphql/src/schema/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,26 @@ impl LibraryMutation {
pool.clone(),
Favourites {
id: cuid::cuid1()?,
track_id: Some(id),
track_id: Some(id.clone()),
created_at: chrono::Utc::now(),
album_id: None,
},
)
.await?;

let track = repo::track::find(pool.clone(), &id).await?;

if let Some(track) = track {
let album = repo::album::find(pool.clone(), &track.album_id).await?;
if let Some(album) = album {
match rockbox_rocksky::like(track, album).await {
Ok(_) => {}
Err(e) => {
eprintln!("Error liking track: {:?}", e);
}
}
}
}
Ok(0)
}

Expand All @@ -150,6 +164,17 @@ impl LibraryMutation {
async fn unlike_track(&self, ctx: &Context<'_>, id: String) -> Result<i32, Error> {
let pool = ctx.data::<Pool<Sqlite>>()?;
repo::favourites::delete(pool.clone(), &id).await?;

let track = repo::track::find(pool.clone(), &id).await?;

if let Some(track) = track {
match rockbox_rocksky::unlike(track).await {
Ok(_) => {}
Err(e) => {
eprintln!("Error unliking track: {:?}", e);
}
}
}
Ok(0)
}

Expand Down
20 changes: 20 additions & 0 deletions crates/rocksky/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "rockbox-rocksky"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
anyhow.workspace = true
dirs = "6.0.0"
rockbox-library = { path = "../library" }
reqwest = { version = "0.12.5", features = [
"rustls-tls",
"json",
"multipart"
], default-features = false }
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
sha256 = "1.5.0"
171 changes: 171 additions & 0 deletions crates/rocksky/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use anyhow::Error;
use reqwest::multipart;
use reqwest::Client;
use rockbox_library::entity::album::Album;
use rockbox_library::entity::track::Track;
use std::fs::File;
use std::io::Read;

pub async fn upload_album_cover(name: &str) -> Result<(), Error> {
let home = dirs::home_dir().unwrap();
let cover = home
.join(".config")
.join("rockbox.org")
.join("covers")
.join(name);

let mut file = File::open(&cover)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;

let part = multipart::Part::bytes(buffer).file_name(cover.display().to_string());
let form = multipart::Form::new().part("file", part);

let token_file = home.join(".config").join("rockbox.org").join("token");

if !token_file.exists() {
return Ok(());
}

let token = std::fs::read_to_string(token_file)?;

let client = Client::new();

const URL: &str = "https://uploads.rocksky.app";

let response = client
.post(URL)
.header("Authorization", format!("Bearer {}", token))
.multipart(form)
.send()
.await?;

println!("Cover uploaded: {}", response.status());

Ok(())
}

pub async fn scrobble(track: Track, album: Album) -> Result<(), Error> {
let home = dirs::home_dir().unwrap();
let token_file = home.join(".config").join("rockbox.org").join("token");

if !token_file.exists() {
return Ok(());
}

let token = std::fs::read_to_string(token_file)?;

if let Some(album_art) = track.album_art.clone() {
match upload_album_cover(&album_art).await {
Ok(_) => {}
Err(r) => {
eprintln!("Failed to upload album art: {}", r);
}
}
}

let client = Client::new();
const URL: &str = "https://api.rocksky.app/now-playing";
let response = client
.post(URL)
.header("Authorization", format!("Bearer {}", token))
.json(&serde_json::json!({
"title": track.title,
"album": track.album,
"artist": track.artist,
"albumArtist": track.album_artist,
"duration": track.length,
"trackNumber": track.track_number,
"releaseDate": match album.year_string.contains("-") {
true => Some(album.year_string),
false => None,
},
"year": album.year,
"discNumber": track.disc_number,
"composer": track.composer,
"albumArt": match track.album_art.is_some() {
true => Some(format!("https://cdn.rocksky.app/covers/{}", track.album_art.unwrap())),
false => None
}
}))
.send()
.await?;
println!("Scrobbled: {}", response.status());
Ok(())
}

pub async fn like(track: Track, album: Album) -> Result<(), Error> {
let home = dirs::home_dir().unwrap();
let token_file = home.join(".config").join("rockbox.org").join("token");

if !token_file.exists() {
return Ok(());
}

let token = std::fs::read_to_string(token_file)?;

if let Some(album_art) = track.album_art.clone() {
match upload_album_cover(&album_art).await {
Ok(_) => {}
Err(r) => {
eprintln!("Failed to upload album art: {}", r);
}
}
}

let client = Client::new();
const URL: &str = "https://api.rocksky.app/likes";
let response = client
.post(URL)
.header("Authorization", format!("Bearer {}", token))
.json(&serde_json::json!({
"title": track.title,
"album": track.album,
"artist": track.artist,
"albumArtist": track.album_artist,
"duration": track.length,
"trackNumber": track.track_number,
"releaseDate": match album.year_string.contains("-") {
true => Some(album.year_string),
false => None,
},
"year": album.year,
"discNumber": track.disc_number,
"composer": track.composer,
"albumArt": match track.album_art.is_some() {
true => Some(format!("https://cdn.rocksky.app/covers/{}", track.album_art.unwrap())),
false => None
}
}))
.send()
.await?;
println!("Liked: {}", response.status());
Ok(())
}

pub async fn unlike(track: Track) -> Result<(), Error> {
let home = dirs::home_dir().unwrap();
let token_file = home.join(".config").join("rockbox.org").join("token");

if !token_file.exists() {
return Ok(());
}

let token = std::fs::read_to_string(token_file)?;

let hash = sha256::digest(
format!("{} - {} - {}", track.title, track.artist, track.album).to_lowercase(),
);

let client = Client::new();
let url: &str = &format!("https://api.rocksky.app/likes/{}", hash);
let response = client
.delete(url)
.header("Authorization", format!("Bearer {}", token))
.send()
.await?;

println!("Unliked: {}", response.status());

Ok(())
}
1 change: 1 addition & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ reqwest = { version = "0.12.5", features = [
], default-features = false }
rockbox-graphql = { path = "../graphql" }
rockbox-library = { path = "../library" }
rockbox-rocksky = {path = "../rocksky"}
rockbox-search = { path = "../search" }
rockbox-sys = { path = "../sys" }
rockbox-types = { path = "../types" }
Expand Down
35 changes: 34 additions & 1 deletion crates/rpc/src/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,32 @@ impl LibraryService for Library {
self.pool.clone(),
Favourites {
id: cuid::cuid1().map_err(|e| tonic::Status::internal(e.to_string()))?,
track_id: Some(params.id),
track_id: Some(params.id.clone()),
created_at: chrono::Utc::now(),
album_id: None,
},
)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;

let track = repo::track::find(self.pool.clone(), &params.id)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;

if let Some(track) = track {
let album = repo::album::find(self.pool.clone(), &track.album_id)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;
if let Some(album) = album {
match rockbox_rocksky::like(track, album).await {
Ok(_) => {}
Err(e) => {
eprintln!("Error liking track: {:?}", e);
}
}
}
}

Ok(tonic::Response::new(LikeTrackResponse {}))
}

Expand Down Expand Up @@ -177,6 +196,20 @@ impl LibraryService for Library {
repo::favourites::delete(self.pool.clone(), &params.id)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;

let track = repo::track::find(self.pool.clone(), &params.id)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;

if let Some(track) = track {
match rockbox_rocksky::unlike(track).await {
Ok(_) => {}
Err(e) => {
eprintln!("Error unliking track: {:?}", e);
}
}
}

Ok(tonic::Response::new(UnlikeTrackResponse {}))
}

Expand Down
1 change: 1 addition & 0 deletions crates/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ rockbox-chromecast = {path = "../chromecast"}
rockbox-discovery = {path = "../discovery"}
rockbox-graphql = {path = "../graphql"}
rockbox-library = {path = "../library"}
rockbox-rocksky = {path = "../rocksky"}
rockbox-mpd = {path = "../mpd"}
rockbox-mpris = {path = "../mpris"}
rockbox-network = { path = "../network" }
Expand Down
Loading

0 comments on commit 4338bbc

Please sign in to comment.