Removed last.fm support
This commit is contained in:
parent
2c2b12f536
commit
a5061dfc92
18 changed files with 35 additions and 1046 deletions
|
@ -17,6 +17,7 @@
|
|||
- Added a new `/peaks` endpoint which returns audio signal peaks that can be used to draw waveform visualizations.
|
||||
- The `/thumbnail` endpoint supports a new size labeled `tiny` which returns 40x40px images.
|
||||
- Persistent data, such as playlists, is now saved in a directory that may be configured with the `--data` CLI option or the `POLARIS_DATA_DIR` environment variable.
|
||||
- Removed last.fm integration due to maintenance concerns (abandoned libraries, broken account linking) and mismatch with project goals.
|
||||
|
||||
### Web client
|
||||
|
||||
|
|
468
Cargo.lock
generated
468
Cargo.lock
generated
|
@ -213,7 +213,7 @@ dependencies = [
|
|||
"auto-future",
|
||||
"axum",
|
||||
"bytes",
|
||||
"cookie 0.18.1",
|
||||
"cookie",
|
||||
"http 1.1.0",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
|
@ -252,12 +252,6 @@ version = "0.2.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
|
@ -344,12 +338,6 @@ dependencies = [
|
|||
"orion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.8"
|
||||
|
@ -406,7 +394,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
|
|||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.23",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
@ -433,12 +421,6 @@ dependencies = [
|
|||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chunked_transfer"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
|
@ -460,49 +442,16 @@ version = "0.9.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.2.27",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"time 0.3.36",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3"
|
||||
dependencies = [
|
||||
"cookie 0.14.4",
|
||||
"idna 0.2.3",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.2.27",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
|
@ -633,12 +582,6 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discard"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
|
@ -662,7 +605,7 @@ checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602"
|
|||
dependencies = [
|
||||
"cc",
|
||||
"memchr",
|
||||
"rustc_version 0.4.0",
|
||||
"rustc_version",
|
||||
"toml 0.8.19",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
|
@ -800,7 +743,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
|||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"spin 0.9.8",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -995,7 +938,7 @@ dependencies = [
|
|||
"http 1.1.0",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1 0.10.6",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1166,17 +1109,6 @@ dependencies = [
|
|||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
|
@ -1219,15 +1151,6 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lasso2"
|
||||
version = "0.8.2"
|
||||
|
@ -1244,7 +1167,7 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin 0.9.8",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1313,12 +1236,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
|
@ -1335,12 +1252,6 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
@ -1459,7 +1370,7 @@ dependencies = [
|
|||
"native_model",
|
||||
"redb 1.5.1",
|
||||
"redb 2.1.3",
|
||||
"semver 1.0.23",
|
||||
"semver",
|
||||
"serde",
|
||||
"skeptic",
|
||||
"thiserror",
|
||||
|
@ -1826,7 +1737,6 @@ dependencies = [
|
|||
"rand",
|
||||
"rayon",
|
||||
"regex",
|
||||
"rustfm-scrobble",
|
||||
"sd-notify",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
|
@ -1843,7 +1753,7 @@ dependencies = [
|
|||
"tower-http",
|
||||
"trie-rs",
|
||||
"unicase",
|
||||
"ureq 2.10.0",
|
||||
"ureq",
|
||||
"winres",
|
||||
]
|
||||
|
||||
|
@ -1890,12 +1800,6 @@ dependencies = [
|
|||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
|
@ -1914,16 +1818,6 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f"
|
||||
dependencies = [
|
||||
"idna 0.2.3",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.6"
|
||||
|
@ -1935,15 +1829,6 @@ dependencies = [
|
|||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qstring"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
|
@ -2078,21 +1963,6 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted 0.7.1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
|
@ -2103,8 +1973,8 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
|
@ -2150,22 +2020,13 @@ version = "0.1.24"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver 1.0.23",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2183,19 +2044,6 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustfm-scrobble"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c46a75fb6409a528f7e0d8e99826684f88461d1b0d0edeec60d82e3f554dad5"
|
||||
dependencies = [
|
||||
"md5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq 1.5.5",
|
||||
"wrapped-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
|
@ -2209,19 +2057,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"log",
|
||||
"ring 0.16.20",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.12"
|
||||
|
@ -2230,7 +2065,7 @@ checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
|
|||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
|
@ -2249,9 +2084,9 @@ version = "0.102.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted 0.9.0",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2281,31 +2116,12 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
|
||||
dependencies = [
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sd-notify"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4646d6f919800cd25c50edb49438a1381e2cd4833c027e75e8897981c50b8b5e"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
|
@ -2315,12 +2131,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.204"
|
||||
|
@ -2384,15 +2194,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
|
||||
dependencies = [
|
||||
"sha1_smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
|
@ -2404,12 +2205,6 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
|
@ -2445,7 +2240,7 @@ checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
|
|||
dependencies = [
|
||||
"log",
|
||||
"termcolor",
|
||||
"time 0.3.36",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2491,12 +2286,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
|
@ -2646,7 +2435,7 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"rand",
|
||||
"rsa",
|
||||
"sha1 0.10.6",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
|
@ -2730,64 +2519,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "standback"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
||||
dependencies = [
|
||||
"discard",
|
||||
"rustc_version 0.2.3",
|
||||
"stdweb-derive",
|
||||
"stdweb-internal-macros",
|
||||
"stdweb-internal-runtime",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb-derive"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb-internal-macros"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
|
||||
dependencies = [
|
||||
"base-x",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha1 0.6.1",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb-internal-runtime"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||
|
||||
[[package]]
|
||||
name = "strength_reduce"
|
||||
version = "0.2.4"
|
||||
|
@ -3083,21 +2814,6 @@ dependencies = [
|
|||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
|
||||
dependencies = [
|
||||
"const_fn",
|
||||
"libc",
|
||||
"standback",
|
||||
"stdweb",
|
||||
"time-macros 0.1.1",
|
||||
"version_check",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
|
@ -3112,7 +2828,7 @@ dependencies = [
|
|||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros 0.2.18",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3121,16 +2837,6 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"time-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.18"
|
||||
|
@ -3141,19 +2847,6 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"standback",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.8.0"
|
||||
|
@ -3446,37 +3139,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b8b063c2d59218ae09f22b53c42eaad0d53516457905f5235ca4bc9e99daa71"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"chunked_transfer",
|
||||
"cookie 0.14.4",
|
||||
"cookie_store",
|
||||
"log",
|
||||
"once_cell",
|
||||
"qstring",
|
||||
"rustls 0.19.1",
|
||||
"url",
|
||||
"webpki",
|
||||
"webpki-roots 0.21.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.10.0"
|
||||
|
@ -3486,10 +3154,10 @@ dependencies = [
|
|||
"base64 0.22.1",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.12",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"url",
|
||||
"webpki-roots 0.26.3",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3499,7 +3167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 0.5.0",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
|
@ -3566,89 +3234,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.72",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
|
||||
dependencies = [
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.3"
|
||||
|
@ -3887,17 +3472,6 @@ dependencies = [
|
|||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrapped-vec"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b85e08702c1e919669e1e90213c9c75ea4bb689d0f3970347e2b37c04600b4e5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
|
|
|
@ -33,11 +33,9 @@ nohash-hasher = "0.2.0"
|
|||
num_cpus = "1.14.0"
|
||||
opus_headers = "0.1.2"
|
||||
pbkdf2 = "0.11"
|
||||
percent-encoding = "2.2"
|
||||
rand = "0.8"
|
||||
rayon = "1.10.0"
|
||||
regex = "1.10.5"
|
||||
rustfm-scrobble = "1.1.1"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_derive = "1.0.147"
|
||||
serde_json = "1.0.122"
|
||||
|
@ -98,6 +96,7 @@ winres = "0.1"
|
|||
[dev-dependencies]
|
||||
axum-test = "15.7"
|
||||
bytes = "1.7.1"
|
||||
percent-encoding = "2.2"
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
|
|
@ -963,163 +963,6 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/lastfm/now_playing/{song}": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"Last.fm"
|
||||
],
|
||||
"summary": "Tells Last.fm the song currently being played",
|
||||
"operationId": "putLastFMNowPlaying",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "song",
|
||||
"in": "path",
|
||||
"description": "Path to the song being played",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"auth_http_bearer": [],
|
||||
"auth_query_parameter": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/lastfm/scrobble/{song}": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Last.fm"
|
||||
],
|
||||
"summary": "Tells Last.fm that a song has been playing for long enough to be scrobbled",
|
||||
"operationId": "postLastFMScrobble",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "song",
|
||||
"in": "path",
|
||||
"description": "Path to the song being played",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"auth_http_bearer": [],
|
||||
"auth_query_parameter": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/lastfm/link_token": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Last.fm"
|
||||
],
|
||||
"summary": "Obtain an authentication token to be used when linking a Polaris account to a Last.fm account. The token is only valid for 10 minutes.",
|
||||
"operationId": "getLastFMLinkToken",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LastFMLinkToken"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"auth_http_bearer": [],
|
||||
"auth_query_parameter": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/lastfm/link": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Last.fm"
|
||||
],
|
||||
"summary": "Links a Polaris user with a Last.fm account.",
|
||||
"externalDocs": {
|
||||
"description": "This endpoint is meant to be used as a Last.fm authentication handler, as described here:",
|
||||
"url": "https://www.last.fm/api/webauth"
|
||||
},
|
||||
"operationId": "getLastFMLink",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "auth_token",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"description": "Polaris authentication token received from the `lastfm/link_token` endpoint",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "token",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"description": "Last.fm authentication token",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"description": "Base64 encoded HTML content to be returned to the client initiating the link operation",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"text/html": {
|
||||
"description": "The same content originally present in the 'content' parameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Last.fm"
|
||||
],
|
||||
"summary": "Unlinks Polaris user and Last.fm account",
|
||||
"operationId": "deleteLastFMLink",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"auth_http_bearer": [],
|
||||
"auth_query_parameter": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
|
@ -1262,9 +1105,6 @@
|
|||
"Preferences": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lastfm_username": {
|
||||
"type": "string"
|
||||
},
|
||||
"web_theme_base": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1298,14 +1138,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"LastFMLinkToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CollectionFile": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
|
13
src/app.rs
13
src/app.rs
|
@ -8,7 +8,6 @@ pub mod config;
|
|||
pub mod ddns;
|
||||
pub mod formats;
|
||||
pub mod index;
|
||||
pub mod lastfm;
|
||||
pub mod ndb;
|
||||
pub mod peaks;
|
||||
pub mod playlist;
|
||||
|
@ -133,21 +132,12 @@ pub enum Error {
|
|||
InvalidAuthToken,
|
||||
#[error("Incorrect authorization scope")]
|
||||
IncorrectAuthorizationScope,
|
||||
#[error("Last.fm session key is missing")]
|
||||
MissingLastFMSessionKey,
|
||||
#[error("Failed to hash password")]
|
||||
PasswordHashing,
|
||||
#[error("Failed to encode authorization token")]
|
||||
AuthorizationTokenEncoding,
|
||||
#[error("Failed to encode Branca token")]
|
||||
BrancaTokenEncoding,
|
||||
|
||||
#[error("Failed to authenticate with last.fm")]
|
||||
ScrobblerAuthentication(rustfm_scrobble::ScrobblerError),
|
||||
#[error("Failed to emit last.fm scrobble")]
|
||||
Scrobble(rustfm_scrobble::ScrobblerError),
|
||||
#[error("Failed to emit last.fm now playing update")]
|
||||
NowPlaying(rustfm_scrobble::ScrobblerError),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -159,7 +149,6 @@ pub struct App {
|
|||
pub index_manager: index::Manager,
|
||||
pub config_manager: config::Manager,
|
||||
pub ddns_manager: ddns::Manager,
|
||||
pub lastfm_manager: lastfm::Manager,
|
||||
pub peaks_manager: peaks::Manager,
|
||||
pub playlist_manager: playlist::Manager,
|
||||
pub settings_manager: settings::Manager,
|
||||
|
@ -210,7 +199,6 @@ impl App {
|
|||
let peaks_manager = peaks::Manager::new(peaks_dir_path);
|
||||
let playlist_manager = playlist::Manager::new(ndb_manager);
|
||||
let thumbnail_manager = thumbnail::Manager::new(thumbnails_dir_path);
|
||||
let lastfm_manager = lastfm::Manager::new(index_manager.clone(), user_manager.clone());
|
||||
|
||||
if let Some(config_path) = paths.config_file_path {
|
||||
let config = config::Config::from_path(&config_path)?;
|
||||
|
@ -225,7 +213,6 @@ impl App {
|
|||
index_manager,
|
||||
config_manager,
|
||||
ddns_manager,
|
||||
lastfm_manager,
|
||||
peaks_manager,
|
||||
playlist_manager,
|
||||
settings_manager,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
|
@ -248,29 +247,6 @@ impl Manager {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn get_song_internal(virtual_path: &PathBuf, index: &Index) -> Result<Song, Error> {
|
||||
let Some(virtual_path) = virtual_path.get(&index.strings) else {
|
||||
return Err(Error::SongNotFound);
|
||||
};
|
||||
let song_key = SongKey { virtual_path };
|
||||
index
|
||||
.collection
|
||||
.get_song(&index.strings, song_key)
|
||||
.ok_or_else(|| Error::SongNotFound)
|
||||
}
|
||||
|
||||
pub async fn get_song(&self, virtual_path: PathBuf) -> Result<Song, Error> {
|
||||
spawn_blocking({
|
||||
let index_manager = self.clone();
|
||||
move || {
|
||||
let index = index_manager.index.read().unwrap();
|
||||
Self::get_song_internal(&virtual_path, index.borrow())
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_songs(&self, virtual_paths: Vec<PathBuf>) -> Vec<Result<Song, Error>> {
|
||||
spawn_blocking({
|
||||
let index_manager = self.clone();
|
||||
|
@ -278,7 +254,14 @@ impl Manager {
|
|||
let index = index_manager.index.read().unwrap();
|
||||
virtual_paths
|
||||
.into_iter()
|
||||
.map(|path| Self::get_song_internal(&path, index.borrow()))
|
||||
.map(|p| {
|
||||
p.get(&index.strings)
|
||||
.and_then(|virtual_path| {
|
||||
let key = SongKey { virtual_path };
|
||||
index.collection.get_song(&index.strings, key)
|
||||
})
|
||||
.ok_or_else(|| Error::SongNotFound)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
use rustfm_scrobble::{Scrobble, Scrobbler};
|
||||
use std::path::Path;
|
||||
use user::AuthToken;
|
||||
|
||||
use crate::app::{index, user, Error};
|
||||
|
||||
const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
|
||||
const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Manager {
|
||||
index_manager: index::Manager,
|
||||
user_manager: user::Manager,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new(index_manager: index::Manager, user_manager: user::Manager) -> Self {
|
||||
Self {
|
||||
index_manager,
|
||||
user_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_link_token(&self, username: &str) -> Result<AuthToken, Error> {
|
||||
self.user_manager
|
||||
.generate_lastfm_link_token(username)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub async fn link(&self, username: &str, lastfm_token: &str) -> Result<(), Error> {
|
||||
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
|
||||
let auth_response = scrobbler
|
||||
.authenticate_with_token(lastfm_token)
|
||||
.map_err(Error::ScrobblerAuthentication)?;
|
||||
|
||||
self.user_manager
|
||||
.lastfm_link(username, &auth_response.name, &auth_response.key)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub async fn unlink(&self, username: &str) -> Result<(), Error> {
|
||||
self.user_manager
|
||||
.lastfm_unlink(username)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub async fn scrobble(&self, username: &str, track: &Path) -> Result<(), Error> {
|
||||
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
|
||||
let scrobble = self.scrobble_from_path(track).await?;
|
||||
let auth_token = self.user_manager.get_lastfm_session_key(username).await?;
|
||||
scrobbler.authenticate_with_session_key(&auth_token);
|
||||
scrobbler.scrobble(&scrobble).map_err(Error::Scrobble)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn now_playing(&self, username: &str, track: &Path) -> Result<(), Error> {
|
||||
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
|
||||
let scrobble = self.scrobble_from_path(track).await?;
|
||||
let auth_token = self.user_manager.get_lastfm_session_key(username).await?;
|
||||
scrobbler.authenticate_with_session_key(&auth_token);
|
||||
scrobbler
|
||||
.now_playing(&scrobble)
|
||||
.map_err(Error::NowPlaying)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn scrobble_from_path(&self, track: &Path) -> Result<Scrobble, Error> {
|
||||
let song = self.index_manager.get_song(track.to_owned()).await?;
|
||||
Ok(Scrobble::new(
|
||||
song.artists.first().map(|s| s.as_str()).unwrap_or(""),
|
||||
song.title.as_deref().unwrap_or(""),
|
||||
song.album.as_deref().unwrap_or(""),
|
||||
))
|
||||
}
|
||||
}
|
|
@ -33,7 +33,6 @@ pub struct AuthToken(pub String);
|
|||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum AuthorizationScope {
|
||||
PolarisAuth,
|
||||
LastFMLink,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
|
@ -44,7 +43,6 @@ pub struct Authorization {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Preferences {
|
||||
pub lastfm_username: Option<String>,
|
||||
pub web_theme_base: Option<String>,
|
||||
pub web_theme_accent: Option<String>,
|
||||
}
|
||||
|
@ -150,8 +148,7 @@ impl Manager {
|
|||
) -> Result<Authorization, Error> {
|
||||
let AuthToken(data) = auth_token;
|
||||
let ttl = match scope {
|
||||
AuthorizationScope::PolarisAuth => 0, // permanent
|
||||
AuthorizationScope::LastFMLink => 10 * 60, // 10 minutes
|
||||
AuthorizationScope::PolarisAuth => 0, // permanent
|
||||
};
|
||||
let authorization = branca::decode(data, &self.auth_secret.key, ttl)
|
||||
.map_err(|_| Error::InvalidAuthToken)?;
|
||||
|
@ -211,7 +208,7 @@ impl Manager {
|
|||
pub async fn read_preferences(&self, username: &str) -> Result<Preferences, Error> {
|
||||
Ok(sqlx::query_as!(
|
||||
Preferences,
|
||||
"SELECT web_theme_base, web_theme_accent, lastfm_username FROM users WHERE name = $1",
|
||||
"SELECT web_theme_base, web_theme_accent FROM users WHERE name = $1",
|
||||
username
|
||||
)
|
||||
.fetch_one(self.db.connect().await?.as_mut())
|
||||
|
@ -233,56 +230,6 @@ impl Manager {
|
|||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn lastfm_link(
|
||||
&self,
|
||||
username: &str,
|
||||
lastfm_login: &str,
|
||||
session_key: &str,
|
||||
) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE users SET lastfm_username = $1, lastfm_session_key = $2 WHERE name = $3",
|
||||
lastfm_login,
|
||||
session_key,
|
||||
username
|
||||
)
|
||||
.execute(self.db.connect().await?.as_mut())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_lastfm_link_token(&self, username: &str) -> Result<AuthToken, Error> {
|
||||
self.generate_auth_token(&Authorization {
|
||||
username: username.to_owned(),
|
||||
scope: AuthorizationScope::LastFMLink,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_lastfm_session_key(&self, username: &str) -> Result<String, Error> {
|
||||
let token: Option<String> = sqlx::query_scalar!(
|
||||
"SELECT lastfm_session_key FROM users WHERE name = $1",
|
||||
username
|
||||
)
|
||||
.fetch_one(self.db.connect().await?.as_mut())
|
||||
.await?;
|
||||
token.ok_or(Error::MissingLastFMSessionKey)
|
||||
}
|
||||
|
||||
pub async fn is_lastfm_linked(&self, username: &str) -> bool {
|
||||
self.get_lastfm_session_key(username).await.is_ok()
|
||||
}
|
||||
|
||||
pub async fn lastfm_unlink(&self, username: &str) -> Result<(), Error> {
|
||||
let null: Option<String> = None;
|
||||
sqlx::query!(
|
||||
"UPDATE users SET lastfm_session_key = $1, lastfm_username = $1 WHERE name = $2",
|
||||
null,
|
||||
username
|
||||
)
|
||||
.execute(self.db.connect().await?.as_mut())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_password(password: &str) -> Result<String, Error> {
|
||||
|
@ -378,7 +325,6 @@ mod test {
|
|||
let new_preferences = Preferences {
|
||||
web_theme_base: Some("very-dark-theme".to_owned()),
|
||||
web_theme_accent: Some("#FF0000".to_owned()),
|
||||
lastfm_username: None,
|
||||
};
|
||||
|
||||
let new_user = NewUser {
|
||||
|
@ -481,29 +427,4 @@ mod test {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn authenticate_validates_scope() {
|
||||
let ctx = test::ContextBuilder::new(test_name!()).build().await;
|
||||
|
||||
let new_user = NewUser {
|
||||
name: TEST_USERNAME.to_owned(),
|
||||
password: TEST_PASSWORD.to_owned(),
|
||||
admin: false,
|
||||
};
|
||||
|
||||
ctx.user_manager.create(&new_user).await.unwrap();
|
||||
let token = ctx
|
||||
.user_manager
|
||||
.generate_lastfm_link_token(TEST_USERNAME)
|
||||
.unwrap();
|
||||
let authorization = ctx
|
||||
.user_manager
|
||||
.authenticate(&token, AuthorizationScope::PolarisAuth)
|
||||
.await;
|
||||
assert!(matches!(
|
||||
authorization.unwrap_err(),
|
||||
Error::IncorrectAuthorizationScope
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@ CREATE TABLE users (
|
|||
name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
admin INTEGER NOT NULL,
|
||||
lastfm_username TEXT,
|
||||
lastfm_session_key TEXT,
|
||||
web_theme_base TEXT,
|
||||
web_theme_accent TEXT,
|
||||
UNIQUE(name)
|
||||
|
|
|
@ -65,12 +65,6 @@ impl FromRef<App> for app::ddns::Manager {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromRef<App> for app::lastfm::Manager {
|
||||
fn from_ref(app: &App) -> Self {
|
||||
app.lastfm_manager.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<App> for app::peaks::Manager {
|
||||
fn from_ref(app: &App) -> Self {
|
||||
app.peaks_manager.clone()
|
||||
|
|
|
@ -2,21 +2,17 @@ use std::path::PathBuf;
|
|||
|
||||
use axum::{
|
||||
extract::{DefaultBodyLimit, Path, Query, State},
|
||||
response::{Html, IntoResponse, Response},
|
||||
response::{IntoResponse, Response},
|
||||
routing::{delete, get, post, put},
|
||||
Json, Router,
|
||||
};
|
||||
use axum_extra::headers::Range;
|
||||
use axum_extra::TypedHeader;
|
||||
use axum_range::{KnownSize, Ranged};
|
||||
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
|
||||
use percent_encoding::percent_decode_str;
|
||||
use tower_http::{compression::CompressionLayer, CompressionLevel};
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
config, ddns, index, lastfm, peaks, playlist, scanner, settings, thumbnail, user, vfs, App,
|
||||
},
|
||||
app::{config, ddns, index, peaks, playlist, scanner, settings, thumbnail, user, vfs, App},
|
||||
server::{
|
||||
dto, error::APIError, APIMajorVersion, API_ARRAY_SEPARATOR, API_MAJOR_VERSION,
|
||||
API_MINOR_VERSION,
|
||||
|
@ -73,12 +69,6 @@ pub fn router() -> Router<App> {
|
|||
.route("/playlist/:name", put(put_playlist))
|
||||
.route("/playlist/:name", get(get_playlist))
|
||||
.route("/playlist/:name", delete(delete_playlist))
|
||||
// LastFM
|
||||
.route("/lastfm/now_playing/*path", put(put_lastfm_now_playing))
|
||||
.route("/lastfm/scrobble/*path", post(post_lastfm_scrobble))
|
||||
.route("/lastfm/link_token", get(get_lastfm_link_token))
|
||||
.route("/lastfm/link", get(get_lastfm_link))
|
||||
.route("/lastfm/link", delete(delete_lastfm_link))
|
||||
// Media
|
||||
.route("/songs", post(get_songs)) // post because of https://github.com/whatwg/fetch/issues/551
|
||||
.route("/peaks/*path", get(get_peaks))
|
||||
|
@ -728,77 +718,3 @@ async fn get_thumbnail(
|
|||
let range = range.map(|TypedHeader(r)| r);
|
||||
Ok(Ranged::new(range, body))
|
||||
}
|
||||
|
||||
async fn put_lastfm_now_playing(
|
||||
auth: Auth,
|
||||
State(lastfm_manager): State<lastfm::Manager>,
|
||||
State(user_manager): State<user::Manager>,
|
||||
Path(path): Path<PathBuf>,
|
||||
) -> Result<(), APIError> {
|
||||
if !user_manager.is_lastfm_linked(auth.get_username()).await {
|
||||
return Err(APIError::LastFMAccountNotLinked);
|
||||
}
|
||||
lastfm_manager
|
||||
.now_playing(auth.get_username(), &path)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn post_lastfm_scrobble(
|
||||
auth: Auth,
|
||||
State(lastfm_manager): State<lastfm::Manager>,
|
||||
State(user_manager): State<user::Manager>,
|
||||
Path(path): Path<PathBuf>,
|
||||
) -> Result<(), APIError> {
|
||||
if !user_manager.is_lastfm_linked(auth.get_username()).await {
|
||||
return Err(APIError::LastFMAccountNotLinked);
|
||||
}
|
||||
lastfm_manager.scrobble(auth.get_username(), &path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_lastfm_link_token(
|
||||
auth: Auth,
|
||||
State(lastfm_manager): State<lastfm::Manager>,
|
||||
) -> Result<Json<dto::LastFMLinkToken>, APIError> {
|
||||
let user::AuthToken(value) = lastfm_manager.generate_link_token(auth.get_username())?;
|
||||
Ok(Json(dto::LastFMLinkToken { value }))
|
||||
}
|
||||
|
||||
async fn get_lastfm_link(
|
||||
State(lastfm_manager): State<lastfm::Manager>,
|
||||
State(user_manager): State<user::Manager>,
|
||||
Query(payload): Query<dto::LastFMLink>,
|
||||
) -> Result<Html<String>, APIError> {
|
||||
let auth_token = user::AuthToken(payload.auth_token.clone());
|
||||
let authorization = user_manager
|
||||
.authenticate(&auth_token, user::AuthorizationScope::LastFMLink)
|
||||
.await?;
|
||||
let lastfm_token = &payload.token;
|
||||
lastfm_manager
|
||||
.link(&authorization.username, lastfm_token)
|
||||
.await?;
|
||||
|
||||
// Percent decode
|
||||
let base64_content = percent_decode_str(&payload.content).decode_utf8_lossy();
|
||||
|
||||
// Base64 decode
|
||||
let popup_content = BASE64_STANDARD_NO_PAD
|
||||
.decode(base64_content.as_bytes())
|
||||
.map_err(|_| APIError::LastFMLinkContentBase64DecodeError)?;
|
||||
|
||||
// UTF-8 decode
|
||||
let popup_content_string = std::str::from_utf8(&popup_content)
|
||||
.map_err(|_| APIError::LastFMLinkContentEncodingError)
|
||||
.map(|s| s.to_owned())?;
|
||||
|
||||
Ok(Html(popup_content_string))
|
||||
}
|
||||
|
||||
async fn delete_lastfm_link(
|
||||
auth: Auth,
|
||||
State(lastfm_manager): State<lastfm::Manager>,
|
||||
) -> Result<(), APIError> {
|
||||
lastfm_manager.unlink(auth.get_username()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -32,12 +32,6 @@ impl IntoResponse for APIError {
|
|||
APIError::IncorrectCredentials => StatusCode::UNAUTHORIZED,
|
||||
APIError::Internal => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
APIError::Io(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
APIError::LastFMAccountNotLinked => StatusCode::NO_CONTENT,
|
||||
APIError::LastFMLinkContentBase64DecodeError => StatusCode::BAD_REQUEST,
|
||||
APIError::LastFMLinkContentEncodingError => StatusCode::BAD_REQUEST,
|
||||
APIError::LastFMNowPlaying(_) => StatusCode::FAILED_DEPENDENCY,
|
||||
APIError::LastFMScrobble(_) => StatusCode::FAILED_DEPENDENCY,
|
||||
APIError::LastFMScrobblerAuthentication(_) => StatusCode::FAILED_DEPENDENCY,
|
||||
APIError::OwnAdminPrivilegeRemoval => StatusCode::CONFLICT,
|
||||
APIError::PasswordHashing => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
APIError::PlaylistNotFound => StatusCode::NOT_FOUND,
|
||||
|
|
|
@ -76,18 +76,6 @@ pub struct SavePlaylistInput {
|
|||
pub tracks: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LastFMLink {
|
||||
pub auth_token: String, // user::AuthToken emitted by Polaris, valid for LastFMLink scope
|
||||
pub token: String, // LastFM token for use in scrobble calls
|
||||
pub content: String, // Payload to send back to client after successful link
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LastFMLinkToken {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
|
|
|
@ -105,18 +105,6 @@ pub struct SavePlaylistInput {
|
|||
pub tracks: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LastFMLink {
|
||||
pub auth_token: String, // user::AuthToken emitted by Polaris, valid for LastFMLink scope
|
||||
pub token: String, // LastFM token for use in scrobble calls
|
||||
pub content: String, // Payload to send back to client after successful link
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LastFMLinkToken {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
|
|
|
@ -47,18 +47,6 @@ pub enum APIError {
|
|||
EmptyPassword,
|
||||
#[error("Incorrect Credentials")]
|
||||
IncorrectCredentials,
|
||||
#[error("No last.fm account has been linked")]
|
||||
LastFMAccountNotLinked,
|
||||
#[error("Could not decode content as base64 after linking last.fm account")]
|
||||
LastFMLinkContentBase64DecodeError,
|
||||
#[error("Could not decode content as UTF-8 after linking last.fm account")]
|
||||
LastFMLinkContentEncodingError,
|
||||
#[error("Could send Now Playing update to last.fm:\n\n{0}")]
|
||||
LastFMNowPlaying(rustfm_scrobble::ScrobblerError),
|
||||
#[error("Could emit scrobble with last.fm:\n\n{0}")]
|
||||
LastFMScrobble(rustfm_scrobble::ScrobblerError),
|
||||
#[error("Could authenticate with last.fm:\n\n{0}")]
|
||||
LastFMScrobblerAuthentication(rustfm_scrobble::ScrobblerError),
|
||||
#[error("Internal server error")]
|
||||
Internal,
|
||||
#[error("File I/O error for `{0}`:\n\n{1}")]
|
||||
|
@ -156,14 +144,9 @@ impl From<app::Error> for APIError {
|
|||
app::Error::IncorrectPassword => APIError::IncorrectCredentials,
|
||||
app::Error::InvalidAuthToken => APIError::IncorrectCredentials,
|
||||
app::Error::IncorrectAuthorizationScope => APIError::IncorrectCredentials,
|
||||
app::Error::MissingLastFMSessionKey => APIError::IncorrectCredentials,
|
||||
app::Error::PasswordHashing => APIError::PasswordHashing,
|
||||
app::Error::AuthorizationTokenEncoding => APIError::AuthorizationTokenEncoding,
|
||||
app::Error::BrancaTokenEncoding => APIError::BrancaTokenEncoding,
|
||||
|
||||
app::Error::ScrobblerAuthentication(e) => APIError::LastFMScrobblerAuthentication(e),
|
||||
app::Error::Scrobble(e) => APIError::LastFMScrobble(e),
|
||||
app::Error::NowPlaying(e) => APIError::LastFMNowPlaying(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ mod auth;
|
|||
mod browser;
|
||||
mod collection;
|
||||
mod ddns;
|
||||
mod lastfm;
|
||||
mod media;
|
||||
mod playlist;
|
||||
mod search;
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
use http::StatusCode;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::server::dto;
|
||||
use crate::server::test::{constants::*, protocol, ServiceType, TestService};
|
||||
use crate::test_name;
|
||||
|
||||
#[tokio::test]
|
||||
async fn lastfm_scrobble_ignores_unlinked_user() {
|
||||
let mut service = ServiceType::new(&test_name!()).await;
|
||||
service.complete_initial_setup().await;
|
||||
service.login_admin().await;
|
||||
service.index().await;
|
||||
service.login().await;
|
||||
|
||||
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "02 - Candlelight.mp3"]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
let request = protocol::lastfm_scrobble(&path);
|
||||
let response = service.fetch(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lastfm_now_playing_ignores_unlinked_user() {
|
||||
let mut service = ServiceType::new(&test_name!()).await;
|
||||
service.complete_initial_setup().await;
|
||||
service.login_admin().await;
|
||||
service.index().await;
|
||||
service.login().await;
|
||||
|
||||
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "02 - Candlelight.mp3"]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
let request = protocol::lastfm_now_playing(&path);
|
||||
let response = service.fetch(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lastfm_link_token_requires_auth() {
|
||||
let mut service = ServiceType::new(&test_name!()).await;
|
||||
let request = protocol::lastfm_link_token();
|
||||
let response = service.fetch(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lastfm_link_token_golden_path() {
|
||||
let mut service = ServiceType::new(&test_name!()).await;
|
||||
service.complete_initial_setup().await;
|
||||
service.login().await;
|
||||
|
||||
let request = protocol::lastfm_link_token();
|
||||
let response = service
|
||||
.fetch_json::<_, dto::LastFMLinkToken>(&request)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let link_token = response.body();
|
||||
assert!(!link_token.value.is_empty());
|
||||
}
|
|
@ -364,34 +364,6 @@ pub fn delete_playlist(name: &str) -> Request<()> {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn lastfm_link_token() -> Request<()> {
|
||||
Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri("/api/lastfm/link_token")
|
||||
.body(())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn lastfm_now_playing(path: &Path) -> Request<()> {
|
||||
let path = path.to_string_lossy();
|
||||
let endpoint = format!("/api/lastfm/now_playing/{}", url_encode(path.as_ref()));
|
||||
Request::builder()
|
||||
.method(Method::PUT)
|
||||
.uri(&endpoint)
|
||||
.body(())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn lastfm_scrobble(path: &Path) -> Request<()> {
|
||||
let path = path.to_string_lossy();
|
||||
let endpoint = format!("/api/lastfm/scrobble/{}", url_encode(path.as_ref()));
|
||||
Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(&endpoint)
|
||||
.body(())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn url_encode(input: &str) -> String {
|
||||
percent_encode(input.as_bytes(), NON_ALPHANUMERIC).to_string()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue