commit e30854304f1a1f4028b258d45bcfd1738de1f154 Author: uh wot Date: Thu Jul 15 00:10:10 2021 +0200 joscha gay diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..afe6105 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1012 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99446914425f48a667458b33c7fb920e24cf9e7c149a072a9fc420731b353835" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "async-stream" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a26cb53174ddd320edfff199a853f93d571f48eeb4dde75e67a9a3dbb7b7e5e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db134ba52475c060f3329a8ef0f8786d6b872ed01515d4b79c162e5798da1340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cpufeatures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4" +dependencies = [ + "libc", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dzlib-rs" +version = "0.1.0" +dependencies = [ + "aes", + "block-modes", + "hex", + "md-5", + "reqwest", + "serde", + "thiserror", + "tokio-test", +] + +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" + +[[package]] +name = "futures-macro" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" + +[[package]] +name = "futures-task" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" + +[[package]] +name = "futures-util" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "h2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d832b01df74254fe364568d6ddc294443f61cbec82816b60904303af87efae78" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" + +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "hyper" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "futures-util", + "hyper", + "log", + "rustls", + "tokio", + "tokio-rustls", + "webpki", +] + +[[package]] +name = "idna" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +dependencies = [ + "socket2", + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reqwest" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0460542b551950620a3648c6aa23318ac6b3cd779114bd873209e6e8b5eb1c34" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if", + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "pin-project-lite", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-util" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e67a5806118af01f0d9045915676b22aaebecf4178ae7021bc171dab0b897ab" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" + +[[package]] +name = "web-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[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 = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1311e9c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "dzlib-rs" +version = "0.1.0" +authors = ["uh_wot "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false } +serde = { version = "1.0", features = ["derive"] } +md-5 = "0.9" +hex = { version = "0.4", optional = true } +aes = { version = "0.7", optional = true } +block-modes = { version = "0.8", optional = true } +thiserror = "1.0" + +[dev-dependencies] +tokio-test = "0.4" + +[features] +default = ["stream"] +stream = ["hex", "aes", "block-modes"] \ No newline at end of file diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..537a271 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,269 @@ +use std::fmt::Display; +use std::str::FromStr; + +use reqwest::Method; + +use serde::{Deserialize, Deserializer, de}; + +use std::time::Instant; + +use crate::objects::*; + +use md5::{Digest, Md5}; + +use crate::errors::{ErrorKind, DzErr}; + +const API_URL: &str = "https://api.deezer.com"; +// client id and secret are from deezer's chromecast app +const CLIENT_ID: &str = "119915"; +const CLIENT_SECRET: &str = "2f5b4c9785ddc367975b83d90dc46f5c"; + +fn from_str<'de, T, D>(deserializer: D) -> Result + where T: FromStr, + T::Err: Display, + D: Deserializer<'de> +{ + let s = String::deserialize(deserializer)?; + T::from_str(&s).map_err(de::Error::custom) +} + +#[derive(Deserialize, Debug)] +struct TokenResp { + access_token: String, + #[serde(deserialize_with = "from_str")] + expires: u64, +} + +// needed because deezer's API returns an integer on "expires" instead of a string when logging in +// thanks baguette assholes +#[derive(Deserialize, Debug)] +struct LoginTokenResp { + access_token: String, + expires: u64, +} + +pub struct AccessToken { + pub token: String, + expires: u64, + instant: Instant +} + +impl AccessToken { + fn new(token: String, expires: u64) -> AccessToken { + AccessToken { + token, + expires, + instant: Instant::now(), + } + } +} + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +enum DzRespType { + Err { error: DzErr }, + Ok(T), +} + +pub struct Client { + client: reqwest::Client, + pub access_token: Option, +} + +impl Client { + pub fn new() -> Self { + Self { + access_token: None, + client: reqwest::Client::new(), + } + } + + pub async fn get_token(&mut self) -> Result<(), ErrorKind> { + let resp = self.client.request(Method::GET, "https://connect.deezer.com/oauth/access_token.php") + .query(&[ + ("grant_type", "client_credentials"), + ("client_id", CLIENT_ID), + ("client_secret", CLIENT_SECRET), + ("output", "json"), + ]) + .send().await.map_err(|e| ErrorKind::Reqwest(e))? + .json::>().await.map_err(|e| ErrorKind::Reqwest(e))?; + + match resp { + DzRespType::Ok(token) => { + self.access_token = Some(AccessToken::new(token.access_token, token.expires)); + Ok(()) + }, + DzRespType::Err { error } => Err(ErrorKind::API(error)), + } + } + + async fn check_token(&mut self) -> Result<(), ErrorKind> { + match &self.access_token { + Some(token) => { + if token.instant.elapsed().as_secs() >= token.expires && token.expires != 0 { + self.get_token().await + } else { + Ok(()) + } + }, + None => self.get_token().await + } + } + + pub async fn login(&mut self, email: &str, password: &str) -> Result<(), ErrorKind> { + let password = format!("{:x}", Md5::digest(password.as_bytes())); + + let hash = [ + CLIENT_ID.as_bytes(), + email.as_bytes(), + password.as_bytes(), + CLIENT_SECRET.as_bytes(), + ].concat(); + let hash = format!("{:x}", Md5::digest(&hash)); + + let url = format!("{}/auth/token", API_URL); + let resp = self.client.request(Method::GET, &url) + .query(&[ + ("app_id", CLIENT_ID), + ("login", email), + ("password", &password), + ("hash", &hash), + ]) + .send().await.map_err(|e| ErrorKind::Reqwest(e))? + .json::>().await.map_err(|e| ErrorKind::Reqwest(e))?; + + match resp { + DzRespType::Ok(token) => { + self.access_token = Some(AccessToken::new(token.access_token, token.expires)); + Ok(()) + }, + DzRespType::Err { error } => Err(ErrorKind::API(error)), + } + } + + pub async fn login_token(&mut self, token: &str) -> Result<(), ErrorKind> { + self.access_token = Some(AccessToken::new(token.to_string(), 0)); + match self.infos().await { + Err(e) => { + self.access_token = None; + Err(e) + }, + Ok(i) => { + match i.user_token { + Some(_) => Ok(()), + None => { + self.access_token = None; + Err(ErrorKind::NotUserToken) + }, + } + } + } + } + + pub async fn api_call(&mut self, method: reqwest::Method, path: &str, query: &Q) -> Result + where T: de::DeserializeOwned, + Q: serde::ser::Serialize + ?Sized, + { + self.check_token().await.map_err(|e| ErrorKind::Token(Box::new(e)))?; + let url = format!("{}/{}", API_URL, path); + + let resp = self.client.request(method, &url) + .query(&[("access_token", &self.access_token.as_ref().unwrap().token)]) + .query(query) + .body("\n") // needed otherwise some POST requests fail + .send().await.map_err(|e| ErrorKind::Reqwest(e))? + .json::>().await.map_err(|e| ErrorKind::Reqwest(e))?; + + match resp { + DzRespType::Ok(r) => Ok(r), + DzRespType::Err { error } => Err(ErrorKind::API(error)), + } + } + + pub async fn track(&mut self, id: i64) -> Result { + Track::get(self, id).await + } + + pub async fn album(&mut self, id: u64) -> Result { + Album::get(self, id).await + } + + pub async fn artist(&mut self, id: u64) -> Result { + Artist::get(self, id).await + } + + pub async fn playlist(&mut self, id: u64) -> Result { + Playlist::get(self, id).await + } + + pub async fn podcast(&mut self, id: u64) -> Result { + Podcast::get(self, id).await + } + + pub async fn episode(&mut self, id: u64) -> Result { + Episode::get(self, id).await + } + + pub async fn chart(&mut self, id: u64) -> Result { + Chart::get(self, id).await + } + + pub async fn radio(&mut self, id: u64) -> Result { + Radio::get(self, id).await + } + + pub async fn radio_list(&mut self) -> Result, ErrorKind> { + Radio::list(self).await + } + + pub async fn search_track(&mut self, query: &str) -> Result, ErrorKind> { + search::track(self, query).await + } + + pub async fn search_album(&mut self, query: &str) -> Result, ErrorKind> { + search::album(self, query).await + } + + pub async fn search_artist(&mut self, query: &str) -> Result, ErrorKind> { + search::artist(self, query).await + } + + pub async fn search_playlist(&mut self, query: &str) -> Result, ErrorKind> { + search::playlist(self, query).await + } + + pub async fn genre(&mut self, id: u64) -> Result { + Genre::get(self, id).await + } + + pub async fn editorial(&mut self, id: u64) -> Result { + Editorial::get(self, id).await + } + + pub async fn lyrics(&mut self, id: u64) -> Result { + Lyrics::get(self, id).await + } + + pub async fn user(&mut self, id: u64) -> Result { + User::get(self, id).await + } + + pub async fn user_self(&mut self) -> Result { + User::get_self(self).await + } + + pub async fn infos(&mut self) -> Result { + Infos::get(self).await + } + + pub async fn options(&mut self) -> Result { + Options::get(self).await + } +} + +impl Default for Client { + fn default() -> Self { + Client::new() + } +} \ No newline at end of file diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..ad5b7ac --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,43 @@ +use md5::{Md5, Digest}; +use aes::Aes128; +use block_modes::{BlockMode, Ecb}; +use block_modes::block_padding::ZeroPadding; + +pub fn stream_url(md5_origin: &str, format: u8, id: i64, media_version: u8) -> String { + // md5 origin + format num + id + media version + let metadata = [ + md5_origin.as_bytes(), + &[b'\xa4'], + format.to_string().as_bytes(), + &[b'\xa4'], + id.to_string().as_bytes(), + &[b'\xa4'], + media_version.to_string().as_bytes(), + ].concat(); + + let hash = format!("{:x}", Md5::digest(&metadata)); + + // md5 hash + previous metadata + let metadata_hash = [ + hash.as_bytes(), + &[b'\xa4'], + &metadata, + &[b'\xa4'], + ].concat(); + + // padding + /* + let len = metadata_hash.len(); + if len % 16 > 0 { + metadata_hash.resize(len+(16-len%16), b'\x00'); + } + */ + + const AES_KEY: [u8; 16] = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104]; + + type Aes128Ecb = Ecb; + let cipher = Aes128Ecb::new_from_slices(&AES_KEY, Default::default()).unwrap(); + let ciphertext = cipher.encrypt_vec(&metadata_hash); + + format!("https://cdns-proxy-{}.dzcdn.net/api/1/{}", md5_origin.chars().next().unwrap(), hex::encode(ciphertext)) +} \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..33ae5a3 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,33 @@ +use serde::Deserialize; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ErrorKind { + #[error("error from deezer api")] + API(DzErr), + #[error("error while calling api")] + Reqwest(reqwest::Error), + #[error("error while getting token")] + Token(Box), + #[error("token specified is not an account token")] + NotUserToken, +} + +#[derive(Deserialize, Debug)] +pub struct DzErr { + #[serde(rename = "type")] + pub err_type: DzErrType, + pub message: String, + pub code: Option, +} + +#[derive(Deserialize, Debug)] +pub enum DzErrType { + Exception, + OAuthException, + ParameterException, + MissingParameterException, + InvalidQueryException, + DataException, + IndividualAccountChangedNotAllowedException, +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3c15539 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +mod client; +pub mod objects; +pub mod errors; +#[cfg(feature = "stream")] +pub mod crypto; +pub use self::client::Client; \ No newline at end of file diff --git a/src/objects/album.rs b/src/objects/album.rs new file mode 100644 index 0000000..4f57ec8 --- /dev/null +++ b/src/objects/album.rs @@ -0,0 +1,63 @@ +use serde::Deserialize; +use super::{DzArray, User, Track, ImgObject}; +use crate::errors::ErrorKind; +use reqwest::Method; + +// https://developers.deezer.com/api/album +#[derive(Deserialize, Debug)] +pub struct Album { + pub id: u64, + pub title: String, + pub upc: Option, + pub link: Option, + pub share: Option, + pub cover: Option, + pub cover_small: Option, + pub cover_medium: Option, + pub cover_big: Option, + pub cover_xl: Option, + pub md5_image: Option, + pub genre_id: Option, + pub label: Option, + pub nb_tracks: Option, + pub duration: Option, + pub fans: Option, + pub rating: Option, + pub release_date: Option, + pub record_type: Option, + pub available: Option, + pub tracklist: Option, + pub explicit_lyrics: Option, + pub explicit_content_lyrics: Option, + pub explicit_content_cover: Option, + pub alternative: Option>, + pub tracks: Option>, +} + +impl Album { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("album/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn fans(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("album/{}/fans", id), &()).await + } + + pub async fn tracks(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("album/{}/tracks", id), &()).await + } +} + +impl ImgObject for Album { + fn md5_img(&self) -> Option<&str> { + Some(self.md5_image.as_ref()?) + } + + fn img_type(&self) -> &'static str { + "cover" + } +} \ No newline at end of file diff --git a/src/objects/artist.rs b/src/objects/artist.rs new file mode 100644 index 0000000..1d3d503 --- /dev/null +++ b/src/objects/artist.rs @@ -0,0 +1,67 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; + +use super::{DzArray, User, Track, Album, Playlist, ImgObject}; + +// https://developers.deezer.com/api/artist +#[derive(Deserialize, Debug)] +pub struct Artist { + pub id: Option, + pub name: String, + pub link: Option, + pub picture: Option, + pub picture_small: Option, + pub picture_medium: Option, + pub picture_big: Option, + pub picture_xl: Option, + pub md5_image: Option, + pub nb_album: Option, + pub nb_fan: Option, + pub radio: Option, + pub tracklist: String, +} + +impl Artist { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("artist/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Option> { + Some(Self::get(client, self.id?).await) + } + + pub async fn top(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("artist/{}/top", id), &()).await + } + + pub async fn albums(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("artist/{}/albums", id), &()).await + } + + pub async fn fans(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("artist/{}/fans", id), &()).await + } + + pub async fn related(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("artist/{}/related", id), &()).await + } + + pub async fn radio(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("artist/{}/radio", id), &()).await + } + + pub async fn playlists(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("artist/{}/playlists", id), &()).await + } +} + +impl ImgObject for Artist { + fn md5_img(&self) -> Option<&str> { + Some(self.md5_image.as_ref()?) + } + + fn img_type(&self) -> &'static str { + "artist" + } +} \ No newline at end of file diff --git a/src/objects/auth.rs b/src/objects/auth.rs new file mode 100644 index 0000000..3286610 --- /dev/null +++ b/src/objects/auth.rs @@ -0,0 +1,14 @@ +use crate::errors::ErrorKind; +use reqwest::Method; + +pub async fn check(client: &mut crate::Client, field: &str, value: &str) -> Result { + Ok( + client.api_call(Method::GET, "auth/check", &[("field", field), ("value", value)]).await? + ) +} + +pub async fn register(client: &mut crate::Client, email: &str, password: &str, name: &str, birthday: &str, genre: &str) -> Result { + Ok( + client.api_call(Method::POST, "auth/register", &[("email", email), ("password", password), ("name", name), ("birthday", birthday), ("genre", genre)]).await? + ) +} \ No newline at end of file diff --git a/src/objects/chart.rs b/src/objects/chart.rs new file mode 100644 index 0000000..9b43e0f --- /dev/null +++ b/src/objects/chart.rs @@ -0,0 +1,63 @@ +use serde::Deserialize; +use super::{DzArray, Track, Album, Artist, Playlist, Podcast}; +use crate::errors::ErrorKind; +use reqwest::Method; + +#[derive(Deserialize, Debug)] +pub struct Chart { + pub tracks: DzArray, + pub albums: DzArray, + pub artists: DzArray, + pub playlists: DzArray, + pub podcasts: DzArray, +} + +impl Chart { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("chart/{}", id), &()).await + } + + pub async fn tracks(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("chart/{}/tracks", id), &()).await + } + + pub async fn albums(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("chart/{}/albums", id), &()).await + } + + pub async fn artists(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("chart/{}/artists", id), &()).await + } + + pub async fn playlists(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("chart/{}/playlists", id), &()).await + } + + pub async fn podcasts(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("chart/{}/podcasts", id), &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct ChartTrack { + #[serde(flatten)] + pub track: Track, + + pub position: u64, +} + +#[derive(Deserialize, Debug)] +pub struct ChartAlbum { + #[serde(flatten)] + pub album: Album, + + pub position: u64, +} + +#[derive(Deserialize, Debug)] +pub struct ChartArtist { + #[serde(flatten)] + pub artist: Artist, + + pub position: u64, +} \ No newline at end of file diff --git a/src/objects/editorial.rs b/src/objects/editorial.rs new file mode 100644 index 0000000..32bfce3 --- /dev/null +++ b/src/objects/editorial.rs @@ -0,0 +1,51 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; +use super::{DzArray, Album, Chart, ImgObject}; + +#[derive(Deserialize, Debug)] +pub struct Editorial { + pub id: u64, + pub name: String, + pub picture: String, + pub picture_small: String, + pub picture_medium: String, + pub picture_big: String, + pub picture_xl: String, +} + +impl Editorial { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("editorial/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn list(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "editorial", &()).await + } + + pub async fn selection(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("editorial/{}/selection", id), &()).await + } + + pub async fn charts(client: &mut crate::Client, id: u64) -> Result { + super::Chart::get(client, id).await + } + + pub async fn releases(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("editorial/{}/releases", id), &()).await + } +} + +impl ImgObject for Editorial { + fn md5_img(&self) -> Option<&str> { + Some(self.picture_small.split('/').collect::>()[5]) + } + + fn img_type(&self) -> &'static str { + "misc" + } +} \ No newline at end of file diff --git a/src/objects/episode.rs b/src/objects/episode.rs new file mode 100644 index 0000000..ffb4ad9 --- /dev/null +++ b/src/objects/episode.rs @@ -0,0 +1,58 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; +use super::{Podcast, ImgObject}; + +#[derive(Deserialize, Debug)] +pub struct Episode { + pub id: u64, + pub title: String, + pub description: Option, + pub available: Option, + pub release_date: String, + pub duration: u64, + pub link: Option, + pub share: Option, + pub picture: String, + pub picture_small: Option, + pub picture_medium: Option, + pub picture_big: Option, + pub picture_xl: Option, + pub md5_origin: String, + pub md5_32: String, + pub md5_64: String, + #[serde(deserialize_with = "super::from_str")] + pub filesize_32: u64, + #[serde(deserialize_with = "super::from_str")] + pub filesize_64: u64, + pub direct_stream_url: Option, + pub podcast: Option, +} + +impl Episode { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("episode/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn set_bookmark(client: &mut crate::Client, id: u64, offset: u8) -> Result { + client.api_call(Method::POST, &format!("episode/{}/bookmark", id), &[("offset", &offset.to_string())]).await + } + + pub async fn delete_bookmark(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::DELETE, &format!("episode/{}/bookmark", id), &()).await + } +} + +impl ImgObject for Episode { + fn md5_img(&self) -> Option<&str> { + Some(self.picture_small.as_ref()?.split('/').collect::>()[5]) + } + + fn img_type(&self) -> &'static str { + "talk" + } +} \ No newline at end of file diff --git a/src/objects/genre.rs b/src/objects/genre.rs new file mode 100644 index 0000000..5894f92 --- /dev/null +++ b/src/objects/genre.rs @@ -0,0 +1,43 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; +use super::{DzArray, Artist, Radio, ImgObject}; + +#[derive(Deserialize, Debug)] +pub struct Genre { + pub id: u64, + pub name: String, + pub picture: String, + pub picture_small: String, + pub picture_medium: String, + pub picture_big: String, + pub picture_xl: String, +} + +impl Genre { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("genre/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn artists(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("genre/{}/artists", id), &()).await + } + + pub async fn radios(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("genre/{}/radios", id), &()).await + } +} + +impl ImgObject for Genre { + fn md5_img(&self) -> Option<&str> { + Some(self.picture_small.split('/').collect::>()[5]) + } + + fn img_type(&self) -> &'static str { + "misc" + } +} \ No newline at end of file diff --git a/src/objects/infos.rs b/src/objects/infos.rs new file mode 100644 index 0000000..6b73b68 --- /dev/null +++ b/src/objects/infos.rs @@ -0,0 +1,45 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; + +// https://developers.deezer.com/api/infos/ +#[derive(Deserialize, Debug)] +pub struct Infos { + pub country_iso: String, + pub country: String, + pub open: bool, + pub pop: String, + pub upload_token: String, + pub upload_token_lifetime: u64, + pub user_token: Option, + pub hosts: Hosts, + pub has_podcasts: bool, + pub offers: Vec, + pub freemium_available: bool, + pub lyrics_available: bool, +} + +impl Infos { + pub async fn get(client: &mut crate::Client) -> Result { + client.api_call(Method::GET, "infos", &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct Hosts { + pub stream: String, + pub images: String, +} + +#[derive(Deserialize, Debug)] +pub struct Offer { + pub id: u32, + pub name: String, + pub amount: String, + pub currency: String, + pub displayed_amount: String, + pub tc: String, + pub tc_html: String, + pub tc_txt: String, + pub try_and_buy: u32, +} \ No newline at end of file diff --git a/src/objects/lyrics.rs b/src/objects/lyrics.rs new file mode 100644 index 0000000..1b7ac49 --- /dev/null +++ b/src/objects/lyrics.rs @@ -0,0 +1,30 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; + +#[derive(Deserialize, Debug)] +pub struct Lyrics { + pub id: u64, + pub text: String, + pub sync_json: Option>, + pub copyrights: String, + pub writers: String, +} + +impl Lyrics { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("lyrics/{}", id), &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct LyricsSync { + pub lrc_timestamp: Option, + #[serde(default)] + #[serde(deserialize_with = "super::from_str_option")] + pub milliseconds: Option, + #[serde(default)] + #[serde(deserialize_with = "super::from_str_option")] + pub duration: Option, + pub line: String +} \ No newline at end of file diff --git a/src/objects/mod.rs b/src/objects/mod.rs new file mode 100644 index 0000000..e61a277 --- /dev/null +++ b/src/objects/mod.rs @@ -0,0 +1,102 @@ +mod track; +mod album; +mod artist; +mod playlist; +mod podcast; +mod episode; +mod chart; +mod radio; +pub mod search; +mod genre; +mod editorial; +mod lyrics; +mod user; +mod infos; +mod options; +pub mod auth; + +pub use self::track::*; +pub use self::album::*; +pub use self::artist::*; +pub use self::playlist::*; +pub use self::podcast::*; +pub use self::episode::*; +pub use self::chart::*; +pub use self::radio::*; +pub use self::genre::*; +pub use self::editorial::*; +pub use self::lyrics::*; +pub use self::user::*; +pub use self::infos::*; +pub use self::options::*; + +use serde::Deserialize; +use serde::de::{self, Deserializer}; + +use std::fmt::Display; +use std::str::FromStr; + +#[derive(Deserialize, Debug)] +pub struct DzArray { + pub data: Vec, + pub checksum: Option, + pub count: Option, + pub total: Option, + pub next: Option, +} + +pub enum ImgFormat { + JPG, + PNG, +} + +pub trait ImgObject { + fn md5_img(&self) -> Option<&str>; + fn img_type(&self) -> &'static str; + fn img_url(&self, size: u16, format: ImgFormat, jpg_quality: u8) -> Option { + let format = match format { + ImgFormat::JPG => "jpg", + ImgFormat::PNG => "png", + }; + let jpg_quality = { + let quality: u8; + if format == "png" { + quality = 100; + } else { + quality = jpg_quality; + } + quality + }; + Some(format!("https://cdns-images.dzcdn.net/images/{}/{}/{}x{}-000000-{}-0-0.{}", self.img_type(), self.md5_img()?, size, size, jpg_quality, format)) + } +} + +fn from_str<'de, T, D>(deserializer: D) -> Result + where T: FromStr, + T::Err: Display, + D: Deserializer<'de> +{ + let mut s = String::deserialize(deserializer)?; + if s.is_empty() { + s = String::from("0"); + } + T::from_str(&s).map_err(de::Error::custom) +} + +fn from_str_option<'de, T, D>(deserializer: D) -> Result, D::Error> + where T: FromStr, + T::Err: Display, + D: Deserializer<'de> +{ + let s: Option = Option::deserialize(deserializer)?; + if let Some(mut s) = s { + if s.is_empty() { + s = String::from("0"); + } + return Ok(Some( + T::from_str(&s).map_err(de::Error::custom)?, + )); + } + + Ok(None) +} \ No newline at end of file diff --git a/src/objects/options.rs b/src/objects/options.rs new file mode 100644 index 0000000..fc210ca --- /dev/null +++ b/src/objects/options.rs @@ -0,0 +1,26 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; + +// https://developers.deezer.com/api/options/ +#[derive(Deserialize, Debug)] +pub struct Options { + pub streaming: bool, + pub streaming_duration: u64, + pub offline: bool, + pub hq: bool, + pub ads_display: bool, + pub ads_audio: bool, + pub too_many_devices: bool, + pub can_subscribe: bool, + pub radio_skips: u32, + pub lossless: bool, + pub preview: bool, + pub radio: bool, +} + +impl Options { + pub async fn get(client: &mut crate::Client) -> Result { + client.api_call(Method::GET, "options", &()).await + } +} \ No newline at end of file diff --git a/src/objects/playlist.rs b/src/objects/playlist.rs new file mode 100644 index 0000000..c86cb77 --- /dev/null +++ b/src/objects/playlist.rs @@ -0,0 +1,73 @@ +use serde::Deserialize; +use super::{DzArray, User, Track, ImgObject}; +use crate::errors::ErrorKind; +use reqwest::Method; + +// https://developers.deezer.com/api/playlist +#[derive(Deserialize, Debug)] +pub struct Playlist { + pub id: u64, + pub title: String, + pub description: Option, + pub duration: Option, + pub public: bool, + pub is_loved_track: Option, + pub collaborative: Option, + pub nb_tracks: Option, + pub fans: Option, + pub link: Option, + pub share: Option, + pub picture: String, + pub picture_small: String, + pub picture_medium: String, + pub picture_big: String, + pub picture_xl: String, + pub checksum: String, + pub tracklist: String, + pub creation_date: String, + pub md5_image: String, + pub picture_type: String, + pub curator: Option, + pub creator: Option, + pub tracks: Option>, +} + +impl Playlist { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("playlist/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn seen(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::POST, &format!("playlist/{}/seen", id), &()).await + } + + pub async fn fans(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("playlist/{}/fans", id), &()).await + } + + pub async fn tracks(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("playlist/{}/tracks", id), &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct PlaylistTrack { + #[serde(flatten)] + pub track: Track, + + pub time_add: u64, +} + +impl ImgObject for Playlist { + fn md5_img(&self) -> Option<&str> { + Some(&self.md5_image) + } + + fn img_type(&self) -> &'static str { + "playlist" + } +} \ No newline at end of file diff --git a/src/objects/podcast.rs b/src/objects/podcast.rs new file mode 100644 index 0000000..e49ba2e --- /dev/null +++ b/src/objects/podcast.rs @@ -0,0 +1,45 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; +use super::{DzArray, Episode, ImgObject}; + +#[derive(Deserialize, Debug)] +pub struct Podcast { + pub id: u64, + pub title: String, + pub description: Option, + pub available: Option, + pub rating: Option, + pub fans: Option, + pub link: String, + pub share: Option, + pub picture: String, + pub picture_small: String, + pub picture_medium: String, + pub picture_big: String, + pub picture_xl: String, +} + +impl Podcast { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("podcast/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn episodes(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("podcast/{}/episodes", id), &()).await + } +} + +impl ImgObject for Podcast { + fn md5_img(&self) -> Option<&str> { + Some(self.picture_small.split('/').collect::>()[5]) + } + + fn img_type(&self) -> &'static str { + "talk" + } +} \ No newline at end of file diff --git a/src/objects/radio.rs b/src/objects/radio.rs new file mode 100644 index 0000000..0db1d85 --- /dev/null +++ b/src/objects/radio.rs @@ -0,0 +1,55 @@ +use serde::Deserialize; + +use super::{DzArray, ImgObject}; +use crate::errors::ErrorKind; +use reqwest::Method; + +#[derive(Deserialize, Debug)] +pub struct Radio { + pub id: u64, + pub title: String, + pub description: Option, + pub share: Option, + pub picture: String, + pub picture_small: String, + pub picture_medium: String, + pub picture_big: String, + pub picture_xl: String, + pub tracklist: String, + pub md5_image: String, +} + +impl Radio { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("radio/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn list(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "radio", &()).await + } + + pub async fn genres(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "radio/genres", &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct RadioGenre { + pub id: u64, + pub title: String, + pub radios: Vec, +} + +impl ImgObject for Radio { + fn md5_img(&self) -> Option<&str> { + Some(&self.md5_image) + } + + fn img_type(&self) -> &'static str { + "misc" + } +} \ No newline at end of file diff --git a/src/objects/search.rs b/src/objects/search.rs new file mode 100644 index 0000000..c35b642 --- /dev/null +++ b/src/objects/search.rs @@ -0,0 +1,57 @@ +use serde::Deserialize; +use super::{DzArray, Track, Album, Artist, Playlist, Podcast, Radio, User}; +use crate::errors::ErrorKind; +use reqwest::Method; + +pub async fn track(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/track", &[("q", query)]).await? + ) +} + +pub async fn album(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/album", &[("q", query)]).await? + ) +} + +pub async fn artist(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/artist", &[("q", query)]).await? + ) +} + +pub async fn playlist(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/playlist", &[("q", query)]).await? + ) +} + +pub async fn podcast(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/podcast", &[("q", query)]).await? + ) +} + +pub async fn radio(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/radio", &[("q", query)]).await? + ) +} + +pub async fn user(client: &mut crate::client::Client, query: &str) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/user", &[("q", query)]).await? + ) +} + +pub async fn history(client: &mut crate::client::Client) -> Result, ErrorKind> { + Ok( + client.api_call(Method::GET, "search/history", &()).await? + ) +} + +#[derive(Deserialize, Debug)] +pub struct SearchQuery { + pub query: String, +} \ No newline at end of file diff --git a/src/objects/track.rs b/src/objects/track.rs new file mode 100644 index 0000000..a4925a7 --- /dev/null +++ b/src/objects/track.rs @@ -0,0 +1,132 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; +use super::{DzArray, Artist, Album, Genre}; + +// https://developers.deezer.com/api/track +#[derive(Deserialize, Debug)] +pub struct Track { + pub id: i64, + pub readable: Option, + pub title: String, + pub title_short: Option, + pub title_version: Option, + pub isrc: Option, + pub link: Option, + pub share: Option, + pub duration: u64, + pub track_position: Option, + pub disk_number: Option, + pub rank: u32, + pub release_date: Option, + pub lyrics_id: u64, + pub explicit_lyrics: bool, + pub explicit_content_lyrics: u8, + pub explicit_content_cover: u8, + pub preview: Option, + pub alternative: Option>, + pub bpm: Option, + pub gain: Option, + pub available_countries: Option>, + pub contributors: Option>, + pub md5_image: String, + pub user_id: Option, + pub artist: Option, + pub album: Option, + + // stream url gen stuff + pub md5_origin: String, + pub media_version: u8, + + // stream filesizes + #[serde(deserialize_with = "super::from_str")] + pub filesize_128: u64, + #[serde(deserialize_with = "super::from_str")] + pub filesize_320: u64, + #[serde(default)] + #[serde(deserialize_with = "super::from_str_option")] + pub filesize_flac: Option, + #[serde(default)] + #[serde(deserialize_with = "super::from_str_option")] + pub filesize_mhm1_ra3: Option, + #[serde(deserialize_with = "super::from_str")] + pub filesize_misc: u64, +} + +pub enum TrkFormat { + AAC96, + MP364, + MP3128, + MP3320, + FLAC, + MP4RA1, + MP4RA2, + MP4RA3, + SBC256, + Misc, +} + +pub fn format_to_num(format: TrkFormat) -> u8 { + match format { + TrkFormat::AAC96 => 8, + TrkFormat::MP364 => 10, + TrkFormat::MP3128 => 1, + TrkFormat::MP3320 => 3, + TrkFormat::FLAC => 9, + TrkFormat::MP4RA1 => 13, + TrkFormat::MP4RA2 => 14, + TrkFormat::MP4RA3 => 15, + TrkFormat::SBC256 => 12, + TrkFormat::Misc => 0, + } +} + +impl Track { + pub async fn get(client: &mut crate::Client, id: i64) -> Result { + client.api_call(Method::GET, &format!("track/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub fn format_to_filesize(&self, format: &TrkFormat) -> Option { + match format { + TrkFormat::AAC96 => None, + TrkFormat::MP364 => None, + TrkFormat::MP3128 => Some(self.filesize_128), + TrkFormat::MP3320 => Some(self.filesize_320), + TrkFormat::FLAC => self.filesize_flac, + TrkFormat::MP4RA1 => None, + TrkFormat::MP4RA2 => None, + TrkFormat::MP4RA3 => None, + TrkFormat::SBC256 => None, + TrkFormat::Misc => Some(self.filesize_misc), + } + } + + #[cfg(feature = "stream")] + pub fn stream_url(&self, format: TrkFormat) -> Option { + if let Some(size) = self.format_to_filesize(&format) { + if size == 0 { + return None; + } + } + + let format = format_to_num(format); + + Some(crate::crypto::stream_url(&self.md5_origin, format, self.id, self.media_version)) + } + + pub async fn genres(client: &mut crate::Client, id: i64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("track/{}/genres", id), &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct Contributor { + #[serde(flatten)] + pub artist: Artist, + + pub role: String, +} \ No newline at end of file diff --git a/src/objects/user.rs b/src/objects/user.rs new file mode 100644 index 0000000..2487b8f --- /dev/null +++ b/src/objects/user.rs @@ -0,0 +1,193 @@ +use serde::Deserialize; +use crate::errors::ErrorKind; +use reqwest::Method; +use super::{DzArray, Album, Artist, Track, Playlist, Podcast, Radio, ImgObject}; + +// https://developers.deezer.com/api/user +#[derive(Deserialize, Debug)] +pub struct User { + pub id: u64, + pub name: String, + pub lastname: Option, + pub firstname: Option, + pub email: Option, + pub status: Option, + pub birthday: Option, + pub inscription_date: Option, + pub gender: Option, + pub link: Option, + pub picture: Option, + pub picture_small: Option, + pub picture_medium: Option, + pub picture_big: Option, + pub picture_xl: Option, + pub country: Option, + pub lang: Option, + pub explicit_content_level: Option, + pub explicit_content_levels_available: Option>, + pub tracklist: String, +} + +impl User { + pub async fn get(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::GET, &format!("user/{}", id), &()).await + } + + pub async fn get_full(&self, client: &mut crate::Client) -> Result { + Self::get(client, self.id).await + } + + pub async fn get_self(client: &mut crate::Client) -> Result { + client.api_call(Method::GET, "user/me", &()).await + } + + pub async fn albums(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/albums", id), &()).await + } + + pub async fn artists(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/artists", id), &()).await + } + + pub async fn tracks(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/tracks", id), &()).await + } + + pub async fn playlists(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/playlists", id), &()).await + } + + pub async fn personal_songs(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/personal_songs", &()).await + } + + pub async fn album_chart(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/charts/albums", id), &()).await + } + + pub async fn artist_chart(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/charts/artists", id), &()).await + } + + pub async fn track_chart(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/charts/tracks", id), &()).await + } + + pub async fn playlist_chart(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/charts/playlists", id), &()).await + } + + pub async fn flow(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/flow", id), &()).await + } + + pub async fn history(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/history", &()).await + } + + pub async fn followings(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/followings", id), &()).await + } + + pub async fn follow(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::POST, "user/me/followings", &[("user_id", &id.to_string())]).await + } + + pub async fn unfollow(client: &mut crate::Client, id: u64) -> Result { + client.api_call(Method::DELETE, "user/me/followings", &[("user_id", &id.to_string())]).await + } + + pub async fn followers(client: &mut crate::Client, id: u64) -> Result, ErrorKind> { + client.api_call(Method::GET, &format!("user/{}/followers", id), &()).await + } + + pub async fn album_recommendations(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/recommendations/albums", &()).await + } + + pub async fn release_recommendations(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/recommendations/releases", &()).await + } + + pub async fn artist_recommendations(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/recommendations/artists", &()).await + } + + pub async fn playlist_recommendations(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/recommendations/playlists", &()).await + } + + pub async fn track_recommendations(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/recommendations/tracks", &()).await + } + + pub async fn radio_recommendations(client: &mut crate::Client) -> Result, ErrorKind> { + client.api_call(Method::GET, "user/me/recommendations/radios", &()).await + } +} + +#[derive(Deserialize, Debug)] +pub struct UserAlbum { + #[serde(flatten)] + pub album: Album, + + pub time_add: u64, +} + +#[derive(Deserialize, Debug)] +pub struct UserArtist { + #[serde(flatten)] + pub artist: Artist, + + pub time_add: u64, +} + +#[derive(Deserialize, Debug)] +pub struct UserTrack { + #[serde(flatten)] + pub track: Track, + + pub time_add: u64, +} + +#[derive(Deserialize, Debug)] +pub struct UserPlaylist { + #[serde(flatten)] + pub playlist: Playlist, + + pub time_add: u64, +} + +#[derive(Deserialize, Debug)] +pub struct UserPodcast { + #[serde(flatten)] + pub podcast: Podcast, + + pub time_add: u64, +} + +#[derive(Deserialize, Debug)] +pub struct UserRadio { + #[serde(flatten)] + pub radio: Radio, + + pub time_add: u64, +} + +#[derive(Deserialize, Debug)] +pub struct RecommendationAlbum { + #[serde(flatten)] + pub album: Album, + + pub timestamp: u64, +} + +impl ImgObject for User { + fn md5_img(&self) -> Option<&str> { + Some(self.picture_small.as_ref()?.split('/').collect::>()[5]) + } + + fn img_type(&self) -> &'static str { + "user" + } +} \ No newline at end of file diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..d60dc4e --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,570 @@ +use dzlib_rs::{Client, objects::*, errors::ErrorKind}; +use tokio_test::block_on; + +const EMAIL: &str = "email"; +const PASSWORD: &str = "password"; + +#[test] +fn check_access_token() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.get_token())?; + let token = client.access_token.unwrap().token; + println!("{}", token); + assert_eq!(token.len(), 51); + Ok(()) +} + +#[test] +fn check_track() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let track = block_on(client.track(3135556))?; + println!("{:#?}", track); + Ok(()) +} + +#[test] +fn check_track_genres() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let genres = block_on(Track::genres(&mut client, 3135556))?; + println!("{:#?}", genres); + Ok(()) +} + +#[test] +fn check_album() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let album = block_on(client.album(302127))?; + println!("{:#?}", album); + Ok(()) +} + +#[test] +fn check_album_fans() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let fans = block_on(Album::fans(&mut client, 302127))?; + println!("{:#?}", fans); + Ok(()) +} + +#[test] +fn check_album_tracks() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let tracks = block_on(Album::tracks(&mut client, 302127))?; + println!("{:#?}", tracks); + Ok(()) +} + +#[test] +fn check_artist() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let artist = block_on(client.artist(27))?; + println!("{:#?}", artist); + Ok(()) +} + +#[test] +fn check_artist_top() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let top = block_on(Artist::top(&mut client, 27))?; + println!("{:#?}", top); + Ok(()) +} + +#[test] +fn check_artist_albums() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let albums = block_on(Artist::fans(&mut client, 27))?; + println!("{:#?}", albums); + Ok(()) +} + +#[test] +fn check_artist_fans() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let fans = block_on(Artist::fans(&mut client, 27))?; + println!("{:#?}", fans); + Ok(()) +} + +#[test] +fn check_artist_related() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let related = block_on(Artist::related(&mut client, 27))?; + println!("{:#?}", related); + Ok(()) +} + +#[test] +fn check_artist_radio() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let radio = block_on(Artist::radio(&mut client, 27))?; + println!("{:#?}", radio); + Ok(()) +} + +#[test] +fn check_artist_playlists() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let playlists = block_on(Artist::playlists(&mut client, 27))?; + println!("{:#?}", playlists); + Ok(()) +} + +#[test] +fn check_playlist() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let playlist = block_on(client.playlist(2021502402))?; + println!("{:#?}", playlist); + Ok(()) +} + +#[test] +fn check_playlist_fans() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let playlist = block_on(Playlist::fans(&mut client, 2021502402))?; + println!("{:#?}", playlist); + Ok(()) +} + +#[test] +fn check_playlist_tracks() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let tracks = block_on(Playlist::tracks(&mut client, 2021502402))?; + println!("{:#?}", tracks); + Ok(()) +} + +#[test] +fn check_playlist_seen() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let seen = block_on(Playlist::seen(&mut client, 2021502402))?; + println!("{:#?}", seen); + Ok(()) +} + +#[test] +fn check_user() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let user = block_on(client.user(2741487522))?; + println!("{:#?}", user); + Ok(()) +} + +#[test] +fn check_user_self() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let user = block_on(client.user_self())?; + println!("{:#?}", user); + Ok(()) +} + +#[test] +fn check_user_albums() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let albums = block_on(User::albums(&mut client, 5))?; + println!("{:#?}", albums); + Ok(()) +} + +#[test] +fn check_user_artists() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let artists = block_on(User::artists(&mut client, 5))?; + println!("{:#?}", artists); + Ok(()) +} + +#[test] +fn check_user_tracks() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let tracks = block_on(User::tracks(&mut client, 5))?; + println!("{:#?}", tracks); + Ok(()) +} + +#[test] +fn check_user_playlists() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let playlists = block_on(User::playlists(&mut client, 5))?; + println!("{:#?}", playlists); + Ok(()) +} + +#[test] +fn check_user_personal_songs() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let songs = block_on(User::personal_songs(&mut client))?; + println!("{:#?}", songs); + Ok(()) +} + +#[test] +fn check_user_album_chart() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let chart = block_on(User::album_chart(&mut client, 5))?; + println!("{:#?}", chart); + Ok(()) +} + +#[test] +fn check_user_artist_chart() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let chart = block_on(User::artist_chart(&mut client, 5))?; + println!("{:#?}", chart); + Ok(()) +} + +#[test] +fn check_user_track_chart() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let chart = block_on(User::track_chart(&mut client, 5))?; + println!("{:#?}", chart); + Ok(()) +} + +#[test] +fn check_user_playlist_chart() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let chart = block_on(User::playlist_chart(&mut client, 5))?; + println!("{:#?}", chart); + Ok(()) +} + +#[test] +fn check_user_flow() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let flow = block_on(User::flow(&mut client, 5))?; + println!("{:#?}", flow); + Ok(()) +} + +#[test] +fn check_user_history() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let history = block_on(User::history(&mut client))?; + println!("{:#?}", history); + Ok(()) +} + +#[test] +fn check_user_followings() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let followings = block_on(User::followings(&mut client, 5))?; + println!("{:#?}", followings); + Ok(()) +} + +#[test] +fn check_user_followers() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let followers = block_on(User::followers(&mut client, 5))?; + println!("{:#?}", followers); + Ok(()) +} + +#[test] +fn check_user_album_rec() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let rec = block_on(User::album_recommendations(&mut client))?; + println!("{:#?}", rec); + Ok(()) +} + +#[test] +fn check_user_release_rec() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let rec = block_on(User::release_recommendations(&mut client))?; + println!("{:#?}", rec); + Ok(()) +} + +#[test] +fn check_user_artist_rec() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let rec = block_on(User::artist_recommendations(&mut client))?; + println!("{:#?}", rec); + Ok(()) +} + +#[test] +fn check_user_playlist_rec() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let rec = block_on(User::playlist_recommendations(&mut client))?; + println!("{:#?}", rec); + Ok(()) +} + +#[test] +fn check_user_track_rec() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let rec = block_on(User::track_recommendations(&mut client))?; + println!("{:#?}", rec); + Ok(()) +} + +#[test] +fn check_user_radio_rec() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let rec = block_on(User::radio_recommendations(&mut client))?; + println!("{:#?}", rec); + Ok(()) +} + +#[test] +fn check_infos() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let infos = block_on(client.infos())?; + println!("{:#?}", infos); + Ok(()) +} + +#[test] +fn check_genre() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let genre = block_on(client.genre(0))?; + println!("{:#?}", genre); + Ok(()) +} + +#[test] +fn check_genre_artists() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let artists = block_on(Genre::artists(&mut client, 0))?; + println!("{:#?}", artists); + Ok(()) +} + +#[test] +fn check_genre_radios() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let radios = block_on(Genre::radios(&mut client, 0))?; + println!("{:#?}", radios); + Ok(()) +} + +#[test] +fn check_options() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let options = block_on(client.options())?; + println!("{:#?}", options); + Ok(()) +} + +#[test] +fn check_editorial() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let editorial = block_on(client.editorial(0))?; + println!("{:#?}", editorial); + Ok(()) +} + +#[test] +fn check_editorial_list() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let list = block_on(Editorial::list(&mut client))?; + println!("{:#?}", list); + Ok(()) +} + +#[test] +fn check_editorial_selection() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let selection = block_on(Editorial::selection(&mut client, 0))?; + println!("{:#?}", selection); + Ok(()) +} + +#[test] +fn check_editorial_charts() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let charts = block_on(Editorial::charts(&mut client, 0))?; + println!("{:#?}", charts); + Ok(()) +} + +#[test] +fn check_lyrics() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let lyrics = block_on(client.lyrics(2780622))?; + println!("{:#?}", lyrics); + Ok(()) +} + +#[test] +fn check_radio() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let radio = block_on(client.radio(6))?; + println!("{:#?}", radio); + Ok(()) +} + +#[test] +fn check_radio_list() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let radio_list = block_on(client.radio_list())?; + println!("{:#?}", radio_list); + Ok(()) +} + +#[test] +fn check_radio_genres() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let genres = block_on(Radio::genres(&mut client))?; + println!("{:#?}", genres); + Ok(()) +} + +#[test] +fn check_chart() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let chart = block_on(client.chart(0))?; + println!("{:#?}", chart); + Ok(()) +} + +#[test] +fn check_chart_tracks() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let tracks = block_on(Chart::tracks(&mut client, 0))?; + println!("{:#?}", tracks); + Ok(()) +} + +#[test] +fn check_chart_albums() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let albums = block_on(Chart::albums(&mut client, 0))?; + println!("{:#?}", albums); + Ok(()) +} + +#[test] +fn check_chart_artists() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let artists = block_on(Chart::artists(&mut client, 0))?; + println!("{:#?}", artists); + Ok(()) +} + +#[test] +fn check_chart_playlists() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let playlists = block_on(Chart::playlists(&mut client, 0))?; + println!("{:#?}", playlists); + Ok(()) +} + +#[test] +fn check_chart_podcasts() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let playlists = block_on(Chart::podcasts(&mut client, 0))?; + println!("{:#?}", playlists); + Ok(()) +} + +#[test] +fn check_search_track() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(client.search_track("eminem"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_album() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(client.search_album("eminem"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_artist() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(client.search_artist("eminem"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_playlist() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(client.search_playlist("eminem"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_podcast() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(search::podcast(&mut client, "eminem"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_radio() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(search::radio(&mut client, "classic"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_user() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let search = block_on(search::user(&mut client, "eminem"))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_search_history() -> Result<(), ErrorKind> { + let mut client = Client::new(); + block_on(client.login(EMAIL, PASSWORD))?; + let search = block_on(search::history(&mut client))?; + println!("{:#?}", search); + Ok(()) +} + +#[test] +fn check_podcast() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let podcast = block_on(client.podcast(2027))?; + println!("{:#?}", podcast); + Ok(()) +} + +#[test] +fn check_podcast_episodes() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let episodes = block_on(Podcast::episodes(&mut client, 2027))?; + println!("{:#?}", episodes); + Ok(()) +} + +#[test] +fn check_episode() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let episode = block_on(client.episode(56469))?; + println!("{:#?}", episode); + Ok(()) +} + +#[test] +fn check_register() -> Result<(), ErrorKind> { + let mut client = Client::new(); + let user_id = block_on(auth::register(&mut client, "email", "password", "name", "0000-00-00", "M"))?; + println!("{:#?}", user_id); + Ok(()) +}