init commit

This commit is contained in:
Reid 2024-08-22 00:34:43 -07:00
commit 6899d676aa
Signed by: reidlab
GPG key ID: DAF5EAF6665839FD
15 changed files with 1490 additions and 0 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 4
[*.nix]
indent_size = 2

5
.env.example Normal file
View file

@ -0,0 +1,5 @@
#PLAYER_BUS="org.mpris.MediaPlayer2.mpv"
USERNAME="ILoveLastDotFm"
PASSWORD="lastfm4lyfe"
API_KEY="t8mb4e0kn9it150amaeezbcfe3iusy8o"
API_SECRET="jd47iz6h1u2kbnbfm8c763lzrj1o89h3"

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/target
/result
.env

945
Cargo.lock generated Normal file
View file

@ -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",
]

15
Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "lastfmpris"
description = "a rust application to scrobble your currently playing song on last.fm with mpris"
authors = [ "reidlab <reidlab@reidlab.pink>" ]
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"

39
README.md Normal file
View file

@ -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
```

96
flake.lock generated Normal file
View file

@ -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
}

58
flake.nix Normal file
View file

@ -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";
};
});
}

19
src/config.rs Normal file
View file

@ -0,0 +1,19 @@
use envconfig::Envconfig;
#[derive(Envconfig)]
pub struct Config {
#[envconfig(from = "PLAYER_BUS")]
pub player_bus: Option<String>,
#[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
}

15
src/lastfm.rs Normal file
View file

@ -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<Scrobbler> {
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);
}

35
src/main.rs Normal file
View file

@ -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(())
}

118
src/main_loop.rs Normal file
View file

@ -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<Duration>) -> 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(&current_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);
}
}

51
src/player.rs Normal file
View file

@ -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<Player> {
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);
}
}

77
src/track.rs Normal file
View file

@ -0,0 +1,77 @@
use mpris::Metadata;
#[derive(Debug, Default, PartialEq)]
pub struct Track {
artist: String,
title: String,
album: Option<String>,
}
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,
}
}
}

4
toolchain.toml Normal file
View file

@ -0,0 +1,4 @@
[toolchain]
channel = "nightly"
components = [ "rust-src" ]
profile = "minimal"