use std::time::{SystemTime, UNIX_EPOCH}; use once_cell::sync::OnceCell; use reqwest::{cookie::Jar, Client, Url}; use std::io::prelude::*; /// Stores a client with a persistent cookie store static SESSION_COOKIE: OnceCell = OnceCell::new(); /// Stores the last time a request was made, in seconds since UNIX epoch static SESSION_TIME: OnceCell = OnceCell::new(); /// Makes a request to the online classroom, and returns the html string pub async fn request(url: String) -> Result { let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); ensure_session().await?; // Get the stored client let client = SESSION_COOKIE .get() .expect("SESSION_COOKIE was not set, even after calling ensure_session"); // Do the request let req_builder = client .get(format!("{}{}", classroom_url, url)) .build() .unwrap(); println!("{:?}", req_builder); let response = client.execute(req_builder).await; let response = match response { Ok(r) => r, Err(err) => return Err(format!("Error sending request: {:?}", err)), }; // Check if there's a new cookie if let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") { println!("new cookie: {}", session_cookie.value()); } match response.text().await { Ok(t) => Ok(t), Err(err) => Err(format!("Error getting text from response: {:?}", err)), } } /// Makes sure that the session cookie is set, and that it is valid pub async fn ensure_session() -> Result<(), String> { let last_usage_time = match SESSION_TIME.get() { Some(time) => *time, None => 0, }; // Get current time in seconds let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") .as_secs(); let time_passed = current_time - last_usage_time; // Default PHP session timeout is 1440 seconds. Use a 1400 seconds timeout to be safe if time_passed > 1400 { login().await?; if let Err(err) = SESSION_TIME.set(current_time) { return Err(format!("Error setting session time: {:?}", err)); } } Ok(()) } /// Logins to the online classroom, and sets the session cookie async fn login() -> Result<(), String> { let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); let classroom_user = std::env::var("CLASSROOM_USER").expect("CLASSROOM_USER env var is not set!"); let classroom_password = std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!"); let params = [ ("login", classroom_user), ("password", classroom_password), ("submitAuth", "".into()), ("_qf__formLogin", "".into()), ]; let jar = Jar::default(); let client = Client::builder() .cookie_store(true) .cookie_provider(jar.into()) .build() .unwrap(); // let client = Client::new(); let req = client .post(format!("{}/index.php", classroom_url)) .form(¶ms) .build().unwrap(); println!("{:?}\n", req); let body_bytes= req.body().unwrap().as_bytes().unwrap(); println!("{:?}\n", std::str::from_utf8(body_bytes)); let result = client.execute(req) .await; match result { Ok(response) => { println!("{:?}\n\n", response); let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") else { return Err("Response succeeded, but no session cookie was found".into()); }; // Save the client with the session cookie println!("Got a cookie: {}", session_cookie.value()); let text = response.text().await.unwrap(); // save text to file let mut f = std::fs::File::create("scraps/test.html").unwrap(); f.write_all(text.as_bytes()).unwrap(); match SESSION_COOKIE.set(client) { Ok(_) => Ok(()), Err(error) => Err(format!("Error saving client: {:?}", error)), } } Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)), } }