Skip to content

Commit

Permalink
Basic librespot oauth integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Ovenoboyo committed Sep 20, 2024
1 parent 87809af commit bd24d7f
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 7,049 deletions.
310 changes: 243 additions & 67 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ tracing-subscriber = { features = [
"ansi",
"env-filter",
], default-features = false, version = "0.3.18" }
rustls = { version = "0.23.13", features = ["ring"] }

[build-dependencies.tauri-build]
version = "2.0.0-beta.19"
Expand Down
6,906 changes: 0 additions & 6,906 deletions src-tauri/features.txt

This file was deleted.

4 changes: 4 additions & 0 deletions src-tauri/librespot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ impl LibrespotHolder {
events.push(event_name.clone());
Ok(())
}

pub fn is_initialized(&self) -> Result<bool> {
Ok(self.check_initialized().is_ok())
}
}

generate_methods!(LibrespotHolder,
Expand Down
8 changes: 6 additions & 2 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fs;

use extensions::get_extension_state;
use librespot::{
get_canvaz, get_librespot_state, initialize_librespot, librespot_close, librespot_get_token,
get_canvaz, get_librespot_state, is_initialized, librespot_close, librespot_get_token,
librespot_load, librespot_pause, librespot_play, librespot_seek, librespot_volume,
register_event,
};
Expand Down Expand Up @@ -81,6 +81,10 @@ mod youtube;
#[tracing::instrument(level = "trace", skip())]
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
rustls::crypto::ring::default_provider()
.install_default()
.expect("Failed to install rustls crypto provider");

tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_deep_link::init())
Expand Down Expand Up @@ -137,7 +141,7 @@ pub fn run() {
// Scanner
start_scan,
// Librespot
initialize_librespot,
is_initialized,
librespot_play,
librespot_pause,
librespot_close,
Expand Down
37 changes: 12 additions & 25 deletions src-tauri/src/librespot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use librespot::{
};
use macros::{generate_command, generate_command_cached};

use preferences::preferences::PreferenceConfig;
use tauri::{AppHandle, Emitter, Manager, State, Window};
use types::{canvaz::CanvazResponse, errors::Result};

Expand All @@ -14,24 +13,10 @@ pub fn get_librespot_state() -> LibrespotHolder {
LibrespotHolder::new()
}

#[tracing::instrument(level = "trace", skip(app, window, librespot))]
#[tracing::instrument(level = "trace", skip(app))]
#[tauri::command()]
pub fn initialize_librespot(
app: AppHandle,
window: Window,
librespot: State<LibrespotHolder>,
) -> Result<()> {
let prefs: State<PreferenceConfig> = app.state();
let username: String = prefs.load_selective("spotify.username".into())?;
let password: String = prefs.load_selective("spotify.password".into())?;

tracing::info!(
"Initializing librespot {}@{}",
username.trim(),
password.trim()
);

let credentials = Credentials::with_password(username, password);
pub fn initialize_librespot(app: AppHandle, access_token: String) -> Result<()> {
let credentials = Credentials::with_access_token(access_token);

let player_config = PlayerConfig {
bitrate: Bitrate::Bitrate320,
Expand All @@ -55,6 +40,7 @@ pub fn initialize_librespot(
None,
)?;

let librespot: State<LibrespotHolder> = app.state();
librespot.initialize(
credentials,
player_config,
Expand All @@ -79,13 +65,13 @@ pub fn initialize_librespot(
"librespot_event_{}",
parsed_event.get("event").unwrap(),
)) {
window
.emit(
format!("librespot_event_{}", parsed_event.get("event").unwrap(),)
.as_str(),
parsed_event,
)
.unwrap();
tracing::info!("Emitting event {:?}", parsed_event);
app.emit(
format!("librespot_event_{}", parsed_event.get("event").unwrap(),)
.as_str(),
parsed_event,
)
.unwrap();
}
}
Err(e) => {
Expand All @@ -99,6 +85,7 @@ pub fn initialize_librespot(
Ok(())
}

generate_command!(is_initialized, LibrespotHolder, bool,);
generate_command!(librespot_play, LibrespotHolder, (),);
generate_command!(librespot_pause, LibrespotHolder, (),);
generate_command!(librespot_close, LibrespotHolder, (),);
Expand Down
10 changes: 8 additions & 2 deletions src-tauri/src/providers/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ pub struct OAuthClientArgs {

#[tracing::instrument(level = "trace", skip(config))]
pub fn get_oauth_client(config: OAuthClientArgs) -> OAuth2Client {
let client_secret = if config.client_secret.is_empty() {
None
} else {
Some(ClientSecret::new(config.client_secret))
};

BasicClient::new(
ClientId::new(config.client_id),
Some(ClientSecret::new(config.client_secret)),
client_secret,
AuthUrl::new(config.auth_url).unwrap(),
Some(TokenUrl::new(config.token_url).unwrap()),
)
Expand Down Expand Up @@ -118,7 +124,7 @@ pub fn login(
client: OAuth2Client,
app: &AppHandle,
) -> Result<(String, OAuth2Verifier)> {
if config.client_id.is_none() || config.client_secret.is_none() {
if config.client_id.is_none() {
return Err("Client ID not set".into());
}

Expand Down
68 changes: 64 additions & 4 deletions src-tauri/src/providers/spotify.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
io::{BufRead, BufReader},
net::{SocketAddr, TcpListener},
thread,
};

use async_trait::async_trait;

Expand Down Expand Up @@ -28,7 +33,7 @@ use types::{
use types::{errors::MoosyncError, providers::generic::GenericProvider};
use url::Url;

use crate::oauth::handler::OAuthHandler;
use crate::{librespot::initialize_librespot, oauth::handler::OAuthHandler};

use super::common::{
authorize, get_oauth_client, login, refresh_login, LoginArgs, OAuthClientArgs, TokenHolder,
Expand Down Expand Up @@ -95,7 +100,7 @@ impl SpotifyProvider {
token_url: "https://accounts.spotify.com/api/token".to_string(),
redirect_url: self.config.redirect_uri.to_string(),
client_id: self.config.client_id.clone().unwrap(),
client_secret: self.config.client_secret.clone().unwrap(),
client_secret: self.config.client_secret.clone().unwrap_or_default(),
})
}

Expand Down Expand Up @@ -257,7 +262,23 @@ impl GenericProvider for SpotifyProvider {

self.config.client_id = client_id.map(|v| v.as_str().unwrap().to_string());
self.config.client_secret = client_secret.map(|v| v.as_str().unwrap().to_string());
self.config.redirect_uri = "https://moosync.app/spotify";

if self
.config
.client_id
.as_ref()
.map_or(true, |id| id.is_empty())
|| self
.config
.client_secret
.as_ref()
.map_or(true, |secret| secret.is_empty())
{
self.config.redirect_uri = "http://127.0.0.1:8898/login";
self.config.client_id = Some("65b708073fc0480ea92a077233ca87bd".into())
} else {
self.config.redirect_uri = "https://moosync.app/spotify";
}
self.config.scopes = vec![
"playlist-read-private",
"user-top-read",
Expand Down Expand Up @@ -302,6 +323,40 @@ impl GenericProvider for SpotifyProvider {
)?;
self.verifier = verifier;

let redirect_uri = self.config.redirect_uri;
if redirect_uri.starts_with("http://127.0.0.1:8898") {
let app_handle = self.app.clone();
thread::spawn(move || {
let socket_addr = Url::parse(redirect_uri)
.unwrap()
.socket_addrs(|| None)
.unwrap()
.pop()
.unwrap();

tracing::info!("Listening {:?}", socket_addr);

let listener = TcpListener::bind(socket_addr).unwrap();
let stream = listener.incoming().flatten().next().unwrap();
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();

let code = request_line.split_whitespace().nth(1);
if let Some(code) = code {
tracing::info!("Got redirect URI {:?}", code);
let parsed_code = code.replace("/login", "");
let oauth_handler: State<OAuthHandler> = app_handle.state();
oauth_handler
.handle_oauth(
app_handle.clone(),
format!("moosync://spotifyoauthcallback{}", parsed_code),
)
.unwrap();
}
});
}

let oauth_handler: State<OAuthHandler> = self.app.state();
oauth_handler.register_oauth_path("spotifyoauthcallback".into(), self.key());

Expand Down Expand Up @@ -345,6 +400,11 @@ impl GenericProvider for SpotifyProvider {
);

self.create_api_client().await;
if let Some(tokens) = &self.config.tokens {
if let Err(err) = initialize_librespot(self.app.clone(), tokens.access_token.clone()) {
tracing::error!("Error initializing librespot {:?}", err);
}
}
Ok(())
}

Expand Down
6 changes: 0 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,6 @@ pub fn App() -> impl IntoView {
LibrespotPlayer::set_enabled(pref.enabled)
}
}
} else if key == "prefs.spotify.username" {
let value = value.as_string().unwrap();
LibrespotPlayer::set_has_username(!value.is_empty())
} else if key == "prefs.spotify.password" {
let value = value.as_string().unwrap();
LibrespotPlayer::set_has_password(!value.is_empty())
} else if key == "prefs.themes.active_theme" {
let value = value.as_string().unwrap();
handle_theme(value);
Expand Down
2 changes: 2 additions & 0 deletions src/components/audiostream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ impl PlayerHolder {
player.add_listeners(self.state_setter.clone());
}

console_log!("Active player: {}", player.key());

let (resolver_tx, resolver_rx) = oneshot::channel();
player.load(src.unwrap(), resolver_tx);

Expand Down
53 changes: 16 additions & 37 deletions src/players/librespot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ pub struct LibrespotPlayer {
}

static ENABLED: Mutex<bool> = Mutex::new(false);
static HAS_USERNAME: Mutex<bool> = Mutex::new(false);
static HAS_PASSWORD: Mutex<bool> = Mutex::new(false);
static INITIALIZED: Mutex<bool> = Mutex::new(false);

impl std::fmt::Debug for LibrespotPlayer {
Expand All @@ -98,28 +96,18 @@ impl LibrespotPlayer {
LibrespotPlayer::initialize_librespot();
}

pub fn set_has_username(en: bool) {
*(HAS_USERNAME.lock().unwrap()) = en;
LibrespotPlayer::initialize_librespot();
}

pub fn set_has_password(en: bool) {
*(HAS_PASSWORD.lock().unwrap()) = en;
LibrespotPlayer::initialize_librespot();
}

fn initialize_librespot() {
if *ENABLED.lock().unwrap()
&& *HAS_USERNAME.lock().unwrap()
&& *HAS_PASSWORD.lock().unwrap()
{
if *ENABLED.lock().unwrap() {
spawn_local(async move {
let res = invoke("initialize_librespot", JsValue::undefined()).await;
if res.is_err() {
console_log!("Error initializing librespot: {:?}", res.unwrap_err());
return;
let res = invoke("is_initialized", JsValue::undefined()).await;
console_log!("Librespot initialized: {:?}", res);
if let Ok(res) = res {
if let Some(initialized) = res.as_bool() {
*INITIALIZED.lock().unwrap() = initialized;
return;
}
}
*INITIALIZED.lock().unwrap() = true;
*INITIALIZED.lock().unwrap() = false;
})
}
}
Expand All @@ -128,28 +116,18 @@ impl LibrespotPlayer {
impl GenericPlayer for LibrespotPlayer {
fn initialize(&self, _: leptos::NodeRef<leptos::html::Div>) {
spawn_local(async move {
let enabled: Vec<CheckboxPreference> =
load_selective_async("spotify.enable".into()).await.unwrap();
let enabled: Vec<CheckboxPreference> = load_selective_async("spotify.enable".into())
.await
.unwrap_or(vec![CheckboxPreference {
key: "enable".into(),
enabled: true,
}]);
for pref in enabled {
if pref.key == "enable" {
LibrespotPlayer::set_enabled(pref.enabled)
}
}
});

spawn_local(async move {
let enabled: String = load_selective_async("spotify.username".into())
.await
.unwrap();
LibrespotPlayer::set_has_username(!enabled.is_empty())
});

spawn_local(async move {
let enabled: String = load_selective_async("spotify.password".into())
.await
.unwrap();
LibrespotPlayer::set_has_password(!enabled.is_empty())
});
}

fn key(&self) -> String {
Expand Down Expand Up @@ -238,6 +216,7 @@ impl GenericPlayer for LibrespotPlayer {
}

fn can_play(&self, song: &types::songs::Song) -> bool {
Self::initialize_librespot();
*INITIALIZED.lock().unwrap() && song.song.type_ == types::songs::SongType::SPOTIFY
}

Expand Down

0 comments on commit bd24d7f

Please sign in to comment.