From 6899d676aada7c96fd2537588c02ac67db9aaed5 Mon Sep 17 00:00:00 2001 From: "Reid \"reidlab" Date: Thu, 22 Aug 2024 00:34:43 -0700 Subject: [PATCH] init commit --- .editorconfig | 9 + .env.example | 5 + .gitignore | 4 + Cargo.lock | 945 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 15 + README.md | 39 ++ flake.lock | 96 +++++ flake.nix | 58 +++ src/config.rs | 19 + src/lastfm.rs | 15 + src/main.rs | 35 ++ src/main_loop.rs | 118 ++++++ src/player.rs | 51 +++ src/track.rs | 77 ++++ toolchain.toml | 4 + 15 files changed, 1490 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/config.rs create mode 100644 src/lastfm.rs create mode 100644 src/main.rs create mode 100644 src/main_loop.rs create mode 100644 src/player.rs create mode 100644 src/track.rs create mode 100644 toolchain.toml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3e71bac --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 4 + +[*.nix] +indent_size = 2 \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fc88e23 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +#PLAYER_BUS="org.mpris.MediaPlayer2.mpv" +USERNAME="ILoveLastDotFm" +PASSWORD="lastfm4lyfe" +API_KEY="t8mb4e0kn9it150amaeezbcfe3iusy8o" +API_SECRET="jd47iz6h1u2kbnbfm8c763lzrj1o89h3" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d5f606 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/result + +.env \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..03b5af1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,945 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "attohttpc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57d6e7a84f33ff3316e97af3180fe7f86597a6a60161c0be70c0e45f382620" +dependencies = [ + "flate2", + "http", + "log", + "native-tls", + "serde", + "serde_urlencoded", + "url", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote 1.0.36", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "derive_is_enum_variant" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ac8859845146979953797f03cc5b282fb4396891807cdb3d04929a88418197" +dependencies = [ + "heck", + "quote 0.3.15", + "syn 0.11.11", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "enum-kinds" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e40a16955681d469ab3da85aaa6b42ff656b3c67b52e1d8d3dd36afe97fd462" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "envconfig" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea81cc7e21f55a9d9b1efb6816904978d0bfbe31a50347cb24b2e75564bcac9b" +dependencies = [ + "envconfig_derive", +] + +[[package]] +name = "envconfig_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfca278e5f84b45519acaaff758ebfa01f18e96998bc24b8f1b722dd804b9bf" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "flate2" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +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]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "from_variants" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e859c8f2057687618905dbe99fc76e836e0a69738865ef90e46fc214a41bbf2" +dependencies = [ + "from_variants_impl", +] + +[[package]] +name = "from_variants_impl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a5e644a80e6d96b2b4910fa7993301d7b7926c045b475b62202b20a36ce69e" +dependencies = [ + "darling", + "proc-macro2", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lastfmpris" +version = "0.1.0" +dependencies = [ + "anyhow", + "dotenvy", + "envconfig", + "mpris", + "rustfm-scrobble-proxy", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mpris" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cef955a7826b1e00e901a3652e7a895abd221fb4ab61547e7d0e4c235d7feb" +dependencies = [ + "dbus", + "derive_is_enum_variant", + "enum-kinds", + "from_variants", + "thiserror", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +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 1.0.36", + "syn 2.0.72", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustfm-scrobble-proxy" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe604e5881fb980b570695795440cdba1abdbe13d1cc4858923ce11b4fc912be" +dependencies = [ + "attohttpc", + "md5", + "serde", + "serde_json", + "wrapped-vec", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "syn 2.0.72", +] + +[[package]] +name = "serde_json" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "unicode-ident", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "syn 2.0.72", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "syn 2.0.72", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wrapped-vec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85e08702c1e919669e1e90213c9c75ea4bb689d0f3970347e2b37c04600b4e5" +dependencies = [ + "proc-macro2", + "quote 1.0.36", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d8ca9f3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "lastfmpris" +description = "a rust application to scrobble your currently playing song on last.fm with mpris" +authors = [ "reidlab " ] +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.86" +dotenvy = "0.15.7" +envconfig = "0.10.0" +mpris = "2.0.1" +rustfm-scrobble-proxy = "2.0.0" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c48b96c --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# lastfmpris + +a rust application to scrobble your currently playing song on last.fm with mpris + +## limitations + +unfortunately, because of how most mpris players work, the [Track_Id](https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html#Simple-Type:Track_Id) (unique identifier for every song in the tracklist) object is very rarely supported, or, if supported, implemented incorrectly (looking at you, [Cider](https://cider.sh/)) meaning it is impossible to tell if the track has been played twice in a row. this means you cannot have succssive scrobbles of the same song, sorry! besides this, i'd say the program is feature complete. + +## configuration + +copy the values from `.env.example` into a `.env` file and configure your api keys, and the api keys are available [here](https://www.last.fm/login?next=/api/account/create) + +you also have the option to define your mpris bus name, which will instead wait for your specified player instead of waiting for the first active player + +## how to install + +*if you're on a setup that uses nix, you can just use `nix build` or the provided devshell to build the application.* + +first, clone repository: + +```sh +git clone https://git.reidlab.pink/reidlab/lastfmpris && cd lastfmpris +``` + +second, obtain dependencies (this will vary on your package manager, here, alpine is used) + +```sh +sudo apk add install pkgconf openssl dbus +``` + +finally, run/build + +```sh +cargo run +``` + +```sh +cargo build --release +``` \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d7212ff --- /dev/null +++ b/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1721379653, + "narHash": "sha256-8MUgifkJ7lkZs3u99UDZMB4kbOxvMEXQZ31FO3SopZ0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1d9c2c9b3e71b9ee663d11c5d298727dace8d374", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1718428119, + "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1721528458, + "narHash": "sha256-uruH/EV8Rpa/CRxn8CiMzhoA6tJe8qO5c8NdgP1g0rM=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "0b2b2da1dad1c675c45d9e23c75674de969de83b", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d7eaf09 --- /dev/null +++ b/flake.nix @@ -0,0 +1,58 @@ +{ + description = "lastfmpris"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay.url = "github:oxalica/rust-overlay"; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay, }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [rust-overlay.overlays.default]; + }; + package = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package; + rustPlatform = pkgs.makeRustPlatform { + cargo = pkgs.cargo; + rustc = pkgs.rustc; + }; + toolchain = pkgs.rust-bin.fromRustupToolchainFile ./toolchain.toml; + in + rec { + packages = flake-utils.lib.flattenTree { + lastfmpris = rustPlatform.buildRustPackage { + pname = package.name; + inherit (package) version; + + # uncomment this and let the build fail, then get the current hash + # very scuffed but endorsed! + #cargoSha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + cargoSha256 = "sha256-2bDHxZc5Zg7jmA8BenSbTF3fBJEB7QDEVBQLkI8qyRc="; + + buildInputs = with pkgs; [ pkg-config ]; + + nativeBuildInputs = with pkgs; [ openssl dbus ]; + + src = ./.; + }; + }; + + defaultPackage = packages.lastfmpris; + + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ pkg-config ]; + + nativeBuildInputs = with pkgs; [ openssl dbus ]; + + packages = with pkgs; [ + toolchain + rust-analyzer-unwrapped + ]; + + RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; + }; + }); +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1af799b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,19 @@ +use envconfig::Envconfig; + +#[derive(Envconfig)] +pub struct Config { + #[envconfig(from = "PLAYER_BUS")] + pub player_bus: Option, + + #[envconfig(from = "USERNAME")] + pub username: String, + + #[envconfig(from = "PASSWORD")] + pub password: String, + + #[envconfig(from = "API_KEY")] + pub api_key: String, + + #[envconfig(from = "API_SECRET")] + pub api_secret: String +} \ No newline at end of file diff --git a/src/lastfm.rs b/src/lastfm.rs new file mode 100644 index 0000000..3361d43 --- /dev/null +++ b/src/lastfm.rs @@ -0,0 +1,15 @@ +use crate::config::Config; +use anyhow::{Context, Result}; +use rustfm_scrobble_proxy::Scrobbler; +use tracing::info; + +pub fn get_scrobbler(config: &Config) -> Result { + let mut scrobbler = Scrobbler::new(&config.api_key, &config.api_secret); + scrobbler + .authenticate_with_password(&config.username, &config.password) + .context("failed to authenticate")?; + + info!(" successfully authenticated"); + + return Ok(scrobbler); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6df96f8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,35 @@ +#![feature(duration_constructors)] + +mod config; +mod lastfm; +mod main_loop; +mod player; +mod track; + +use crate::config::Config; +use crate::lastfm::get_scrobbler; +use anyhow::{Context, Result}; +use envconfig::Envconfig; +use tracing::{debug, Level}; + +fn main() -> Result<()> { + dotenvy::dotenv()?; + tracing_subscriber::fmt() + .with_max_level(if cfg!(debug_assertions) { Level::DEBUG } else { Level::INFO }) + .init(); + + debug!("{} by {}, {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_AUTHORS"), + env!("CARGO_PKG_DESCRIPTION") + ); + + let config = Config::init_from_env() + .context("failed to initiate configuration from .env")?; + let scrobbler = get_scrobbler(&config) + .context("failed to create scrobbler")?; + + main_loop::start(&config, scrobbler)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/main_loop.rs b/src/main_loop.rs new file mode 100644 index 0000000..7b01af0 --- /dev/null +++ b/src/main_loop.rs @@ -0,0 +1,118 @@ +use crate::config::Config; +use crate::player; +use crate::track::Track; +use anyhow::{Context, Result}; +use mpris::PlaybackStatus; +use std::thread; +use std::time::{Duration, Instant}; +use rustfm_scrobble_proxy::{Scrobble, Scrobbler}; +use tracing::info; + +const INTERVAL: Duration = Duration::from_millis(1000); + +// see https://www.last.fm/api/scrobbling#scrobble-requests +// do not scrobble songs over 30 seconds, scrobble them if we are 4 minutes through, +// or halfway through (whichever occurs first) +const MIN_SONG_LENGTH: Duration = Duration::from_secs(30); +const MIN_SCROBBLE_POSITION: Duration = Duration::from_mins(4); +fn do_we_scrobble(position: Duration, length: Option) -> bool { + match length { + Some(length) if length <= Duration::from_secs(30) => false, + Some(length) => position >= MIN_SCROBBLE_POSITION || position >= length / 2, + // we can't exactly appeal the spec here... but we don't also want only songs over 30 seconds to work + // because of this, it is a much safer option to use the min song length + None => position >= MIN_SONG_LENGTH, + } +} + +pub fn start(config: &Config, scrobbler: Scrobbler) -> Result<()> { + info!(" looking for an active mpris player"); + + let mut player = player::wait_for_player(config)?; + + info!(" found an active player: {}", player.identity()); + + let mut previously_logged_track = Track::default(); + + let mut timer = Instant::now(); + let mut current_play_time = Duration::from_secs(0); + let mut scrobbled_current_song = false; + + loop { + if !player::is_active(&player) { + info!(" player {} stopped, searching for an active mpris player", player.identity()); + + player = player::wait_for_player(config) + .context("failed to find player")?; + + info!(" found an active player: {}", player.identity()); + } + + let playback_status = player + .get_playback_status() + .context("failed to retrieve playback status")?; + + match playback_status { + PlaybackStatus::Playing => {} + _ => { + thread::sleep(INTERVAL); + continue; + } + } + + let metadata = player + .get_metadata() + .context("failed to get metadata")?; + + let current_track = Track::from_metadata(&metadata); + + let length = metadata + .length() + .and_then(|length| if length.is_zero() { None } else { Some(length) }); + + if current_track == previously_logged_track { + if !scrobbled_current_song && do_we_scrobble(current_play_time, length) { + let scrobble = Scrobble::new(current_track.artist(), current_track.title(), current_track.album()); + scrobbler.scrobble(&scrobble) + .context("failed to scrobble")?; + + info!(" scrobble submitted sucessfully"); + + scrobbled_current_song = true + } else if length + .map(|length| current_play_time >= length) + .unwrap_or(false) + { + current_play_time = Duration::from_secs(0); + scrobbled_current_song = false; + } + + current_play_time += timer.elapsed(); + timer = Instant::now(); + } else { + previously_logged_track.clone_from(¤t_track); + + timer = Instant::now(); + current_play_time = Duration::from_secs(0); + scrobbled_current_song = false; + + info!( + " now playing {} by {}{}", + current_track.title(), + current_track.artist(), + match current_track.album() { + Some(album) => format!(" on {}", album), + None => String::new(), + } + ); + + let scrobble = Scrobble::new(current_track.artist(), current_track.title(), current_track.album()); + scrobbler.now_playing(&scrobble) + .context("failed to update now playing status")?; + + info!(" now playing status updated successfully"); + } + + thread::sleep(INTERVAL); + } +} diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..7cff068 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,51 @@ +use crate::config::Config; +use anyhow::{anyhow, Result, Context}; +use mpris::{Player, PlayerFinder, PlaybackStatus}; +use std::thread; +use std::time::Duration; + +const INTERVAL: Duration = Duration::from_millis(1000); + +const MPRIS_BUS_PREFIX: &str = "org.mpris.MediaPlayer2."; + +pub fn is_active(player: &Player) -> bool { + if !player.is_running() { return false; } + + matches!(player.get_playback_status(), Ok(PlaybackStatus::Playing)) +} + +pub fn is_whitelisted(config: &Config, player: &Player) -> bool { + if Some(player.bus_name()) == config.player_bus.as_deref() { + return true + } else if config.player_bus.is_none() { + return player.bus_name().starts_with(MPRIS_BUS_PREFIX) + } else { + return false + } +} + +pub fn wait_for_player(config: &Config) -> Result { + let finder = PlayerFinder::new() + .map_err(|err| anyhow!("{}", err)) + .context("failed to connect to d-bus")?; + + loop { + let players = match finder.iter_players() { + Ok(players) => players, + _ => { + thread::sleep(INTERVAL); + continue; + } + }; + + for player in players { + if let Ok(player) = player { + if is_active(&player) && is_whitelisted(config, &player) { + return Ok(player); + } + } + } + + thread::sleep(INTERVAL); + } +} \ No newline at end of file diff --git a/src/track.rs b/src/track.rs new file mode 100644 index 0000000..2e9c3f3 --- /dev/null +++ b/src/track.rs @@ -0,0 +1,77 @@ +use mpris::Metadata; + +#[derive(Debug, Default, PartialEq)] +pub struct Track { + artist: String, + title: String, + album: Option, +} + +impl Track { + pub fn artist(&self) -> &str { + &self.artist + } + + pub fn title(&self) -> &str { + &self.title + } + + pub fn album(&self) -> Option<&str> { + self.album.as_deref() + } + + pub fn new(artist: &str, title: &str, album: Option<&str>) -> Self { + Self { + artist: artist.to_owned(), + title: title.to_owned(), + album: album.and_then(|album| { + if !album.is_empty() { + Some(album.to_owned()) + } else { + None + } + }), + } + } + + pub fn clear(&mut self) { + self.artist.clear(); + self.title.clear(); + self.album.take(); + } + + pub fn clone_from(&mut self, other: &Self) { + self.artist.clone_from(&other.artist); + self.title.clone_from(&other.title); + self.album.clone_from(&other.album); + } + + pub fn from_metadata(metadata: &Metadata) -> Self { + // these unknown checks are required or else scrobbling will return a bad request + // last.fm can handle unknown artist kinda well! + let artist = metadata + .artists() + .as_ref() + .and_then(|artists| artists.first().copied()) + .unwrap_or("Unknown Artist") + .to_owned(); + + // unknown song on the other hand.. well, it just scrobbles the song "Unknown Song" + // this is probably better than getting a bad request and the program crashing :3 + let title = metadata.title().unwrap_or("Unknown Song").to_owned(); + + let album = metadata.album_name().and_then(|album| { + if !album.is_empty() { + Some(album.to_owned()) + } else { + None + } + }); + + Self { + artist, + title, + album, + } + } +} \ No newline at end of file diff --git a/toolchain.toml b/toolchain.toml new file mode 100644 index 0000000..fc0b1fe --- /dev/null +++ b/toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src" ] +profile = "minimal"