diff --git a/Cargo.lock b/Cargo.lock index 5e6b90b..00730fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -66,14 +75,25 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bili-sync" version = "2.0.0" dependencies = [ "async-stream", + "cookie 0.18.0", "futures-core", "futures-util", + "hex", + "rand", + "regex", "reqwest", + "rsa", "serde", "serde_json", "strum", @@ -98,6 +118,12 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -116,6 +142,51 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" +dependencies = [ + "cookie 0.17.0", + "idna 0.3.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -132,6 +203,46 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "const-oid", + "crypto-common", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -254,6 +365,27 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -297,6 +429,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[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.12" @@ -368,6 +506,16 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -414,6 +562,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -421,6 +572,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -493,6 +650,59 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -585,6 +795,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -603,12 +822,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -618,6 +870,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + [[package]] name = "quote" version = "1.0.35" @@ -627,6 +895,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -636,6 +934,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "reqwest" version = "0.11.26" @@ -644,6 +971,8 @@ checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" dependencies = [ "base64", "bytes", + "cookie 0.17.0", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -678,6 +1007,26 @@ dependencies = [ "winreg", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -808,6 +1157,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -833,6 +1192,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strum" version = "0.26.2" @@ -855,6 +1230,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.52" @@ -905,6 +1286,37 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1005,6 +1417,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -1033,7 +1451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] @@ -1043,6 +1461,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "want" version = "0.3.1" @@ -1288,3 +1712,9 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index c8e9455..fec5635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,17 @@ edition = "2021" [dependencies] serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0" -reqwest = { version = "0.11", features = ["json", "stream"] } +reqwest = { version = "0.11", features = ["json", "stream", "cookies"] } +cookie = { version = "0.18.0", features = ["percent-encode"]} tokio = { version = "1", features = ["full"] } strum = { version = "0.26", features = ["derive"] } async-stream = "0.3.5" futures-core = "0.3" futures-util = "0.3.30" +rsa = "0.9.6" +hex = "0.4.3" +rand = "0.8.5" +regex = "1.10.3" [profile.release] strip = true diff --git a/src/bilibili/client.rs b/src/bilibili/client.rs index af71005..c1f41bc 100644 --- a/src/bilibili/client.rs +++ b/src/bilibili/client.rs @@ -1,79 +1,83 @@ use reqwest::{header, Method}; -pub struct Credential { - sessdata: String, - bili_jct: String, - buvid3: String, - dedeuserid: String, - ac_time_value: String, -} +use crate::bilibili::Credential; +use crate::Result; -impl Credential { - pub fn new( - sessdata: String, - bili_jct: String, - buvid3: String, - dedeuserid: String, - ac_time_value: String, - ) -> Self { - Self { - sessdata, - bili_jct, - buvid3, - dedeuserid, - ac_time_value, +pub struct Client(reqwest::Client); + +impl Client { + pub fn new() -> Self { + let mut headers = header::HeaderMap::new(); + headers.insert( + header::USER_AGENT, + header::HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54")); + headers.insert( + header::REFERER, + header::HeaderValue::from_static("https://www.bilibili.com"), + ); + Self( + reqwest::Client::builder() + .default_headers(headers) + .build() + .unwrap(), + ) + } + + pub fn request( + &self, + method: Method, + url: &str, + credential: Option<&Credential>, + ) -> reqwest::RequestBuilder { + let mut req = self.0.request(method, url); + if let Some(credential) = credential { + req = req + .header(header::COOKIE, format!("SESSDATA={}", credential.sessdata)) + .header(header::COOKIE, format!("bili_jct={}", credential.bili_jct)) + .header(header::COOKIE, format!("buvid3={}", credential.buvid3)) + .header( + header::COOKIE, + format!("DedeUserID={}", credential.dedeuserid), + ) + .header( + header::COOKIE, + format!("ac_time_value={}", credential.ac_time_value), + ); } + req } } -pub fn client_with_header() -> reqwest::Client { - let mut headers = header::HeaderMap::new(); - headers.insert( - header::USER_AGENT, - header::HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54")); - headers.insert( - header::REFERER, - header::HeaderValue::from_static("https://www.bilibili.com"), - ); - reqwest::Client::builder() - .default_headers(headers) - .build() - .unwrap() +impl Default for Client { + fn default() -> Self { + Self::new() + } } pub struct BiliClient { credential: Option, - client: reqwest::Client, + client: Client, } impl BiliClient { - pub fn anonymous() -> Self { - let credential = None; - let client = client_with_header(); + pub fn new(credential: Option) -> Self { + let client = Client::new(); Self { credential, client } } - pub fn authenticated(credential: Credential) -> Self { - let credential = Some(credential); - let client = client_with_header(); - Self { credential, client } - } - - fn set_header(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder { - let Some(credential) = &self.credential else { - return req; - }; - req.header("cookie", format!("SESSDATA={}", credential.sessdata)) - .header("cookie", format!("bili_jct={}", credential.bili_jct)) - .header("cookie", format!("buvid3={}", credential.buvid3)) - .header("cookie", format!("DedeUserID={}", credential.dedeuserid)) - .header( - "cookie", - format!("ac_time_value={}", credential.ac_time_value), - ) - } - pub fn request(&self, method: Method, url: &str) -> reqwest::RequestBuilder { - self.set_header(self.client.request(method, url)) + self.client.request(method, url, self.credential.as_ref()) + } + + pub async fn check_refresh(&mut self) -> Result<()> { + let Some(credential) = self.credential.as_mut() else { + // no credential, just ignore it + return Ok(()); + }; + if credential.check(&self.client).await? { + // is valid, no need to refresh + return Ok(()); + } + credential.refresh(&self.client).await } } diff --git a/src/bilibili/credential.rs b/src/bilibili/credential.rs new file mode 100644 index 0000000..c7d5cc1 --- /dev/null +++ b/src/bilibili/credential.rs @@ -0,0 +1,152 @@ +use std::collections::HashSet; +use std::time::{SystemTime, UNIX_EPOCH}; + +use cookie::Cookie; +use regex::Regex; +use reqwest::{header, Method}; +use rsa::pkcs8::DecodePublicKey; +use rsa::{Pkcs1v15Encrypt, RsaPublicKey}; + +use crate::bilibili::Client; +use crate::Result; + +#[derive(Default)] +pub struct Credential { + pub sessdata: String, + pub bili_jct: String, + pub buvid3: String, + pub dedeuserid: String, + pub ac_time_value: String, +} + +impl Credential { + pub fn new( + sessdata: String, + bili_jct: String, + buvid3: String, + dedeuserid: String, + ac_time_value: String, + ) -> Self { + Self { + sessdata, + bili_jct, + buvid3, + dedeuserid, + ac_time_value, + } + } + + pub async fn check(&self, client: &Client) -> Result { + let res = client + .request( + Method::GET, + "https://passport.bilibili.com/x/passport-login/web/cookie/info", + Some(self), + ) + .send() + .await? + .json::() + .await?; + res["refresh"] + .as_bool() + .ok_or("check refresh failed".into()) + } + + pub async fn refresh(&mut self, client: &Client) -> Result<()> { + let correspond_path = Self::get_correspond_path(); + let csrf = self.get_refresh_csrf(client, correspond_path).await?; + let new_credential = self.get_new_credential(client, &csrf).await?; + self.sessdata = new_credential.sessdata; + self.bili_jct = new_credential.bili_jct; + self.dedeuserid = new_credential.dedeuserid; + Ok(()) + } + + fn get_correspond_path() -> String { + // maybe as a static value + let key = RsaPublicKey::from_public_key_pem( + "-----BEGIN PUBLIC KEY----- + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLgd2OAkcGVtoE3ThUREbio0Eg + Uc/prcajMKXvkCKFCWhJYJcLkcM2DKKcSeFpD/j6Boy538YXnR6VhcuUJOhH2x71 + nzPjfdTcqMz7djHum0qSZA0AyCBDABUqCrfNgCiJ00Ra7GmRj+YCK1NJEuewlb40 + JNrRuoEUXpabUzGB8QIDAQAB + -----END PUBLIC KEY-----", + ) + .unwrap(); + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let data = format!("refresh_{}", ts).into_bytes(); + let mut rng = rand::rngs::OsRng; + let encrypted = key.encrypt(&mut rng, Pkcs1v15Encrypt, &data).unwrap(); + hex::encode(encrypted) + } + + async fn get_refresh_csrf(&self, client: &Client, correspond_path: String) -> Result { + let res = client + .request( + Method::GET, + format!("https://www.bilibili.com/correspond/1/{}", correspond_path).as_str(), + Some(self), + ) + .header(header::COOKIE, "Domain=.bilibili.com") + .send() + .await?; + if !res.status().is_success() { + return Err("error get csrf".into()); + } + let re = Regex::new("
(.+?)
").unwrap(); + if let Some(res) = re.find(&res.text().await?) { + return Ok(res.as_str().to_string()); + } + Err("error get csrf".into()) + } + + async fn get_new_credential(&self, client: &Client, csrf: &str) -> Result { + let res = client + .request( + Method::POST, + "https://passport.bilibili.com/x/passport-login/web/cookie/refresh", + Some(self), + ) + .header(header::COOKIE, "Domain=.bilibili.com") + .json(&serde_json::json!({ + "csrf": self.bili_jct, + "refresh_csrf": csrf, + "refresh_token": self.ac_time_value, + "source": "main_web", + })) + .send() + .await?; + let set_cookie = res + .headers() + .get(header::SET_COOKIE) + .ok_or("error refresh credential")? + .to_str() + .unwrap(); + let mut credential = Credential::default(); + let required_cookies = HashSet::from(["SESSDATA", "bili_jct", "DedeUserID"]); + let cookies: Vec = Cookie::split_parse_encoded(set_cookie) + .filter(|x| { + x.as_ref() + .is_ok_and(|x| required_cookies.contains(x.name())) + }) + .map(|x| x.unwrap()) + .collect(); + for cookie in cookies { + match cookie.name() { + "SESSDATA" => credential.sessdata = cookie.value().to_string(), + "bili_jct" => credential.bili_jct = cookie.value().to_string(), + "DedeUserID" => credential.dedeuserid = cookie.value().to_string(), + _ => continue, + } + } + let json = res.json::().await?; + if !json["data"]["refresh_token"].is_string() { + return Err("error refresh credential".into()); + } + credential.ac_time_value = json["data"]["refresh_token"].as_str().unwrap().to_string(); + Ok(credential) + } +} diff --git a/src/bilibili/favorite_list.rs b/src/bilibili/favorite_list.rs index e29542f..0aa2865 100644 --- a/src/bilibili/favorite_list.rs +++ b/src/bilibili/favorite_list.rs @@ -11,6 +11,7 @@ pub struct FavoriteList { fid: String, } +#[allow(dead_code)] #[derive(Debug, serde::Deserialize)] pub struct FavoriteListInfo { id: u64, diff --git a/src/bilibili/mod.rs b/src/bilibili/mod.rs index 53d6db5..542df59 100644 --- a/src/bilibili/mod.rs +++ b/src/bilibili/mod.rs @@ -1,11 +1,13 @@ mod analyzer; mod client; +mod credential; mod favorite_list; mod video; pub use analyzer::{ AudioQuality, BestStream, FilterOption, PageAnalyzer, VideoCodecs, VideoQuality, }; -pub use client::{client_with_header, BiliClient, Credential}; +pub use client::{BiliClient, Client}; +pub use credential::Credential; pub use favorite_list::FavoriteList; pub use video::Video; diff --git a/src/bilibili/video.rs b/src/bilibili/video.rs index 76d7945..0198795 100644 --- a/src/bilibili/video.rs +++ b/src/bilibili/video.rs @@ -27,6 +27,7 @@ pub struct Tag { pub tag_name: String, } +#[allow(dead_code)] #[derive(Debug, serde::Deserialize)] pub struct Page { cid: u64, diff --git a/src/downloader.rs b/src/downloader.rs index 2b0607b..9670073 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -1,17 +1,18 @@ use std::path::Path; use futures_util::StreamExt; +use reqwest::Method; use tokio::fs::{self, File}; use tokio::io; -use crate::bilibili::client_with_header; +use crate::bilibili::Client; use crate::Result; pub struct Downloader { - client: reqwest::Client, + client: Client, } impl Downloader { - pub fn new(client: reqwest::Client) -> Self { + pub fn new(client: Client) -> Self { Self { client } } @@ -20,7 +21,12 @@ impl Downloader { fs::create_dir_all(parent).await?; } let mut file = File::create(path).await?; - let mut res = self.client.get(url).send().await?.bytes_stream(); + let mut res = self + .client + .request(Method::GET, url, None) + .send() + .await? + .bytes_stream(); while let Some(item) = res.next().await { io::copy(&mut item?.as_ref(), &mut file).await?; } @@ -59,6 +65,6 @@ impl Downloader { impl Default for Downloader { fn default() -> Self { - Self::new(client_with_header()) + Self::new(Client::new()) } } diff --git a/src/main.rs b/src/main.rs index 889d183..b029cc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use futures_util::{pin_mut, StreamExt}; #[tokio::main] async fn main() { - let bili_client = Rc::new(BiliClient::anonymous()); + let bili_client = Rc::new(BiliClient::new(None)); let favorite_list = FavoriteList::new(bili_client.clone(), "52642258".to_string()); dbg!(favorite_list.get_info().await.unwrap());