feat: Last.fm beta, Similar Artists, Statistics Last.fm stats, TooltipPortal, CustomSelect, Psychowave theme (v1.7.0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-03-20 18:35:26 +01:00
parent 9400a5fb2b
commit 2ba7845c79
31 changed files with 1379 additions and 365 deletions
+8 -1
View File
@@ -2273,6 +2273,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.8.0"
@@ -3133,9 +3139,10 @@ dependencies = [
[[package]]
name = "psysonic"
version = "1.5.0"
version = "1.7.0"
dependencies = [
"biquad",
"md5",
"reqwest 0.12.28",
"rodio",
"serde",
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "psysonic"
version = "1.6.0"
version = "1.7.0"
description = "Psysonic Desktop Music Player"
authors = []
license = ""
@@ -31,7 +31,8 @@ tauri-plugin-fs = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
rodio = { version = "0.19", default-features = false, features = ["symphonia-all"] }
reqwest = { version = "0.12", features = ["stream"] }
reqwest = { version = "0.12", features = ["stream", "json"] }
md5 = "0.7"
tokio = { version = "1", features = ["rt", "time"] }
biquad = "0.4"
tauri-plugin-window-state = "2.4.1"
+57
View File
@@ -19,6 +19,62 @@ fn exit_app(app_handle: tauri::AppHandle) {
app_handle.exit(0);
}
/// Proxy Last.fm API calls through Rust/reqwest to avoid WebView networking restrictions.
/// `params` is a list of [key, value] pairs (method must be included).
/// If `sign` is true an api_sig is computed. If `get` is true, a GET request is made.
#[tauri::command]
async fn lastfm_request(
params: Vec<[String; 2]>,
sign: bool,
get: bool,
api_key: String,
api_secret: String,
) -> Result<serde_json::Value, String> {
use std::collections::HashMap;
let mut map: HashMap<String, String> = params.into_iter().map(|[k, v]| (k, v)).collect();
map.insert("api_key".into(), api_key.clone());
if sign {
let mut keys: Vec<String> = map.keys().cloned().collect();
keys.sort();
let sig_str: String = keys.iter()
.filter(|k| k.as_str() != "format" && k.as_str() != "callback")
.map(|k| format!("{}{}", k, map[k]))
.collect::<String>();
let sig_input = format!("{}{}", sig_str, api_secret);
let digest = md5::compute(sig_input.as_bytes());
map.insert("api_sig".into(), format!("{:x}", digest));
}
map.insert("format".into(), "json".into());
let client = reqwest::Client::new();
let resp = if get {
client
.get("https://ws.audioscrobbler.com/2.0/")
.query(&map)
.header("User-Agent", "psysonic/1.6.0")
.send()
.await
} else {
client
.post("https://ws.audioscrobbler.com/2.0/")
.form(&map)
.header("User-Agent", "psysonic/1.6.0")
.send()
.await
}.map_err(|e| e.to_string())?;
let json: serde_json::Value = resp.json().await.map_err(|e| e.to_string())?;
if let Some(err) = json.get("error") {
return Err(format!("Last.fm {} {}", err, json.get("message").and_then(|m| m.as_str()).unwrap_or("")));
}
Ok(json)
}
pub fn run() {
let (audio_engine, _audio_thread) = audio::create_engine();
@@ -128,6 +184,7 @@ pub fn run() {
audio::audio_set_eq,
audio::audio_preload,
audio::audio_set_crossfade,
lastfm_request,
])
.run(tauri::generate_context!())
.expect("error while running Psysonic");
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Psysonic",
"version": "1.6.0",
"version": "1.7.0",
"identifier": "dev.psysonic.player",
"build": {
"beforeDevCommand": "npm run dev",