diff --git a/.gitignore b/.gitignore index c371942..5fa16cd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.vscode .env +/resources diff --git a/Cargo.lock b/Cargo.lock index 5a8ba3e..be3a161 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -81,6 +90,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -126,6 +141,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -185,12 +201,57 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "digest" version = "0.10.7" @@ -284,6 +345,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + [[package]] name = "futures-sink" version = "0.3.29" @@ -303,8 +370,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", + "futures-io", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -337,6 +406,21 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "git2" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +dependencies = [ + "bitflags 2.4.1", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "h2" version = "0.3.22" @@ -348,14 +432,39 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap", + "http 0.2.11", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "h2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.1.0", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.3" @@ -368,10 +477,10 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "headers-core", - "http", + "http 0.2.11", "httpdate", "mime", "sha1", @@ -383,7 +492,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.11", ] [[package]] @@ -392,6 +501,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.11" @@ -403,6 +518,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -410,7 +536,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -436,9 +585,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.22", + "http 0.2.11", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -451,16 +600,90 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", - "hyper", + "futures-channel", + "futures-util", + "h2 0.4.4", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.11", + "hyper 0.14.27", + "rustls 0.21.10", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.23.10", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -486,6 +709,12 @@ dependencies = [ "cc", ] +[[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" @@ -496,6 +725,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -503,7 +743,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "ip2location" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c1511093b9295abf32d5b63cfefa2c59cd237fde31c339fabf9710a54c16c69" +dependencies = [ + "memmap", + "serde", + "serde_json", + "serde_with", ] [[package]] @@ -518,18 +771,31 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "jonas_jones-api" -version = "0.2.1" +version = "0.4.1" dependencies = [ "chrono", "dotenv", + "git2", + "ip2location", "lastfm", "log", "parking_lot", - "reqwest", + "regex", + "reqwest 0.12.5", "serde", "serde_json", + "sha2", "tokio", "toml", "warp", @@ -546,16 +812,16 @@ dependencies = [ [[package]] name = "lastfm" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2b8e3a1ad7d64fae5e5542beccb5596402aafeb69c7259e3baa2dfdfa364e1" +checksum = "6e1c7d19b3dabcb2ccef2ffe389604248cdcbc063e3455e9079992e4ec336158" dependencies = [ "async-stream", "chrono", "dotenv", "lazy_static", "rand", - "reqwest", + "reqwest 0.11.22", "serde", "serde_json", "thiserror", @@ -578,6 +844,46 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libgit2-sys" +version = "0.17.0+1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -606,6 +912,16 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "mime" version = "0.3.17" @@ -651,7 +967,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 0.2.11", "httparse", "log", "memchr", @@ -678,6 +994,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.17" @@ -823,6 +1145,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -886,22 +1214,95 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "reqwest" version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.22", + "http 0.2.11", + "http-body 0.4.5", + "hyper 0.14.27", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.4", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.27.2", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -910,9 +1311,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -921,7 +1324,21 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.52.0", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", ] [[package]] @@ -943,13 +1360,75 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.5", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -979,6 +1458,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -1054,6 +1543,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1065,6 +1584,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1085,9 +1615,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1115,6 +1645,18 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.39" @@ -1126,6 +1668,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -1180,6 +1728,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1235,6 +1814,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.10", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -1299,13 +1899,35 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -1359,7 +1981,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", "rand", @@ -1425,6 +2047,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -1473,15 +2101,15 @@ dependencies = [ "futures-channel", "futures-util", "headers", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.27", "log", "mime", "mime_guess", "multer", "percent-encoding", "pin-project", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "scoped-tls", "serde", "serde_json", @@ -1576,6 +2204,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "winapi" version = "0.3.9" @@ -1757,3 +2391,19 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 81222a9..17ea181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jonas_jones-api" -version = "0.2.1" +version = "0.4.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,9 +11,13 @@ parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35", features = ["macros"] } dotenv = "0.15.0" -lastfm = "0.7.0" +lastfm = "0.10.0" log = "0.4.20" chrono = "0.4.31" toml = "0.8.8" -reqwest = { version = "0.11.22", features = ["json"] } +reqwest = { version = "0.12.5", features = ["json", "blocking"] } serde_json = "1.0.108" +regex = "1" +git2 = "0.19.0" +ip2location = "0.5.0" +sha2 = "0.10.8" diff --git a/README.md b/README.md index 5cbba9f..4c7b7b1 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,34 @@ export API_IP={ip_address} export LASTFM_API_KEY={lastfm_api_key} export LASTFM_API_SECRET={lastfm_api_secret} ``` + +## Docker Compose + +`docker-compose.yaml` (folder paths need adjusting): +```yaml +version: '3.8' +services: + arch-linux: + image: archlinux:latest + container_name: jonas_jones-api + ports: + - "3030:3030" + volumes: + - /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api + - /home/jonas_jones/.config/rclone/:/root/.config/rclone/ + command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"] +``` + +run container: +```sh +docker-compose up -d +``` + +## Roadmap + +- analytics backend. track request origin through IP from header (store IP hash, region and time) +- rewrite all scripts in rust +- DB implementation for projects, kcomebacks, minecraft mod versions +- session backend, auth token system +- implementation for dashboard front-end with analytics/config +- complete minecraft mod implementation \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3a3650d --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +version: '3.8' +services: + arch-linux: + image: archlinux:latest + container_name: jonas_jones-api + ports: + - "3030:3030" + volumes: + - /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api + - /home/jonas_jones/.config/rclone/:/root/.config/rclone/ + command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce908f5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +requests +datetime +python-dotenv +spotipy +praw +spotipy +pylast +typing +markdown \ No newline at end of file diff --git a/src/iplookup.rs b/src/iplookup.rs new file mode 100644 index 0000000..ddfafba --- /dev/null +++ b/src/iplookup.rs @@ -0,0 +1,14 @@ +use ip2location::{DB, Record}; + +pub fn ip_lookup(ip: &str) -> String { + let database_path = "resources/IP2LOCATION-LITE-DB5.IPV6.BIN/IP2LOCATION-LITE-DB5.IPV6.BIN"; + + let mut db = DB::from_file(database_path).unwrap(); + + let geo_info = db.ip_lookup(ip.parse().unwrap()).unwrap(); + + let record = if let Record::LocationDb(rec) = geo_info { + Some(rec) + } else { None }; + return record.unwrap().country.unwrap().short_name.to_string(); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 86eb624..e8a62ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,50 @@ use dotenv::dotenv; +use tokio::time::{sleep, Duration}; +use v1::{run_sync_all_command}; +use std::fs; pub mod v1; pub mod logger; pub mod tools; pub mod server; pub mod error_responses; +pub mod iplookup; +pub mod request_logger; pub use logger::Logger; pub use tools::parse_ip; +async fn periodic_script_runner() { + loop { + Logger::info("Running periodic scripts..."); + // Run all Functions + let _ = run_sync_all_command(); + + // Sleep for 6 hours + sleep(Duration::from_secs(6 * 60 * 60)).await; + } +} + #[tokio::main(flavor = "current_thread")] async fn main() { - // load .env file + // Load .env file dotenv().ok(); - server::serve().await; + // Start the api + let server_task = tokio::spawn(async { + server::serve().await; + }); + + // periodic script runner + let second_task = tokio::spawn(async { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + v1::run_setup().unwrap(); + }; + periodic_script_runner().await; + }); + + // Wait for both tasks to complete + let _ = tokio::try_join!(server_task, second_task); } diff --git a/src/request_logger.rs b/src/request_logger.rs new file mode 100644 index 0000000..e742d4a --- /dev/null +++ b/src/request_logger.rs @@ -0,0 +1,71 @@ +use chrono::Utc; +use reqwest::Client; +use reqwest::blocking::get; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::fs::OpenOptions; +use std::io::{BufReader, BufWriter}; +use warp::http::Method; +use std::path::Path; + +#[derive(Serialize, Deserialize)] +struct RequestLog { + timestamp: String, + method: String, + pathname: String, + ip_country_code: String, + ip_hash: String, +} + +async fn get_ip_country_code(ip: &str) -> Result> { + let url = format!("http://ip-api.com/json/{}", ip); + let response: Value = get(&url)?.json()?; + if let Some(country_code) = response["countryCode"].as_str() { + Ok(country_code.to_string()) + } else { + Err("Could not fetch country code".into()) + } +} + +async fn hash_ip(ip: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(ip); + format!("{:x}", hasher.finalize()) +} + +pub async fn log_request(client: &Client, ip: &str, pathname: &str, method: &Method, file_path: &str) -> Result<(), Box> { + let method_str = method.as_str(); + let timestamp = Utc::now().to_rfc3339(); + let ip_country_code = get_ip_country_code(ip).await?; + let ip_hash = hash_ip(ip).await; + + let log_entry = RequestLog { + timestamp, + method: method_str.to_string(), + pathname: pathname.to_string(), + ip_country_code, + ip_hash, + }; + + let path = Path::new(file_path); + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path)?; + + let mut logs: Vec = if path.exists() { + let reader = BufReader::new(&file); + serde_json::from_reader(reader)? + } else { + Vec::new() + }; + + logs.push(log_entry); + + let writer = BufWriter::new(&file); + serde_json::to_writer_pretty(writer, &logs)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 7aa133e..51ec63b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,13 +1,19 @@ use std::convert::Infallible; +use std::net::SocketAddr; use std::env; -use reqwest::StatusCode; +use lastfm::reqwest::StatusCode; +use warp::filters::path::FullPath; +use warp::http::Method; use warp::Filter; use warp::reply::Reply; +use reqwest::Client; + use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError}; use crate::v1::get_v1_routes; -use crate::{Logger, parse_ip}; +use crate::{parse_ip, request_logger, Logger}; +use crate::iplookup::ip_lookup; pub async fn serve() { @@ -20,9 +26,38 @@ pub async fn serve() { let favicon = warp::path("favicon.ico").and(warp::fs::file("./src/favicon.png")); + // /status => 200 OK + let status = warp::path("status") + .map(|| warp::reply()); + + // Middleware filter to log request details + let log_request = warp::any() + .and(warp::method()) + .and(warp::path::full()) + .and(warp::addr::remote()) + .and(warp::header::optional::("x-forwarded-for")) + .map(|method: Method, path: FullPath, addr: Option, fwd_for: Option| { + let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown"))); + let path_str = path.as_str().to_string(); // Convert to owned String + let method_clone = method.clone(); + let method_str = method_clone.clone().as_str().to_string(); + let client_ip_clone = client_ip.clone(); // Clone for use outside the async block + let path_str_clone = path_str.clone(); + + /*tokio::spawn(async move { + let client = Client::new(); + if let Err(e) = request_logger::log_request(&client, &client_ip, &path_str, &method_clone, "requests.json").await { + eprintln!("Failed to log request: {:?}", e); + } + });*/ + + Logger::info(&format!("{} {} from {} ({})", method_str, path_str_clone, ip_lookup(&client_ip_clone), client_ip_clone)); + }); + // GET (any) => reply with return from handle_path - let routes = favicon.or(get_v1_routes()) - .recover(handle_rejection); + let routes = log_request + .clone().untuple_one().and(favicon.or(status.or(get_v1_routes()) + .recover(handle_rejection))); async fn handle_rejection(err: warp::Rejection) -> Result { @@ -43,7 +78,7 @@ pub async fn serve() { message: message.into(), }); - Ok(warp::reply::with_status(json, code)) + Ok(warp::reply::with_status(json, StatusCode::from_u16(code.as_u16()).unwrap())) } diff --git a/src/v1/analytics/mod.rs b/src/v1/analytics/mod.rs new file mode 100644 index 0000000..98bbf68 --- /dev/null +++ b/src/v1/analytics/mod.rs @@ -0,0 +1,8 @@ +use warp::Filter; + +pub fn get_analytics_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("analytics")) + .and((warp::path("logcdnrequest").map(|| "Please refer to the wiki at https://wiki.jonasjones.dev/Api/")) + .or(warp::path("ping").map(|| "pong")) + .or(warp::path("version").map(|| warp::reply::json(&[option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")])))) +} \ No newline at end of file diff --git a/src/v1/auth/mod.rs b/src/v1/auth/mod.rs new file mode 100644 index 0000000..0a37d4f --- /dev/null +++ b/src/v1/auth/mod.rs @@ -0,0 +1,7 @@ +use warp::Filter; + +pub fn get_auth_routes() -> impl warp::Filter + Clone { + warp::path("v1").and(warp::path("auth")) + .and((warp::path("requestsession").and(warp)) + .or(warp::path("login"))) +} \ No newline at end of file diff --git a/src/v1/mod.rs b/src/v1/mod.rs index cd12a16..199d74d 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -2,13 +2,16 @@ mod builtin; mod debug; mod kcomebacks; mod projects; -mod updates; +mod run; + +pub use run::setup as run_setup; +pub use run::run_sync_all_command; pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes; pub use projects::get_project_routes as get_v1_project_routes; -pub use updates::get_updates_routes as get_v1_updates_routes; +pub use run::get_run_routes as get_v1_updates_routes; use warp::Filter; diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs index 61daf55..777e69e 100644 --- a/src/v1/projects/mod.rs +++ b/src/v1/projects/mod.rs @@ -15,7 +15,8 @@ pub fn get_project_routes() -> impl warp::Filter impl warp::Filter + Clone { + warp::path("v1").and(warp::path("run")) + // update/kcomebacks + // update/projects + // update/makediscography?artists=artist1,artist2,artist3 + // update/synclikedsongs + .and((warp::path("kcomebacks").and_then(update_kcomebacks)) + .or(warp::path("projects").and_then(update_projects)) + .or(warp::path("makediscography").map(||"Not implemented yet")) + .or(warp::path("synclikedsongs").and_then(sync_liked_songs)) + .or(warp::path("sync_all").and_then(sync_all)) + ) +} + +async fn update_kcomebacks() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_kcomebacks_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "updating..."}))) + +} + +async fn update_projects() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_projects_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "updating..."}))) + +} + +async fn sync_liked_songs() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_likedsongs_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "updating..."}))) + +} + +async fn sync_all() -> Result { + // check if the local repository exists, if not, clone it + if !fs::metadata("./resources/turbo_octo_potato").is_ok() { + setup().unwrap(); + }; + + if let Err(err) = run_sync_all_command() { + // Handle the error here + eprintln!("Error: {}", err); + // Return an appropriate response or error + return Err(warp::reject::custom(InternalServerError)); + } + + Ok(warp::reply::json(&json!({"status": "syncing..."}))) + +} + +pub fn setup() -> Result<(), git2::Error> { + let repository_url = "https://github.com/JonasunderscoreJones/turbo-octo-potato.git"; + let local_directory = "resources/turbo_octo_potato"; + + git2::Repository::clone(repository_url, local_directory)?; + + Ok(()) +} + +// fn run_command() -> Result<(), std::io::Error> { +// let (tx, mut rx) = mpsc::channel(1); + +// task::spawn_blocking(move || { +// let mut child = Command::new("python3") +// .arg(&py_file) +// .arg(&args) +// .current_dir("resources/turbo_octo_potato") +// .stdout(Stdio::piped()) +// .spawn() +// .expect("failed to execute child"); + +// let stdout = child.stdout.as_mut().unwrap(); + +// let mut reader = std::io::BufReader::new(stdout); + +// let mut line = String::new(); + +// loop { +// let len = reader.read_line(&mut line).unwrap(); +// if len == 0 { +// break; +// } +// tx.blocking_send(line.clone()).unwrap(); +// line.clear(); +// } + +// child.wait().unwrap(); +// }); + +// task::spawn(async move { +// while let Some(line) = rx.recv().await { +// println!("{}", line); +// } +// }); + +// Ok(()) +// } + +// run_command with python file and args as parameters + + +pub fn run_kcomebacks_command() -> Result<(), std::io::Error> { + // let (tx, mut rx) = mpsc::channel(1); + + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("rpopfetch.py") + .arg("--cdn") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + // let stdout = child.stdout.as_mut().unwrap(); + + // let mut reader = std::io::BufReader::new(stdout); + + // let mut line = String::new(); + + // loop { + // let len = reader.read_line(&mut line).unwrap(); + // if len == 0 { + // break; + // } + // tx.blocking_send(line.clone()).unwrap(); + // line.clear(); + // } + + child.wait().unwrap(); + }); + + // task::spawn(async move { + // while let Some(line) = rx.recv().await { + // Logger::info(&format!("[/v1/kcomebacks/update]: {}", line)); + // } + // }); + Logger::info("Updating kcomebacks..."); + + Ok(()) +} + +pub fn run_projects_command() -> Result<(), std::io::Error> { + // let (tx, mut rx) = mpsc::channel(1); + + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("update_projects.py") + .arg("--cdn") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + // let stdout = child.stdout.as_mut().unwrap(); + + // let mut reader = std::io::BufReader::new(stdout); + + // let mut line = String::new(); + + // loop { + // let len = reader.read_line(&mut line).unwrap(); + // if len == 0 { + // break; + // } + // tx.blocking_send(line.clone()).unwrap(); + // line.clear(); + // } + + child.wait().unwrap(); + }); + + // task::spawn(async move { + // while let Some(line) = rx.recv().await { + // Logger::info(&format!("[/v1/projects/update]: {}", line)); + // } + // }); + Logger::info("Updating projects..."); + + Ok(()) +} + +pub fn run_likedsongs_command() -> Result<(), std::io::Error> { + // let (tx, mut rx) = mpsc::channel(1); + + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("likedsongsync2.py") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + // let stdout = child.stdout.as_mut().unwrap(); + + // let mut reader = std::io::BufReader::new(stdout); + + // let mut line = String::new(); + + // loop { + // let len = reader.read_line(&mut line).unwrap(); + // if len == 0 { + // break; + // } + // tx.blocking_send(line.clone()).unwrap(); + // line.clear(); + // } + + child.wait().unwrap(); + }); + + // task::spawn(async move { + // while let Some(line) = rx.recv().await { + // Logger::info(&format!("[/v1/synclikedsongs]: {}", line)); + // } + // }); + Logger::info("Syncing liked songs..."); + + Ok(()) +} + +pub fn run_sync_all_command() -> Result<(), std::io::Error> { + task::spawn_blocking(move || { + let mut child = Command::new("python3") + .arg("script_interval_runner.py") + .current_dir("resources/turbo_octo_potato") + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + child.wait().unwrap(); + }); + Logger::info("Running all Sync Scripts..."); + + Ok(()) +} \ No newline at end of file diff --git a/src/v1/updates/minecraft/mods/mod.rs b/src/v1/updates/minecraft/mods/mod.rs index 1959f92..6f2faaf 100644 --- a/src/v1/updates/minecraft/mods/mod.rs +++ b/src/v1/updates/minecraft/mods/mod.rs @@ -1,29 +1,144 @@ +use std::collections::HashMap; +use std::net::IpAddr; +use reqwest::Error; +use serde_json::json; +use serde::{Deserialize, Serialize}; use warp::Filter; +use regex::Regex; + +use crate::error_responses::BadRequestError; pub fn get_mods_paths() -> impl warp::Filter + Clone { // any path that starts with /v1/updates/minecraft/mods/{modname}/{loadername}/{version} calls handle_path warp::path("v1").and(warp::path("updates")).and(warp::path("minecraft")).and(warp::path("mods")) - .and(warp::path::param()) - .and(warp::path::param()) - .and(warp::path::param()) - .and(warp::path::end()) - .and(warp::addr::remote()) - .map(handle_path) + .and(warp::get().and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::end()).and(warp::filters::header::headers_cloned()).and(warp::query::>()).and_then(handle_path)) } -fn handle_path(modname: String, loadername: String, version: String, remote_ip: Option) -> String { - format!("modname: {}, loadername: {}, version: {}, IP: {}", modname, loadername, version, remote_ip.unwrap_or(std::net::SocketAddr::from(([0, 0, 0, 0], 0))).ip()) +#[derive(Debug, Deserialize, Serialize)] +struct ModData { + package: String, + name: String, + versions: Vec>>, } -// fn handle_with_headers( -// headers: warp::http::HeaderMap, -// ) -> String { -// // Iterate through the headers and print them -// for (name, value) in headers.iter() { -// println!("Header: {}: {}", name, value.to_str().unwrap_or("Invalid UTF-8")); -// } +#[derive(Debug, Deserialize, Serialize, Clone)] +struct ModVersion { + recommended: String, + latest: String, + all: Vec, +} -// // Respond with a message or perform other actions as needed -// "Headers received".to_string() -// } +// get json data from https://https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json +pub async fn fetch_data() -> Result { + let url = "https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json"; + let response = reqwest::get(url).await?; + + if response.status().is_success() { + // Parse the JSON response + let json_data: serde_json::Value = response.json().await?; + return Ok(json_data); + } else { + // Handle non-successful status codes + Err(response.error_for_status().unwrap_err()) + } +} + +fn is_valid_ip(ip_str: &str) -> bool { + if let Ok(ip) = ip_str.parse::() { + match ip { + IpAddr::V4(_) => true, + IpAddr::V6(_) => true, + } + } else { + false + } +} + +fn is_valid_minecraft_version(version: &str) -> bool { + // Define the regex pattern for the Minecraft version + let pattern = Regex::new(r"^1\.\d{1,2}(\.\d)?$").unwrap(); + + // Check if the provided version matches the pattern + pattern.is_match(version) +} + +fn get_header_forward_for_ip(headers: warp::http::HeaderMap) -> String { + // check if the header X-Forward-For exists and return the ip, if not, return an empty string + if let Some(forwarded_for) = headers.get("X-Forwarded-For") { + if let Ok(ip) = forwarded_for.to_str() { + // Extract the first IP address from the comma-separated list + if let Some(first_ip) = ip.split(',').next() { + return first_ip.trim().to_string(); + } + } + } + String::new() +} + +async fn handle_path(modpackage: String, loadername: String, mcversion: String, modversion: String, headers: warp::http::HeaderMap, params: HashMap) -> Result { + // Retrieve the IP from the header and check if it's valid + let mut client_ip = get_header_forward_for_ip(headers); + if !is_valid_ip(&client_ip) { + client_ip = params.get("ip").unwrap_or(&"".to_string()).to_string(); + if !is_valid_ip(&client_ip) { + client_ip = "Not valid".to_string(); + } + } + + // check if the minecraft version is valid + if !is_valid_minecraft_version(&mcversion) { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + // convert the raw list of data into a list of ModData and ModVersion + let mods_data: Vec = serde_json::from_value(data).unwrap(); + + // get the mod data from the requested mod + let mod_data: ModData = mods_data.into_iter().find(|mod_data| mod_data.package == modpackage).unwrap(); + + + // get the version data from the requested loader and remove the other loaders + let version_data: HashMap = mod_data.versions.into_iter().find(|version_data| version_data.contains_key(&loadername)).unwrap().remove(&loadername).unwrap(); + + // turn version_data into an object of String: ModVersion key value pairs + let version_data: HashMap = version_data.into_iter().map(|(key, value)| (key, value)).collect(); + + // get the version data for the current minecraft version + let version_data: ModVersion = version_data.get(&mcversion).unwrap().clone(); + + // get recommended and latest version + let recommended_version = version_data.recommended.clone(); + let latest_version = version_data.latest.clone(); + + // determine whether the client is up to date + let mut up_to_date = false; + if modversion == recommended_version { + up_to_date = true; + } + + // determine if telemetry is enabled by checking if the client_ip is valid + let mut telemetry = false; + if is_valid_ip(&client_ip) { + telemetry = true; + } + + // create the response + let response = json!({ + "promos": { + "latest": latest_version, + "recommended": recommended_version + }, + "upToDate": up_to_date, + "telemetry_enabled": telemetry + }); + + //TODO: Add way to process telemetry data + + // return the data + return Ok(warp::reply::json(&response)); +} diff --git a/src/v1/updates/minecraft/mods/telemetry/mod.rs b/src/v1/updates/minecraft/mods/telemetry/mod.rs new file mode 100644 index 0000000..dae1d5c --- /dev/null +++ b/src/v1/updates/minecraft/mods/telemetry/mod.rs @@ -0,0 +1,46 @@ +use std::fs::OpenOptions; +use std::io::{self, Write}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::path::Path; + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +struct IpInfo { + region: String, + // Add other fields as needed +} + +fn get_ip_hash(ip: &str) -> u64 { + let mut hasher = DefaultHasher::new(); + ip.hash(&mut hasher); + hasher.finish() +} + +pub fn log_ip_info(ip_address: &str, file_path: &str, mod_package: &str) -> io::Result<()> { + let ip_hash = get_ip_hash(ip_address); + + let ip_info = match get_ip_info(ip_address) { + Ok(info) => info, + Err(err) => { + IpInfo { region: "Unknown".to_string() } // Default to "Unknown" in case of an error + } + }; + + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(file_path)?; + + writeln!(file, "{} {} {}", ip_hash, ip_info.region, mod_package)?; + + Ok(()) +} + +fn main() { + let file_path = "ip_log.txt"; // Replace with your desired file path + + // Example usage + match log_ip_info("8.8.8.8", file_path) { + Err(err) => eprintln!("Error: {}", err), + } +}