Compare commits

...

10 Commits

Author SHA1 Message Date
c09494ba9b Add Dockerfile 2025-08-01 12:38:04 -04:00
fde8f3d7a3 Finish project 2025-08-01 12:37:53 -04:00
260142445a Add serde 2025-07-30 19:08:09 -04:00
f8c7227c68 Add nanodb 2025-07-30 18:58:23 -04:00
0b3534348f Update playlist every hour 2025-07-30 18:58:14 -04:00
13fcf08dd6 Remove features
Too much work
2025-07-30 18:55:05 -04:00
26ff7fb0e5 Restrict tokio features 2025-06-23 21:29:35 -04:00
6a9eae5cae Add default, dotenvy, and container features 2025-06-23 21:27:15 -04:00
686853d769 Switch to rustls instead of openssl 2025-06-23 21:15:56 -04:00
e1abd2051c Fetch top tracks, add to playlist 2025-06-23 20:44:24 -04:00
9 changed files with 808 additions and 301 deletions

605
Cargo.lock generated
View File

@ -71,6 +71,60 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.75" version = "0.3.75"
@ -140,6 +194,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.41" version = "0.4.41"
@ -165,16 +225,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.10.1" version = "0.10.1"
@ -256,7 +306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.60.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -271,21 +321,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.1" version = "1.2.1"
@ -414,9 +449,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.2+wasi-0.2.4", "wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -471,6 +508,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.6.0" version = "1.6.0"
@ -483,6 +526,7 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"smallvec", "smallvec",
@ -491,19 +535,20 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-rustls"
version = "0.6.0" version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [ dependencies = [
"bytes", "http",
"http-body-util",
"hyper", "hyper",
"hyper-util", "hyper-util",
"native-tls", "rustls",
"rustls-pki-types",
"tokio", "tokio",
"tokio-native-tls", "tokio-rustls",
"tower-service", "tower-service",
"webpki-roots",
] ]
[[package]] [[package]]
@ -733,22 +778,24 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.27" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "maybe-async" name = "maybe-async"
version = "0.2.10" version = "0.2.10"
@ -766,6 +813,12 @@ version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"
@ -787,20 +840,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "native-tls" name = "nanodb"
version = "0.2.14" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" checksum = "6d01529f007f57affea74efcd9ed62aef7adf35e7c5d4de4967eb88fdff18a40"
dependencies = [ dependencies = [
"libc", "serde",
"log", "serde_json",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile", "tempfile",
"thiserror 1.0.69",
"tokio",
] ]
[[package]] [[package]]
@ -858,73 +907,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -943,12 +925,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.2" version = "0.1.2"
@ -958,6 +934,15 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.95"
@ -967,6 +952,61 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quinn"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.12",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
dependencies = [
"bytes",
"getrandom 0.3.3",
"lru-slab",
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@ -983,12 +1023,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "redox_syscall" name = "rand"
version = "0.5.12" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [ dependencies = [
"bitflags", "rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
] ]
[[package]] [[package]]
@ -1004,20 +1064,21 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-tls", "hyper-rustls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"native-tls",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types", "rustls-pki-types",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-native-tls", "tokio-rustls",
"tower", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
@ -1025,6 +1086,21 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -1093,10 +1169,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]] [[package]]
name = "rustix" name = "rustc-hash"
version = "1.0.7" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -1105,15 +1187,41 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "rustls"
version = "0.23.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.12.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [ dependencies = [
"web-time",
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-webpki"
version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.21"
@ -1135,44 +1243,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.219"
@ -1205,6 +1275,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_path_to_error"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
dependencies = [
"itoa",
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -1234,15 +1314,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.10" version = "0.4.10"
@ -1269,9 +1340,12 @@ dependencies = [
name = "spotify-top-songs-playlist" name = "spotify-top-songs-playlist"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum",
"dotenvy", "dotenvy",
"futures-util", "futures-util",
"nanodb",
"rspotify", "rspotify",
"serde",
"tokio", "tokio",
] ]
@ -1303,6 +1377,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.104" version = "2.0.104"
@ -1397,6 +1477,21 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "tinyvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.45.1" version = "1.45.1"
@ -1407,9 +1502,7 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -1427,12 +1520,12 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tokio-native-tls" name = "tokio-rustls"
version = "0.3.1" version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [ dependencies = [
"native-tls", "rustls",
"tokio", "tokio",
] ]
@ -1449,6 +1542,7 @@ dependencies = [
"tokio", "tokio",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
] ]
[[package]] [[package]]
@ -1487,6 +1581,7 @@ version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [ dependencies = [
"log",
"pin-project-lite", "pin-project-lite",
"tracing-core", "tracing-core",
] ]
@ -1518,6 +1613,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.4" version = "2.5.4"
@ -1535,12 +1636,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@ -1662,13 +1757,23 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "webbrowser" name = "webbrowser"
version = "1.0.5" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98"
dependencies = [ dependencies = [
"core-foundation 0.10.1", "core-foundation",
"jni", "jni",
"log", "log",
"ndk-context", "ndk-context",
@ -1678,13 +1783,22 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "webpki-roots"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.9" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1773,15 +1887,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.42.2" version = "0.42.2"
@ -1806,29 +1911,13 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc 0.52.6",
] ]
[[package]]
name = "windows-targets"
version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.2" version = "0.42.2"
@ -1841,12 +1930,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.2" version = "0.42.2"
@ -1859,12 +1942,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.2" version = "0.42.2"
@ -1877,24 +1954,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.2" version = "0.42.2"
@ -1907,12 +1972,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.2" version = "0.42.2"
@ -1925,12 +1984,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.2" version = "0.42.2"
@ -1943,12 +1996,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.2" version = "0.42.2"
@ -1961,12 +2008,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen-rt"
version = "0.39.0" version = "0.39.0"
@ -2006,6 +2047,26 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zerocopy"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zerofrom" name = "zerofrom"
version = "0.1.6" version = "0.1.6"

View File

@ -4,7 +4,11 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
axum = "0.8.4"
dotenvy = "0.15.7" dotenvy = "0.15.7"
futures-util = "0.3.31" futures-util = "0.3.31"
rspotify = { version = "0.14.0", features = ["cli"] } nanodb = "0.4.5"
tokio = { version = "1.45.1", features = ["full"] } rspotify = { version = "0.14.0", default-features = false, features = ["cli", "client-reqwest", "reqwest-rustls-tls"] }
serde = "1.0.219"
tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] }

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM rust:alpine AS builder
RUN rustup default nightly
RUN apk add --no-cache musl-dev
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && \
echo "fn main() {}" > src/main.rs && \
cargo build --release && \
rm -rf src
COPY src ./src
COPY html ./html
RUN cargo build --release
FROM scratch
COPY --from=builder /app/target/release/spotify-top-songs-playlist /spotify-top-songs-playlist
COPY --from=builder /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
ENTRYPOINT ["/spotify-top-songs-playlist"]

View File

@ -17,8 +17,6 @@
buildPkgs = with pkgs; [ buildPkgs = with pkgs; [
(rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)) (rust-bin.selectLatestNightlyWith (toolchain: toolchain.default))
pkg-config
openssl
]; ];
in in
{ {

19
html/callback.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/style.css">
<title>Spotify Top Songs Playlist | Setup Complete</title>
<link rel="icon" type="image/x-icon" href="https://spotify.com/favicon.ico">
</head>
<body>
<div class="center">
<h1>Spotify Top Songs Playlist</h1>
<h2>Setup Complete!</h1>
<p class="description">Look for a new playlist on Spotify</p>
</div>
</body>
</html>

22
html/error.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/style.css">
<title>Spotify Top Songs Playlist</title>
<link rel="icon" type="image/x-icon" href="https://spotify.com/favicon.ico">
</head>
<body>
<div class="center">
<h1>Spotify Top Songs Playlist</h1>
<h2>Error Occurred</h2>
<p class="description error">{{ ERROR_MESSAGE }}</p>
<a href="/" class="btn home">
<p>Return Home</p>
</a>
</div>
</body>
</html>

26
html/home.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/style.css">
<title>Spotify Top Songs Playlist</title>
<link rel="icon" type="image/x-icon" href="https://spotify.com/favicon.ico">
</head>
<body>
<div class="center">
<h1>Spotify Top Songs Playlist</h1>
<p class="description">Create an auto-updating playlist containing your recent top played tracks</p>
<a href="/authorize" class="btn spotify">
<img class="logo-img"
src="https://storage.googleapis.com/pr-newsroom-wp/1/2021/02/Spotify_Icon_RGB_White.png"
alt="Spotify Logo"
height="25"
/>
<p>Connect Spotify</p>
</a>
</div>
</body>
</html>

67
html/style.css Normal file
View File

@ -0,0 +1,67 @@
body {
background-color: #121212;
color: white;
font-family: sans-serif;
}
div.center {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
}
h1 h2 {
margin: 5px;
}
p.description {
color: grey;
margin: 15px;
}
p.error {
color: red;
}
a.btn {
color: white;
background-color: red;
border-radius: 10px;
padding: 5px;
margin: 5px;
text-decoration: none;
font-weight: bold;
display: flex;
align-items: center;
p {
margin: 5px;
}
}
a.btn:hover {
}
a.btn:active {
}
a.spotify {
background-color: #1ED760;
}
a.spotify:hover {
background-color: #18ac4d;
}
a.spotify:active {
background-color: #159643;
}
img.logo-img {
margin: 5px;
}

View File

@ -1,42 +1,325 @@
use rspotify::{ use rspotify::{
prelude::*, model::{PlaylistId, TimeRange, UserId},
scopes, AuthCodeSpotify, Credentials, OAuth, prelude::*, scopes, AuthCodeSpotify, ClientError, Credentials, OAuth
}; };
use futures_util::TryStreamExt;
use nanodb::nanodb::NanoDB;
use serde::{Serialize, Deserialize};
use axum::{
extract::Query, http::{header::CONTENT_TYPE, HeaderMap, StatusCode}, response::{Html, IntoResponse, Redirect}, routing::get, Router
};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Deserialize)]
pub struct CallbackParams {
code: String,
}
#[derive(Serialize, Deserialize)]
pub struct User {
id: String,
token: rspotify::Token,
}
impl User {
pub fn get_client(self) -> AuthCodeSpotify {
AuthCodeSpotify::from_token(self.token)
}
}
pub struct Config {
client_id: String,
client_secret: String,
redirect_uri: String,
playlist_name: String,
track_limit: usize,
database_path: String,
listen_on: String,
}
impl Config {
pub fn from_env() -> Result<Self, String> {
let client_id = std::env::var("SPOTIFY_CLIENT_ID")
.map_err(|e| format!("SPOTIFY_CLIENT_ID must be set in .env file or as an environment variable: {e}"))?;
let client_secret = std::env::var("SPOTIFY_CLIENT_SECRET")
.map_err(|e| format!("SPOTIFY_CLIENT_SECRET must be set in .env file or as an environment variable: {e}"))?;
let redirect_uri = std::env::var("SPOTIFY_REDIRECT_URI")
.unwrap_or("http://127.0.0.1:3000/callback".to_string());
let playlist_name = std::env::var("PLAYLIST_NAME")
.unwrap_or("My Current Top Tracks".to_string());
let track_limit: usize = std::env::var("PLAYLIST_TRACK_LIMIT")
.unwrap_or("50".to_string())
.parse()
.map_err(|e| format!("SPOTIFY_TRACK_LIMIT must be a valid number: {e}"))?;
let database_path = std::env::var("DATABASE_FILE")
.unwrap_or("data.json".to_string());
let listen_on = std::env::var("LISTEN_ON")
.unwrap_or("0.0.0.0:3000".to_string());
Ok(Config {
client_id,
client_secret,
redirect_uri,
playlist_name,
track_limit,
database_path,
listen_on,
})
}
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), String> {
dotenvy::dotenv()?; println!("Starting Spotify Top Songs Playlist...");
let client_id = std::env::var("SPOTIFY_CLIENT_ID") let _ = dotenvy::dotenv();
.expect("SPOTIFY_CLIENT_ID must be set in .env file or as an environment variable");
let client_secret = std::env::var("SPOTIFY_CLIENT_SECRET") let config = Config::from_env()
.expect("SPOTIFY_CLIENT_SECRET must be set in .env file or as an environment variable"); .map_err(|e| format!("Config error: {e}"))?;
let redirect_uri = std::env::var("SPOTIFY_REDIRECT_URI") let db = NanoDB::new_from(config.database_path.clone(), "{ \"users\": [] }")
.unwrap_or("http://127.0.0.1:8888/callback".to_string()); .map_err(|e| format!("Error initializing database: {e}"))?;
let playlist_name = std::env::var("PLAYLIST_NAME") let spotify_creds = Credentials::new(&config.client_id, &config.client_secret);
.unwrap_or("My Current Top Tracks".to_string());
let track_limit: usize = std::env::var("PLAYLIST_TRACK_LIMIT") let spotify_oauth = OAuth {
.unwrap_or("25".to_string()) redirect_uri: config.redirect_uri.clone(),
.parse() scopes: scopes!("playlist-read-private playlist-modify-private user-top-read"),
.expect("SPOTIFY_TRACK_LIMIT must be a valid number");
let credentials = Credentials::new(&client_id, &client_secret);
let oauth = OAuth {
redirect_uri,
scopes: scopes!(),
..Default::default() ..Default::default()
}; };
let spotify = AuthCodeSpotify::new(credentials, oauth); // TODO gross (?)
let db = Arc::new(Mutex::new(db));
let spotify_creds = Arc::new(spotify_creds);
let spotify_oauth = Arc::new(spotify_oauth);
let db2 = db.clone();
let spotify_creds2 = spotify_creds.clone();
let spotify_oauth2 = spotify_oauth.clone();
let playlist_name2 = config.playlist_name.clone();
let app = Router::new()
.route("/style.css", get(css))
.route("/", get(|| async { Html(include_str!("../html/home.html")) }))
.route("/authorize", get(move || authorize((*spotify_creds).clone(), (*spotify_oauth).clone())))
.route("/callback", get(move |callback_params: Query<CallbackParams>|
callback((*spotify_creds2).clone(), (*spotify_oauth2).clone(), db.clone(), callback_params, playlist_name2, config.track_limit)));
let listener = tokio::net::TcpListener::bind(&config.listen_on).await
.map_err(|e| format!("Error listening: {e}"))?;
println!("Listening on: {}", config.listen_on);
tokio::spawn(async move {
println!("Starting playlist update loop...");
update_loop(db2, config.playlist_name, config.track_limit).await
});
axum::serve(listener, app).await
.map_err(|e| format!("Error serving application: {e}"))?;
Err("axum stopped listening".to_string())
}
async fn css() -> impl IntoResponse {
let mut headers = HeaderMap::with_capacity(1);
headers.insert(CONTENT_TYPE, "text/css".parse().unwrap());
(StatusCode::OK, headers, include_str!("../html/style.css"))
}
fn error(message: &str) -> Html<String> {
let page = include_str!("../html/error.html").replace("{{ ERROR_MESSAGE }}", message);
Html(page)
}
async fn authorize(spotify_creds: Credentials, spotify_oauth: OAuth) -> axum::response::Response {
println!("Performing authorization..."); println!("Performing authorization...");
let url = spotify.get_authorize_url(false)?;
spotify.prompt_for_token(&url).await?;
let spotify = AuthCodeSpotify::new(spotify_creds, spotify_oauth);
match spotify.get_authorize_url(false) {
Ok(url) => {
println!("Redirect: {url}");
Redirect::to(&url).into_response()
},
Err(e) => error(&format!("Authorization Error: {e}")).into_response()
}
}
async fn callback(spotify_creds: Credentials, spotify_oauth: OAuth, db: Arc<Mutex<NanoDB>>, callback_params: Query<CallbackParams>, playlist_name: String, track_limit: usize) -> Html<String> {
println!("Callback triggered");
let spotify = AuthCodeSpotify::new(spotify_creds, spotify_oauth);
if let Err(e) = spotify.request_token(&callback_params.code).await {
return error(&format!("Error requesting Spotify token: {e}"))
}
let user_id = match spotify.current_user().await {
Ok(user) => user.id.id().to_string(),
Err(e) => return error(&format!("Error fetching user from database: {e}"))
};
let token = match spotify.get_token().lock().await.map(|token| token.clone()) {
Ok(Some(token)) => token,
Ok(None) => {
return error("No token found")
},
Err(_e) => {
return error("Error locking token")
}
};
let user = User {
id: user_id.to_string(),
token
};
{
let db_guard = db.lock().await;
let mut db = db_guard.update().await;
let db_users = match db.get("users") {
Ok(users) => users,
Err(e) => {
return error(&format!("Error getting users from database: {e}"))
}
};
let users: Vec<User> = match db_users.into() {
Ok(users) => users,
Err(e) => {
return error(&format!("Error deserializing users from database: {e}"))
}
};
for (i, user) in users.iter().enumerate() {
if user.id == user_id {
println!("user found!");
if let Err(e) = db_users.remove_at(i) {
return error(&format!("Error removing existing user from database: {e}"))
}
}
}
if let Err(e) = db_users.push(user) {
return error(&format!("Error adding user to database: {e}"))
}
}
let mut db_guard = db.lock().await;
if let Err(e) = db_guard.write().await {
return error(&format!("Error writing to database: {e}"))
}
if let Err(e) = top_songs_playlist(&spotify, &playlist_name, track_limit).await {
return error(&format!("Error updating playlist: {e}"));
}
Html(include_str!("../html/callback.html").to_string())
}
async fn update_loop(db: Arc<Mutex<NanoDB>>, playlist_name: String, track_limit: usize) -> ! {
println!("Starting update loop...");
let loop_sleep = || { tokio::time::sleep(std::time::Duration::from_secs(60 * 30)) };
let err_sleep = || { tokio::time::sleep(std::time::Duration::from_secs(30)) };
loop {
println!("Running update...");
let users: Vec<User> = {
let db_guard = db.lock().await;
let mut db_read = db_guard.read().await;
let users_field = match db_read.get("users") {
Ok(users) => users,
Err(e) => {
eprintln!("Error getting users: {e}");
err_sleep().await;
continue;
}
};
match users_field.into() {
Ok(users) => users,
Err(e) => {
eprintln!("Error deserializing users: {e}");
err_sleep().await;
continue;
}
}
};
let user_tasks = users.into_iter().map(|user| {
tokio::spawn(update_user(user, playlist_name.clone(), track_limit))
}).collect::<Vec<_>>();
for user_handle in user_tasks {
let _ = user_handle.await;
}
println!("Update finished.");
loop_sleep().await;
}
}
async fn update_user(user: User, playlist_name: String, track_limit: usize) {
let spotify = user.get_client();
if let Err(e) = spotify.auto_reauth().await {
eprintln!("Error enabling auto-reauth: {e}");
return;
}
if let Err(e) = top_songs_playlist(&spotify, &playlist_name, track_limit).await {
eprintln!("Error updating playlist: {e}");
}
}
async fn top_songs_playlist(spotify: &AuthCodeSpotify, playlist_name: &str, track_limit: usize) -> Result<(), ClientError> {
let mut top_tracks = spotify.current_user_top_tracks(Some(TimeRange::ShortTerm));
let mut top_track_ids = Vec::new();
while let Some(top_track) = top_tracks.try_next().await? && top_track_ids.len() < track_limit {
if let Some(track_id) = top_track.id {
top_track_ids.push(track_id);
}
}
let playlist_id = find_or_create_playlist(spotify, playlist_name, spotify.me().await?.id).await?;
spotify.playlist_replace_items(playlist_id, top_track_ids.into_iter().map(PlayableId::Track)).await?;
Ok(()) Ok(())
} }
async fn find_or_create_playlist<'a>(spotify: &AuthCodeSpotify,
playlist_name: &str, user_id: UserId<'_>) -> Result<PlaylistId<'a>, ClientError> {
let mut playlists = spotify.current_user_playlists();
while let Some(playlist) = playlists.try_next().await? {
if playlist.name == playlist_name {
return Ok(playlist.id);
}
}
let playlist = spotify.user_playlist_create(user_id, playlist_name, Some(false), Some(false), Some("")).await?;
Ok(playlist.id)
}