use rocket::form::Form; use rocket::http::Status; use rocket::response::status; use base64::{Engine as _, engine::general_purpose}; use sqlx::Type; use sqlx::{Encode, Sqlite, query_builder::QueryBuilder, Execute}; use crate::helpers; use crate::db; #[derive(Debug, sqlx::FromRow)] struct DynQuery { id: i64, name: String, user_id: i64, description: String, original: Option, game_version: i32, requested_stars: Option, version: i32, song_id: i32, length: i32, objects: i32, coins: i32, has_ldm: i32, two_player: i32, downloads: i32, likes: i32, difficulty: Option, community_difficulty: Option, demon_difficulty: Option, stars: Option, featured: i32, epic: i32, rated_coins: i32, user_username: String, user_udid: Option, user_account_id: Option, user_registered: i32, editor_time: i32, editor_time_copies: i32 } #[derive(FromForm)] pub struct FormGetLevels { page: Option, str: Option, accountID: Option, gjp: Option, gjp2: Option, password: Option, // we have to escape here r#type: Option, featured: Option, original: Option, coins: Option, epic: Option, uncompleted: Option, onlyCompleted: Option, completedLevels: Option, song: Option, customSong: Option, twoPlayer: Option, star: Option, noStar: Option, gauntlet: Option, len: Option, diff: Option, demonFilter: Option, local: Option } #[post("/getGJLevels20.php", data = "")] pub async fn get_levels(input: Form) -> status::Custom<&'static str> { let mut connection = db::establish_sqlite_conn().await; let mut can_see_unlisted = false; // WHERE [...] let mut query_params: Vec<&str> = vec![]; // Use this for binding on `query_params` let mut query_params_bind: Vec> = vec![]; // ORDER BY [...] let mut order = "levels.created_at DESC"; let page_offset = input.page.unwrap_or(0) * 10; let search_query = input.str.clone().unwrap_or("".to_string()); if !search_query.is_empty() && input.r#type != Some(5) && input.r#type != Some(10) && input.r#type != Some(19) { match search_query.parse::() { Ok(id) => { can_see_unlisted = true; query_params.push("levels.id = ?"); query_params_bind.push(Box::new(id)) }, Err(_) => { query_params.push("levels.name LIKE ?"); query_params_bind.push(Box::new(search_query.clone() + "%")); } } } if let Some(1) = input.featured { query_params.push("featured = 1"); } if let Some(1) = input.original { query_params.push("original IS NULL"); } if let Some(1) = input.coins { query_params.push("rated_coins = 1 AND levels.coins != 0"); } if let Some(1) = input.epic { query_params.push("epic = 1"); } if let Some(1) = input.uncompleted { match input.completedLevels.clone() { Some(completed_levels) => { let clean_levels: Vec = completed_levels[1..completed_levels.len() - 1].split(',') .map(|s| s.parse::().expect("failed to parse i64")) .collect(); let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::>().join(", "); query_params.push("levels.id NOT IN (?)"); query_params_bind.push(Box::new(levels_str)); }, None => return status::Custom(Status::Ok, "-1") } } if let Some(1) = input.onlyCompleted { match input.completedLevels.clone() { Some(completed_levels) => { let clean_levels: Vec = completed_levels[1..completed_levels.len() - 1].split(',') .map(|s| s.parse::().expect("failed to parse i64")) .collect(); let levels_str = clean_levels.iter().map(|n| n.to_string()).collect::>().join(", "); query_params.push("levels.id IN (?)"); query_params_bind.push(Box::new(levels_str)); }, None => return status::Custom(Status::Ok, "-1") } } if let Some(song_id) = input.song { if let Some(custom_song) = input.customSong { query_params.push("song_id = ?"); query_params_bind.push(Box::new(custom_song)); } else { query_params.push("song_id = ?"); query_params_bind.push(Box::new(song_id)); } } if let Some(1) = input.twoPlayer { query_params.push("two_player = 1"); } if let Some(1) = input.star { query_params.push("levels.stars IS NOT NULL"); } if let Some(1) = input.noStar { query_params.push("levels.stars IS NULL"); } if let Some(_gauntlet_id) = input.gauntlet { unimplemented!("no gauntlet support") } if let Some(len) = input.len { query_params.push("levels.length = ?"); query_params_bind.push(Box::new(len)); } if let Some(diff) = input.diff.clone() { if diff != "-" { match diff.as_str() { "-1" => { query_params.push("difficulty IS NULL AND community_difficulty IS NULL"); // NA }, "-2" => match input.demonFilter { Some(demon_filter) => { match demon_filter { 1 => { query_params.push("demon_difficulty = ?"); query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Easy.to_demon_difficulty())); }, 2 => { query_params.push("demon_difficulty = ?"); query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Medium.to_demon_difficulty())); }, 3 => { query_params.push("demon_difficulty = ?"); query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Hard.to_demon_difficulty())); }, 4 => { query_params.push("demon_difficulty = ?"); query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Insane.to_demon_difficulty())); }, 5 => { query_params.push("demon_difficulty = ?"); query_params_bind.push(Box::new(helpers::difficulty::DemonDifficulty::Extreme.to_demon_difficulty())); }, _ => panic!("invalid demon filter!") } query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)"); query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty())); query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Demon.to_star_difficulty())); }, None => panic!("demon filter option with no demon filter argument") }, "-3" => { query_params.push("difficulty = ? OR (difficulty IS NULL AND community_difficulty = ?)"); query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty())); query_params_bind.push(Box::new(helpers::difficulty::LevelDifficulty::Auto.to_star_difficulty())); }, // easy, normal, hard, harder, insane _ => { let clean_diffs: Vec = diff.split(',') .map(|v| v.parse::().expect("couldnt parse i32")) .collect(); let diffs_str = clean_diffs.iter().map(|n| n.to_string()).collect::>().join(", "); query_params.push("difficulty IN (?) OR (difficulty IS NULL AND community_difficulty IN (?))"); query_params_bind.push(Box::new(diffs_str.clone())); query_params_bind.push(Box::new(diffs_str)); } } } } if let Some(search_type) = input.r#type { match search_type { // downloads 1 => { order = "levels.downloads DESC"; }, // likes 2 => { order = "levels.likes DESC"; }, // trending 3 => { unimplemented!("no trending sort :("); }, // recent 4 => { unimplemented!("no recent sort :(") // count query order doesnt matter } // creator levels 5 => { if let Some(1) = input.local { if let Some(input_account_id) = input.accountID { let (user_id_val, _account_id_val): (i64, i64); match helpers::accounts::auth(input_account_id, input.password.clone(), input.gjp.clone(), input.gjp2.clone()).await { Ok((user_id_val_auth, account_id_val_auth)) => { user_id_val = user_id_val_auth; _account_id_val = account_id_val_auth; }, Err(_) => return status::Custom(Status::Ok, "-1") }; if user_id_val == search_query.parse::().expect("couldnt convert query input to i64") { can_see_unlisted = true; query_params.push("levels.user_id = ?"); query_params_bind.push(Box::new(user_id_val)); } else { return status::Custom(Status::Ok, "-1") } } } if let None = input.local { let user_id_val = search_query.parse::().expect("couldnt convert query input to i64"); query_params.push("levels.user_id = ?"); query_params_bind.push(Box::new(user_id_val)); } } // featured // 17 is gdworld 6 | 17 => { query_params.push("featured = 1"); }, // epic / HoF 16 => { query_params.push("epic = 1"); }, // magic 7 => { query_params.push("objects > 4000"); }, // map packs 🙄😶 10 | 19 => { unimplemented!("no map packs yet buddy") }, // rated 11 => { query_params.push("levels.stars IS NOT NULL"); }, // followed 12 => { unimplemented!("no followed yet (i actually *could* implement this, but you cant follow yet so its useless)") }, // friends 13 => { unimplemented!("no friends") }, // daily 21 => { unimplemented!("no daily") }, // weekly 22 => { unimplemented!("no weekly") }, // event (honestly idk what this is i think it got leaked from 2.2 or something) 23 => { unimplemented!("no event") }, // default sort // 15 is gdworld 0 | 15 | _ => { order = "likes DESC"; }, } } if !can_see_unlisted { query_params.push("unlisted = 0"); } let where_str = format!("WHERE ({})", query_params.join(" AND ")); let query_base = format!("FROM levels JOIN users ON levels.user_id = users.id {} ORDER BY {}", where_str, order); let mut query_builder: QueryBuilder = QueryBuilder::new(&format!("SELECT levels.id, levels.name, users.id as user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username as user_username, users.udid as user_udid, users.account_id as user_account_id, users.registered as user_registered, editor_time, editor_time_copies {}", query_base)); let mut query = query_builder.build_query_as::(); for param in query_params_bind { query = query.bind(param.to_string()); } let mut results: Vec = vec![]; let mut users: Vec = vec![]; let mut songs: Vec = vec![]; let mut hash_data: Vec<(i64, i32, bool)> = vec![]; let count: i64 = sqlx::query_scalar(&format!("SELECT COUNT(*) {}", query_base)) .fetch_one(&mut connection) .await .expect("error getting level count"); // for goop in { // query // .fetch_all(&mut connection) // .await // .expect("error loading levels") // } { // println!("lvl id: {:?}", goop.id); // println!("user id: {:?}", goop.user_id); // } for result in { query .fetch_all(&mut connection) .await .expect("error loading levels") } { let set_difficulty = result.difficulty.map(helpers::difficulty::LevelDifficulty::new); let community_difficulty = result.community_difficulty.map(helpers::difficulty::LevelDifficulty::new); let difficulty = set_difficulty.or(community_difficulty); let demon_difficulty = result.demon_difficulty.map(helpers::difficulty::DemonDifficulty::new); results.push(helpers::format::format(hashmap! { 1 => result.id.to_string(), 2 => result.name, 3 => general_purpose::URL_SAFE.encode(result.description), 5 => result.version.to_string(), 6 => result.user_id.to_string(), // this argument is weird. its the "difficulty divisor" // used to be vote count but yeah 8 => 10.to_string(), 9 => (match difficulty { Some(diff) => diff.to_star_difficulty(), None => 0 } * 10).to_string(), 10 => result.downloads.to_string(), 12 => (if result.song_id < 50 { result.song_id } else { 0 }).to_string(), 13 => result.game_version.to_string(), 14 => result.likes.to_string(), 16 => (-result.likes).to_string(), 15 => result.length.to_string(), 17 => match difficulty { Some(diff) => { if diff == helpers::difficulty::LevelDifficulty::Demon { 1 } else { 0 } }, None => 0 }.to_string(), 18 => (if let Some(stars) = result.stars { stars } else { 0 }).to_string(), 19 => result.featured.to_string(), 25 => match difficulty { Some(diff) => { if diff == helpers::difficulty::LevelDifficulty::Auto { 1 } else { 0 } }, None => 0 }.to_string(), 30 => (if let Some(original) = result.original { original } else { 0 }).to_string(), 31 => result.two_player.to_string(), 35 => (if result.song_id >= 50 { result.song_id } else { 0 }).to_string(), 37 => result.coins.to_string(), 38 => result.rated_coins.to_string(), 39 => (if let Some(requested_stars) = result.requested_stars { requested_stars } else { 0 }).to_string(), 40 => result.has_ldm.to_string(), 42 => result.epic.to_string(), 43 => match demon_difficulty { Some(diff) => { diff }, None => helpers::difficulty::DemonDifficulty::Hard }.to_demon_difficulty().to_string(), 45 => result.objects.to_string(), 46 => result.editor_time.to_string(), 47 => result.editor_time_copies.to_string() })); users.push(format!("{}:{}:{}", result.user_id, result.user_username, { if result.user_registered == 1 { result.user_account_id.expect("wtf? registered user with no account id.").to_string() } else { result.user_udid.expect("wtf? unregistered user with no udid.") } })); hash_data.push(( result.id, { if let Some(stars) = result.stars { stars } else { 0 }}, { if let 1 = result.rated_coins { true } else { false }} )); } let search_meta = format!("{}:{}:{}", count, page_offset, 10); let response = vec![results.join("|"), users.join("|"), songs.join("|"), search_meta, helpers::encryption::gen_multi(hash_data)].join("#"); return status::Custom(Status::Ok, Box::leak(response.into_boxed_str())) }