diff --git a/ytpapi/Cargo.lock b/ytpapi/Cargo.lock index 889f9aa..d5d367c 100644 --- a/ytpapi/Cargo.lock +++ b/ytpapi/Cargo.lock @@ -96,22 +96,6 @@ dependencies = [ "url", ] -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - [[package]] name = "crc32fast" version = "1.3.2" @@ -130,15 +114,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - [[package]] name = "flate2" version = "1.0.23" @@ -157,21 +132,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -314,16 +274,16 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d" dependencies = [ - "bytes", + "http", "hyper", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -347,15 +307,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.4.0" @@ -437,52 +388,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "native-tls" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -510,39 +423,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.12.0" @@ -563,7 +443,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.34.0", ] [[package]] @@ -584,12 +464,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -641,15 +515,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "reqwest" version = "0.11.11" @@ -668,89 +533,104 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "lazy_static", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", "proc-macro-hack", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] [[package]] -name = "ryu" -version = "1.0.9" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "lazy_static", + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", "winapi", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "rustls" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] [[package]] -name = "security-framework" -version = "2.6.1" +name = "rustls-pemfile" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", + "base64", ] [[package]] -name = "security-framework-sys" -version = "2.6.1" +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] name = "serde" -version = "1.0.136" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -759,9 +639,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" dependencies = [ "itoa", "ryu", @@ -811,6 +691,12 @@ dependencies = [ "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.91" @@ -822,20 +708,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - [[package]] name = "time" version = "0.3.11" @@ -871,16 +743,16 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -901,13 +773,14 @@ dependencies = [ ] [[package]] -name = "tokio-native-tls" -version = "0.3.0" +name = "tokio-rustls" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "native-tls", + "rustls", "tokio", + "webpki", ] [[package]] @@ -989,6 +862,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[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.2" @@ -1001,12 +880,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -1105,6 +978,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1133,43 +1025,100 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "winreg" version = "0.10.1" diff --git a/ytpapi/src/lib.rs b/ytpapi/src/lib.rs index 940955e..c16fb3e 100644 --- a/ytpapi/src/lib.rs +++ b/ytpapi/src/lib.rs @@ -11,10 +11,9 @@ use reqwest::{ Client, ClientBuilder, }; -use serde_json::Value; use string_utils::StringUtils; -use structs::{playlists_from_json, playlists_from_json_hub, search_results, videos_from_playlist}; +use structs::{get_playlist, from_json, get_video}; pub use structs::{Playlist, Video}; const YTM_DOMAIN: &str = "https://music.youtube.com"; @@ -22,26 +21,6 @@ const YTM_DOMAIN: &str = "https://music.youtube.com"; mod string_utils; mod structs; -/* fn sapisid_from_cookie(string: &str) -> Option { - string.find("__Secure-3PAPISID=").map(|i| { - let string = &string[i + "__Secure-3PAPISID=".len()..]; - let string = &string[..string.find(';').unwrap()]; - string.to_owned() - }) -} */ - -/* fn get_authorization(auth: &str) -> Option { - let mut hasher = Sha1::new(); - let unix = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("Time went backwards!") - .as_secs(); - - hasher.update(format!("{unix} + ' ' + {auth}").as_bytes()); - - Some(format!("SAPISIDHASH {unix}_{:x}", hasher.finalize())) -} */ - fn unescape(inp: &str) -> Result { let mut string = String::with_capacity(inp.len()); let mut iter = inp.chars(); @@ -107,12 +86,12 @@ async fn get_visitor_id( .text() .await .map_err(Error::Reqwest)?; - let playlist = playlists_from_json(&extract_json(&response)?)?; + let playlist = from_json(&extract_json(&response)?, get_playlist)?; response .between("VISITOR_DATA\":\"", "\"") .to_owned_() .map(|x| (x, playlist)) - .ok_or_else(|| Error::InvalidHTMLFile(response.to_string())) + .ok_or_else(|| Error::InvalidHTMLFile(0,response.to_string())) } async fn get_user_playlists( @@ -128,8 +107,7 @@ async fn get_user_playlists( .text() .await .map_err(Error::Reqwest)?; - let json = extract_json(&response)?; - playlists_from_json_hub(&json) + from_json(&extract_json(&response)?, get_playlist) } fn extract_json(string: &str) -> Result { @@ -140,7 +118,7 @@ fn extract_json(string: &str) -> Result { ) .after("data: '") .to_owned_() - .ok_or_else(|| Error::InvalidHTMLFile(string.to_string()))?; + .ok_or_else(|| Error::InvalidHTMLFile(1,string.to_string()))?; unescape(&json) } fn extract_json_search(string: &str) -> Result { @@ -151,23 +129,18 @@ fn extract_json_search(string: &str) -> Result { ) .after("data: '") .to_owned_() - .ok_or_else(|| Error::InvalidHTMLFile(string.to_string()))?; + .ok_or_else(|| Error::InvalidHTMLFile(2,string.to_string()))?; unescape(&json) } -/* const YTM_BASE_API: &'static str = "https://music.youtube.com/youtubei/v1/"; -const YTM_PARAMS: &'static str = "?alt=json&key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"; */ - pub struct YTApi { - /* headers: HeaderMap, */ - /* sapi: String, */ client: Client, playlists: Vec, } #[derive(Debug)] pub enum Error { - InvalidHTMLFile(String), + InvalidHTMLFile(u32,String), Reqwest(reqwest::Error), SerdeJson(serde_json::Error), InvalidHeaderValue(InvalidHeaderValue), @@ -215,9 +188,7 @@ impl YTApi { .await .map_err(Error::Reqwest)?, )?; - let json: Value = serde_json::from_str(&k).map_err(Error::SerdeJson)?; - //std::fs::write("search.json", k).map_err(Error::Io)?; - search_results(json) + from_json(&k, get_video) } pub fn playlists(&self) -> &Vec { &self.playlists @@ -228,17 +199,16 @@ impl YTApi { "x-goog-visitor-id", HeaderValue::from_str(&xgoo).map_err(Error::InvalidHeaderValue)?, ); - /* let sapi = sapisid_from_cookie(headers.get("cookie").unwrap().to_str().unwrap()).unwrap(); */ let k = ClientBuilder::default() .cookie_store(true) .default_headers(headers) .build() .map_err(Error::Reqwest)?; playlists.append(&mut get_user_playlists(&k, &HeaderMap::new()).await?); + playlists.sort(); + playlists.dedup(); Ok(Self { - /* sapi, */ client: k, - /* headers: headers, */ playlists, }) } @@ -252,14 +222,8 @@ impl YTApi { } Self::from_headers_map(headers).await } - /* fn browse_home(&self) { - self.send_request( - "browse", - serde_json::from_str("{\"browseId\":\"FEmusic_home\"}").unwrap(), - ) - } */ pub async fn browse_playlist(&self, playlistid: &str) -> Result, Error> { - videos_from_playlist(&extract_json( + let playlist = extract_json( &self .client .get(&format!( @@ -272,6 +236,7 @@ impl YTApi { .text() .await .map_err(Error::Reqwest)?, - )?) + )?; + from_json(&playlist, get_video) } } diff --git a/ytpapi/src/main.rs b/ytpapi/src/main.rs index 6ef4775..757baff 100644 --- a/ytpapi/src/main.rs +++ b/ytpapi/src/main.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, str::FromStr}; -use ytpapi::{Error, YTApi}; +use ytpapi::YTApi; fn main() { tokio::runtime::Builder::new_current_thread() @@ -8,9 +8,14 @@ fn main() { .build() .unwrap() .block_on(async { - let mut api = + let api = YTApi::from_header_file(PathBuf::from_str("headers.txt").unwrap().as_path()) .await .unwrap(); + api.playlists() + .iter() + .for_each(|playlist| { + println!("{:?}", playlist); + }); }); } diff --git a/ytpapi/src/structs.rs b/ytpapi/src/structs.rs index 80cf218..0422777 100644 --- a/ytpapi/src/structs.rs +++ b/ytpapi/src/structs.rs @@ -5,70 +5,46 @@ use serde_json::Value; use crate::Error; -/// Will parse a path and a json value to a Value -/// # Example -/// `test.tabs.#0.value` -/// - -pub fn extract_meaninfull<'a>(value: &'a Value, path: &str) -> Result<&'a Value, Error> { - let mut current_value = value; - for path_part in path.split('.') { - if let Ok(a) = path_part.parse::() { - let array = current_value.as_array().ok_or_else(|| { - Error::InvalidJsonCantFind(path_part.to_string(), current_value.to_string()) - })?; - if array.len() < a { - return Err(Error::InvalidJsonCantFind( - path_part.to_string(), - current_value.to_string(), - )); +/** + * Applies recursively the `transformer` function to the given json value and returns the transformed values. + */ +pub(crate) fn from_json( + json: &str, + transformer: impl Fn(&Value) -> Option, +) -> Result, Error> { + /** + * Execute a function on each element of a json value recursively. + * When the function returns something, the value is added to the result. + */ + pub(crate) fn inner_crawl( + value: &Value, + playlists: &mut Vec, + transformer: &impl Fn(&Value) -> Option, + ) { + if let Some(e) = transformer(value) { + // Maybe an hashset would be better + if !playlists.contains(&e) { + playlists.push(e); } - current_value = &array[a]; - } else if let Some(v) = current_value.get(path_part) { - current_value = v; - } else { - return Err(Error::InvalidJsonCantFind( - path_part.to_string(), - current_value.to_string(), - )); + return; + } + match value { + Value::Array(a) => a + .iter() + .for_each(|x| inner_crawl(x, playlists, transformer)), + Value::Object(a) => a + .values() + .for_each(|x| inner_crawl(x, playlists, transformer)), + _ => (), } } - Ok(current_value) -} -const PATH_PLAYLIST_HUB: &str = "contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents.1.itemSectionRenderer.contents.0.gridRenderer.items"; -const PATH: &str = "contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents.0.musicCarouselShelfRenderer.contents"; -const PLAYLIST_PATH: &str = "contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents.0.musicPlaylistShelfRenderer.contents"; -// contents.singleColumnBrowseResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents -pub(crate) fn playlists_from_json(string: &str) -> Result, Error> { - let jason = serde_json::from_str(string).map_err(Error::SerdeJson)?; - as_array(extract_meaninfull(&jason, PATH)?)? - .iter() - .map(get_playlist) - .collect() -} - -pub(crate) fn playlists_from_json_hub(string: &str) -> Result, Error> { - let jason = serde_json::from_str(string).map_err(Error::SerdeJson)?; - Ok(as_array(extract_meaninfull(&jason, PATH_PLAYLIST_HUB)?)? - .iter() - .map(get_playlist_hubendpoint) - .collect::>, _>>()? - .into_iter() - .flatten() - .collect()) -} - -fn as_array(value: &Value) -> Result<&Vec, Error> { - value - .as_array() - .ok_or_else(|| Error::InvalidJsonCantFind("Not an array".to_owned(), value.to_string())) -} - -fn as_str(value: &Value) -> Result { - value - .as_str() - .ok_or_else(|| Error::InvalidJsonCantFind("Not a string".to_owned(), value.to_string())) - .map(|x| x.to_owned()) + let mut playlists = Vec::new(); + inner_crawl( + &serde_json::from_str(json).map_err(Error::SerdeJson)?, + &mut playlists, + &transformer, + ); + Ok(playlists) } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] @@ -89,159 +65,102 @@ impl Display for Video { ) } } -fn get_video_titles(value: &Value) -> Result { - let value = extract_meaninfull(value, "musicResponsiveListItemRenderer")?; - let texts: Vec = as_array(extract_meaninfull(value, "flexColumns")?)? - .iter() - .map(get_text_from_flexcolumn) - .collect(); - let author = texts.get(1).cloned().unwrap_or_default(); - let begin = author.find('•').map(|x| x + "•".len()).unwrap_or(0); - let end = author.rfind('•').unwrap_or_else(|| author.len()); - let author = author[begin.min(end)..end.max(begin)].trim().to_owned(); - let k = author - .rfind('•') - .unwrap_or_else(|| author.len() - "•".len()); - let album = author[k + "•".len()..].trim().to_owned(); - let author = author[..k].trim().to_owned(); - Ok(Video { - title: texts.get(0).cloned().unwrap_or_default(), - author, - album, - video_id: as_str(extract_meaninfull(value, "playlistItemData.videoId")?)?, - duration: String::new(), - }) + +#[derive(Debug, Serialize, Deserialize, Clone, PartialOrd, Eq, Ord, PartialEq, Hash)] +pub struct Playlist { + pub name: String, + pub subtitle: String, + pub browse_id: String, } -fn get_video(value: &Value) -> Result { - let value = extract_meaninfull(value, "musicResponsiveListItemRenderer")?; - let texts: Vec = as_array(extract_meaninfull(value, "flexColumns")?)? - .iter() - .map(get_text_from_flexcolumn) - .collect(); - let k: Result = (|| { - as_str(extract_meaninfull( - value, - "fixedColumns.0.musicResponsiveListItemFixedColumnRenderer.text.runs.0.text", - )?) - })(); - Ok(Video { - title: texts.get(0).cloned().unwrap_or_default(), - author: texts.get(1).cloned().unwrap_or_default(), - album: texts.get(2).cloned().unwrap_or_default(), - video_id: as_str(extract_meaninfull(value, "playlistItemData.videoId")?)?, - duration: k.unwrap_or_default(), + +/** + * Tries to extract a playlist from a json value. + * Quite flexible to reduce odds of API change breaking this. + */ +pub(crate) fn get_playlist(value: &Value) -> Option { + let object = value.as_object()?; + let title_text = get_text(object.get("title")?, true)?; + let subtitle = object.get("subtitle").and_then(|x| get_text(x, false)); + let browse_id = &object + .get("navigationEndpoint") + .and_then(|x| x.get("browseEndpoint")) + .and_then(|x| x.get("browseId")) + .and_then(Value::as_str)?; + Some(Playlist { + name: title_text, + subtitle: subtitle.unwrap_or_default(), + browse_id: browse_id.strip_prefix("VL")?.to_string(), }) } -const ALLOWED: &[&str] = &[ - "Video", "Vidéo", "Title", "Song", "Titre", "Meilleur", "Best", -]; - -pub fn search_results(json: Value) -> Result, Error> { - let json = extract_meaninfull(&json, "contents.tabbedSearchResultsRenderer.tabs.0.tabRenderer.content.sectionListRenderer.contents")?; - let mut list: Vec