From e591b110656b58049975180249cde9eed5e6fcb1 Mon Sep 17 00:00:00 2001 From: reidlab Date: Mon, 11 Sep 2023 20:00:20 -0700 Subject: [PATCH] some small refactoring --- Cargo.lock | 246 ++++++++++ Cargo.toml | 2 + public/favicon.png | Bin 0 -> 478 bytes public/robots.txt | 2 + public/style.css | 45 ++ readme.md | 14 +- src/db/models.rs | 2 +- .../accounts/update_account_settings.rs | 1 - src/endpoints/levels.rs | 1 + src/endpoints/levels/get_levels.rs | 439 ++++++++++++++++++ src/endpoints/levels/upload_level.rs | 4 +- src/endpoints/users/get_users.rs | 42 +- src/helpers/difficulty.rs | 83 +++- src/helpers/encryption.rs | 20 + src/helpers/format.rs | 2 +- src/helpers/levels.rs | 13 +- src/main.rs | 28 +- src/template_endpoints.rs | 1 + src/template_endpoints/index.rs | 18 + templates/index.html.hbs | 34 ++ 20 files changed, 957 insertions(+), 40 deletions(-) create mode 100644 public/favicon.png create mode 100644 public/robots.txt create mode 100644 public/style.css create mode 100644 src/endpoints/levels/get_levels.rs create mode 100644 src/template_endpoints.rs create mode 100644 src/template_endpoints/index.rs create mode 100644 templates/index.html.hbs diff --git a/Cargo.lock b/Cargo.lock index b10230d..90221a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,6 +190,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -352,6 +371,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.48.0", +] + [[package]] name = "flate2" version = "1.0.27" @@ -368,6 +399,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.28" @@ -443,8 +483,10 @@ dependencies = [ "flate2", "maplit", "password-auth", + "rand", "regex", "rocket", + "rocket_dyn_templates", "serde", "sha", "toml", @@ -511,6 +553,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -614,6 +670,26 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -640,6 +716,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -757,6 +853,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "normpath" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "notify" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -864,6 +987,51 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1079,6 +1247,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "rocket_dyn_templates" +version = "0.1.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276cac97fcddca93d741a4a530f58969f45a5bdb587f8c6b04c75cf849ca7f4c" +dependencies = [ + "glob", + "handlebars", + "normpath", + "notify", + "rocket", +] + [[package]] name = "rocket_http" version = "0.5.0-rc.3" @@ -1131,6 +1312,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1192,6 +1382,17 @@ dependencies = [ "bswap", ] +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1289,6 +1490,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.7" @@ -1504,6 +1725,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uncased" version = "0.9.9" @@ -1544,6 +1771,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1629,6 +1866,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 03b9da2..d7b232b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,10 @@ dotenvy = "0.15.7" flate2 = "1.0.27" maplit = "1.0.2" password-auth = "0.3.0" +rand = "0.8.5" regex = "1.9.4" rocket = "=0.5.0-rc.3" +rocket_dyn_templates = { version = "0.1.0-rc.3", features = ["handlebars"] } serde = { version = "1.0.188", features = ["derive"] } sha = "1.0.3" toml = "0.7.6" diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..9b562aaff4ccc674481408150a8e7d877d9a43e6 GIT binary patch literal 478 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hEk44ofy`glX=O&z&OLx z#WAEJ?(NKnLM;j$EwBH|@0z`H^)n;hh{7p%eoHIV=tSMyq|Z;ub5JjSGYysO1=-n?Qg5oWAk~u1NQSZ$TMzHVdy__@nrF|Q(ikf?mxJ`>7B6| zBg2fPkzuVeuAEo7PUJKdGX#Xb%x*F9KEuEnWp)1OQ7zsHF-KJ=F4)U(WzQZ_M}dYF z6L&j`ur8@goBcP9b&puX7V#N&yOgvRNH^Tuynb(e#xe$`6ZiV2yl36Se&%p(!&e8d zTc!JzWiw2rTI*A!u)HQSjc%!$=^=}Xw_AkgrO( zdk2?3&$JEuttH12(E6VBoE#TNZzF@#_VbFBS_c@DUI>IZy!d9R;?HW~z$4ql#c|iJ j{mDuO_Ktgreen name users... -- add more old endpoints -- better support for older versions \ No newline at end of file +- 2.2 friends only unlisted +- add more old endpoints + better support for older versions +- add dailies, events, weekly +- add defaults to more parameters +- better way for checking if song is custom (currently `id > 50`) +- sqlite would make sense for this +- unscuff difficulties +- moderation utilities \ No newline at end of file diff --git a/src/db/models.rs b/src/db/models.rs index 464f162..55a12b9 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -130,7 +130,7 @@ pub struct NewLevel { pub name: String, pub user_id: i32, pub description: String, - pub original: i32, + pub original: Option, pub game_version: i32, pub binary_version: i32, pub password: Option, diff --git a/src/endpoints/accounts/update_account_settings.rs b/src/endpoints/accounts/update_account_settings.rs index 23b3fff..380a3e9 100644 --- a/src/endpoints/accounts/update_account_settings.rs +++ b/src/endpoints/accounts/update_account_settings.rs @@ -31,7 +31,6 @@ pub fn update_account_settings(input: Form) -> status // account verification let (_user_id_val, account_id_val): (i32, i32); - // password argument is used for the level, so match helpers::accounts::auth(input.accountID.clone(), input.password.clone(), input.gjp.clone(), input.gjp2.clone()) { Ok((user_id, account_id)) => { _user_id_val = user_id; diff --git a/src/endpoints/levels.rs b/src/endpoints/levels.rs index 9d965be..81e45af 100644 --- a/src/endpoints/levels.rs +++ b/src/endpoints/levels.rs @@ -1 +1,2 @@ +pub mod get_levels; pub mod upload_level; \ No newline at end of file diff --git a/src/endpoints/levels/get_levels.rs b/src/endpoints/levels/get_levels.rs new file mode 100644 index 0000000..0e83b67 --- /dev/null +++ b/src/endpoints/levels/get_levels.rs @@ -0,0 +1,439 @@ +use rocket::form::Form; +use rocket::http::Status; +use rocket::response::status; + +use diesel::prelude::*; + +use base64::{Engine as _, engine::general_purpose}; + +use crate::helpers; +use crate::db; + +#[derive(FromForm)] +pub struct FormGetLevels { + page: i64, + str: String, + + 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 fn get_levels(input: Form) -> status::Custom<&'static str> { + let connection = &mut db::establish_connection_pg(); + + use crate::schema::{levels, users}; + + use crate::models::{Level, User}; + + let mut can_see_unlisted = false; + + let mut query = levels::table.into_boxed(); + let mut count_query = levels::table.into_boxed(); + + if input.str != "" && input.r#type != Some(5) && input.r#type != Some(10) && input.r#type != Some(19) { + match input.str.parse::() { + Ok(matched_id) => { + can_see_unlisted = true; + query = query.filter(levels::id.eq(matched_id)); + count_query = count_query.filter(levels::id.eq(matched_id)) + }, + Err(_) => { + query = query.filter(levels::name.ilike(input.str.to_owned() + "%")); + count_query = count_query.filter(levels::name.ilike(input.str.to_owned() + "%")) + } + } + } + + if let Some(1) = input.featured { + query = query.filter(levels::featured.eq(1)); + count_query = count_query.filter(levels::featured.eq(1)) + } + if let Some(1) = input.original { + query = query.filter(levels::original.is_null()); + count_query = count_query.filter(levels::original.is_null()) + } + if let Some(1) = input.coins { + query = query.filter(levels::rated_coins.eq(1).and(levels::coins.ne(0))); + count_query = count_query.filter(levels::rated_coins.eq(1).and(levels::coins.ne(0))) + } + if let Some(1) = input.epic { + query = query.filter(levels::epic.eq(1)); + count_query = count_query.filter(levels::epic.eq(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 i32")) + .collect(); + query = query.filter(levels::id.ne_all(clean_levels.clone())); + count_query = count_query.filter(levels::id.ne_all(clean_levels)) + }, + 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 i32")) + .collect(); + query = query.filter(levels::id.eq_any(clean_levels.clone())); + count_query = count_query.filter(levels::id.eq_any(clean_levels)) + }, + None => return status::Custom(Status::Ok, "-1") + } + } + if let Some(song_id) = input.song { + if let Some(custom_song) = input.customSong { + query = query.filter(levels::song_id.eq(custom_song)); + count_query = count_query.filter(levels::song_id.eq(custom_song)) + } else { + query = query.filter(levels::song_id.eq(song_id)); + count_query = count_query.filter(levels::song_id.eq(song_id)); + } + } + if let Some(1) = input.twoPlayer { + query = query.filter(levels::two_player.eq(1)); + count_query = count_query.filter(levels::two_player.eq(1)) + } + if let Some(1) = input.star { + query = query.filter(levels::stars.is_not_null()); + count_query = count_query.filter(levels::stars.is_not_null()) + } + if let Some(1) = input.noStar { + query = query.filter(levels::stars.is_null()); + count_query = count_query.filter(levels::stars.is_null()) + } + if let Some(_gauntlet_id) = input.gauntlet { + unimplemented!("no gauntlet support") + } + if let Some(len) = input.len { + query = query.filter(levels::length.eq(len)); + count_query = count_query.filter(levels::length.eq(len)) + } + if let Some(diff) = input.diff.clone() { + if diff != "-" { + match diff.as_str() { + "-1" => { + query = query.filter(levels::difficulty.is_null().and(levels::community_difficulty.is_null())); + count_query = count_query.filter(levels::difficulty.is_null().and(levels::community_difficulty.is_null())) + }, + "-2" => match input.demonFilter { + Some(demon_filter) => { + match demon_filter { + 1 => { + query = query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Easy.to_demon_difficulty())); + count_query = count_query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Easy.to_demon_difficulty())) + }, + 2 => { + query = query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Medium.to_demon_difficulty())); + count_query = count_query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Medium.to_demon_difficulty())) + }, + 3 => { + query = query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Hard.to_demon_difficulty())); + count_query = count_query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Hard.to_demon_difficulty())) + }, + 4 => { + query = query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Insane.to_demon_difficulty())); + count_query = count_query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Insane.to_demon_difficulty())) + }, + 5 => { + query = query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Extreme.to_demon_difficulty())); + count_query = count_query.filter(levels::demon_difficulty.eq::(crate::difficulty::DemonDifficulty::Extreme.to_demon_difficulty())) + }, + _ => panic!("invalid demon filter!") + } + query = query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty())))); + count_query = count_query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::(crate::difficulty::LevelDifficulty::Demon.to_star_difficulty())))) + }, + None => panic!("demon filter option with no demon filter argument") + }, + "-3" => { + query = query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty())))); + count_query = count_query.filter(diesel::BoolExpressionMethods::or(levels::difficulty.eq::(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty()), levels::difficulty.is_null().and(levels::community_difficulty.eq::(crate::difficulty::LevelDifficulty::Auto.to_star_difficulty())))) + }, + // easy, normal, hard, harder, insane + _ => { + let diffs: Vec = diff.split(',') + .map(|v| v.parse::().expect("couldnt parse i32")) + .collect(); + query = query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs.clone())))); + count_query = count_query.filter(levels::difficulty.eq_any(diffs.clone()).or(levels::difficulty.is_null().and(levels::community_difficulty.eq_any(diffs)))) + } + } + } + } + + if let Some(search_type) = input.r#type { + match search_type { + // downloads + 1 => { + query = query.order(levels::downloads.desc()); + // count query order doesnt matter + }, + // likes + 2 => { + query = query.order(levels::likes.desc()); + // count query order doesnt matter + }, + // 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): (i32, i32); + + match helpers::accounts::auth(input_account_id, input.password.clone(), input.gjp.clone(), input.gjp2.clone()) { + 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 == input.str.parse::().expect("couldnt convert query input to i32") { + can_see_unlisted = true; + } else { + return status::Custom(Status::Ok, "-1") + } + } + } + } + // featured + // 17 is gdworld + 6 | 17 => { + query = query.filter(levels::featured.eq(1)); + count_query = count_query.filter(levels::featured.eq(1)) + }, + // epic / HoF + 16 => { + query = query.filter(levels::epic.eq(1)); + count_query = count_query.filter(levels::epic.eq(1)) + }, + // magic + 7 => { + query = query.filter(levels::objects.gt(4000)); + count_query = count_query.filter(levels::objects.gt(4000)) + }, + // map packs 🙄😶 + 10 | 19 => { + unimplemented!("no map packs yet buddy") + }, + // rated + 11 => { + query = query.filter(levels::stars.is_not_null()); + count_query = count_query.filter(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 | _ => { + query = query.order(levels::likes.desc()); + // count query order doesnt matter + }, + } + } + + if !can_see_unlisted { + query = query.filter(levels::unlisted.eq(0)); + count_query = count_query.filter(levels::unlisted.eq(0)) + } + + let mut results: Vec = [].to_vec(); + let mut users: Vec = [].to_vec(); + let mut songs: Vec = [].to_vec(); + + let mut hash_data: Vec<(i32, i32, bool)> = [].to_vec(); + + for result in { + query + .order(levels::created_at.desc()) + .offset(input.page * 10) + .limit(10) + .get_results::(connection) + .expect("fatal error loading levels") + } { + let user: User = users::table.find(result.user_id).get_result::(connection).expect("couldnt get user from lvl"); + let level: Level = result; + + let set_difficulty = match level.difficulty { + Some(diff) => { + Some(helpers::difficulty::LevelDifficulty::new(diff)) + }, + None => None + }; + let community_difficulty = match level.community_difficulty { + Some(diff) => { + Some(helpers::difficulty::LevelDifficulty::new(diff)) + }, + None => None + }; + let difficulty = match set_difficulty { + Some(diff) => { + Some(diff) + }, + None => { + match community_difficulty { + Some(diff) => { + Some(diff) + }, + None => None + } + } + }; + let demon_difficulty = match level.demon_difficulty { + Some(diff) => { + Some(helpers::difficulty::DemonDifficulty::new(diff)) + }, + None => None + }; + + results.push(helpers::format::format(hashmap! { + 1 => level.id.to_string(), + 2 => level.name, + 3 => general_purpose::URL_SAFE.encode(level.description), + 5 => level.version.to_string(), + 6 => 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 => level.downloads.to_string(), + 12 => (if level.song_id < 50 { level.song_id } else { 0 }).to_string(), + 13 => level.game_version.to_string(), + 14 => level.likes.to_string(), + 16 => (-level.likes).to_string(), + 15 => level.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) = level.stars { stars } else { 0 }).to_string(), + 19 => level.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) = level.original { original } else { 0 }).to_string(), + 31 => level.two_player.to_string(), + 35 => (if level.song_id >= 50 { level.song_id } else { 0 }).to_string(), + 37 => level.coins.to_string(), + 38 => level.rated_coins.to_string(), + 39 => (if let Some(requested_stars) = level.requested_stars { requested_stars } else { 0 }).to_string(), + 40 => level.has_ldm.to_string(), + 42 => level.epic.to_string(), + 43 => match demon_difficulty { + Some(diff) => { + diff + }, + None => helpers::difficulty::DemonDifficulty::Hard + }.to_demon_difficulty().to_string(), + 45 => level.objects.to_string(), + 46 => level.editor_time.to_string(), + 47 => level.editor_time_copies.to_string() + })); + + users.push(format!("{}:{}:{}", user.id, user.username, { + if user.registered == 1 { + user.account_id.expect("wtf? registered user with no account id.").to_string() + } else { + user.udid.expect("wtf? unregistered user with no udid.") + } + })); + + hash_data.push(( + level.id, + { if let Some(stars) = level.stars { + stars + } else { + 0 + }}, + { if let 1 = level.rated_coins { + true + } else { + println!("{}", "no rated coin"); + false + }} + )); + }; + + let level_count = count_query + .count() + .get_result::(connection) + .expect("failed to get count of levels"); + + let search_meta = format!("{}:{}:{}", level_count, input.page * 10, 10); + + let response = vec![results.join("|"), users.join("|"), songs.join("|"), search_meta, helpers::encryption::gen_multi(hash_data)].join("#"); + println!("{}", response); + + return status::Custom(Status::Ok, Box::leak(response.into_boxed_str())) +} + +//1:93455181:2:Siinamota:5:1:6:158483568:8:10:9:40:10:2171:12:0:13:21:14:40:17::43:5:25::18:0:19:0:42:0:45:3318:3:QnkgSm9yZ2UwMDFZVCAgcGFyYSBlc2N1Y2hhciBsYSBDYW5jaW9uIE5lY2VzaXRhcyBSZW1wbGF6YXIgZXN0YSA6XSBwYXMgMDAwMDAw:15:1:30:0:31:0:37:1:38:0:39:5:46:1:47:2:35:633211#158483568:Jorge001yt:16248905#1~|~633211~|~2~|~Random Song 04~|~3~|~42023~|~4~|~Zhenmuron~|~5~|~0.31~|~6~|~~|~10~|~http%3A%2F%2Faudio.ngfiles.com%2F633000%2F633211_Random-Song-04.mp3~|~7~|~~|~8~|~1#9999:0:10#d19e918b852b706b20e7fbc31bbb07d92efda123 \ No newline at end of file diff --git a/src/endpoints/levels/upload_level.rs b/src/endpoints/levels/upload_level.rs index 5544124..3bacd7a 100644 --- a/src/endpoints/levels/upload_level.rs +++ b/src/endpoints/levels/upload_level.rs @@ -141,7 +141,7 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str length.eq(level_length_val), objects.eq(objects_val as i32), coins.eq(coins_val as i32), - has_ldm.eq(input.ldm.unwrap_or(0)), + has_ldm.eq(input.ldm.unwrap_or(0).clamp(0, 1)), two_player.eq(two_player_val) )) .get_result::(connection) @@ -156,7 +156,7 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str name: helpers::clean::clean_basic(&input.levelName).chars().take(20).collect(), user_id: user_id_val, description: description_val.chars().take(140).collect(), - original: input.original.unwrap_or(0), + original: input.original, game_version: input.gameVersion, binary_version: input.binaryVersion.unwrap_or(0), password: input.password.clone(), diff --git a/src/endpoints/users/get_users.rs b/src/endpoints/users/get_users.rs index 6db748b..268f72d 100644 --- a/src/endpoints/users/get_users.rs +++ b/src/endpoints/users/get_users.rs @@ -38,31 +38,33 @@ pub fn get_users(input: Form) -> status::Custom<&'static str> { .get_results::(connection) .expect("Fatal error loading users") } { + let user: User = result; + let formatted_result = helpers::format::format(hashmap! { - 1 => result.username, - 2 => result.id.to_string(), - 3 => result.stars.to_string(), - 4 => result.demons.to_string(), - 8 => result.creator_points.to_string(), + 1 => user.username, + 2 => user.id.to_string(), + 3 => user.stars.to_string(), + 4 => user.demons.to_string(), + 8 => user.creator_points.to_string(), 9 => { vec![ - result.cube, - result.ship, - result.ball, - result.ufo, - result.wave, - result.robot - ][result.icon_type as usize].to_string() + user.cube, + user.ship, + user.ball, + user.ufo, + user.wave, + user.robot + ][user.icon_type as usize].to_string() }, - 10 => result.color1.to_string(), - 11 => result.color2.to_string(), - 13 => result.coins.to_string(), - 14 => result.icon_type.to_string(), - 15 => result.special.to_string(), + 10 => user.color1.to_string(), + 11 => user.color2.to_string(), + 13 => user.coins.to_string(), + 14 => user.icon_type.to_string(), + 15 => user.special.to_string(), 16 => { - match result.account_id { + match user.account_id { Some(account_id_value) => account_id_value.to_string(), - None => match result.udid { + None => match user.udid { Some(udid_value) => udid_value.to_string(), None => panic!("user has no account_id or udid?!?!?") } @@ -83,7 +85,7 @@ pub fn get_users(input: Form) -> status::Custom<&'static str> { let amount = query_users_count .count() .get_result::(connection) - .expect("Error querying user count"); + .expect("error querying user count"); let response = if results.is_empty() { String::from("-1") diff --git a/src/helpers/difficulty.rs b/src/helpers/difficulty.rs index 38f1a8b..6209bfc 100644 --- a/src/helpers/difficulty.rs +++ b/src/helpers/difficulty.rs @@ -1,3 +1,4 @@ +#[derive(PartialEq, Copy, Clone)] pub enum LevelDifficulty { Auto, Easy, @@ -9,7 +10,32 @@ pub enum LevelDifficulty { } impl LevelDifficulty { - pub fn to_star_difficulty(&self) -> i32 { + pub fn new(value: i32) -> LevelDifficulty { + match value { + 0 => LevelDifficulty::Auto, + 1 => LevelDifficulty::Easy, + 2 => LevelDifficulty::Normal, + 3 => LevelDifficulty::Hard, + 4 => LevelDifficulty::Harder, + 5 => LevelDifficulty::Insane, + 6 => LevelDifficulty::Demon, + _ => panic!("invalid level difficulty") + } + } + + pub fn value(self) -> i32 { + match self { + LevelDifficulty::Auto => 0, + LevelDifficulty::Easy => 1, + LevelDifficulty::Normal => 2, + LevelDifficulty::Hard => 3, + LevelDifficulty::Harder => 4, + LevelDifficulty::Insane => 5, + LevelDifficulty::Demon => 6, + } + } + + pub fn to_star_difficulty(self) -> i32 { match self { LevelDifficulty::Auto => 5, LevelDifficulty::Easy => 1, @@ -20,4 +46,59 @@ impl LevelDifficulty { LevelDifficulty::Demon => 5, } } + + pub fn stars_to_diff(stars: i32) -> Self { + match stars { + 1 => LevelDifficulty::Auto, + 2 => LevelDifficulty::Easy, + 3 => LevelDifficulty::Normal, + 4 | 5 => LevelDifficulty::Hard, + 6 | 7 => LevelDifficulty::Harder, + 8 | 9 => LevelDifficulty::Insane, + 10 => LevelDifficulty::Demon, + _ => panic!("invalid difficulty!") + } + } +} + +pub enum DemonDifficulty { + Easy, + Medium, + Hard, + Insane, + Extreme +} + +impl DemonDifficulty { + pub fn new(value: i32) -> DemonDifficulty { + match value { + 0 => DemonDifficulty::Easy, + 1 => DemonDifficulty::Medium, + 2 => DemonDifficulty::Hard, + 3 => DemonDifficulty::Insane, + 4 => DemonDifficulty::Extreme, + 5 => DemonDifficulty::Insane, + _ => panic!("invalid demon difficulty") + } + } + + pub fn value(self) -> i32 { + match self { + DemonDifficulty::Easy => 0, + DemonDifficulty::Medium => 1, + DemonDifficulty::Hard => 2, + DemonDifficulty::Insane => 3, + DemonDifficulty::Extreme => 4 + } + } + + pub fn to_demon_difficulty(self) -> i32 { + match self { + DemonDifficulty::Easy => 3, + DemonDifficulty::Medium => 4, + DemonDifficulty::Hard => 0, + DemonDifficulty::Insane => 5, + DemonDifficulty::Extreme => 6 + } + } } \ No newline at end of file diff --git a/src/helpers/encryption.rs b/src/helpers/encryption.rs index ab4ef12..303c1de 100644 --- a/src/helpers/encryption.rs +++ b/src/helpers/encryption.rs @@ -27,4 +27,24 @@ pub fn decode_gjp(gjp: String) -> String { let base64_decoded = String::from_utf8(general_purpose::STANDARD.decode(gjp).expect("couldn't decode base64")).expect("invalid UTF-8 sequence (how)"); let xor_decoded = cyclic_xor_string(&base64_decoded, "37526"); return xor_decoded +} + +pub fn gen_multi(level_hash_data: Vec<(i32, i32, bool)>) -> String { + let mut input_str = String::new(); + + for (_index, val) in level_hash_data.iter().enumerate() { + let (level_id, stars, coins) = val; + let level_id_str = level_id.to_string(); + + input_str.push(level_id_str.chars().nth(0).unwrap()); + input_str.push(level_id_str.chars().last().unwrap()); + input_str.push_str(&stars.to_string()); + input_str.push_str(if *coins { "1" } else { "0" }); + } + + let mut bytes: Vec = Vec::new(); + bytes.extend(input_str.as_bytes().to_vec()); + bytes.extend("xI25fpAapCQg".as_bytes().to_vec()); + + return Sha1::default().digest(bytes.as_mut_slice()).to_hex(); } \ No newline at end of file diff --git a/src/helpers/format.rs b/src/helpers/format.rs index beaf5ae..93faabd 100644 --- a/src/helpers/format.rs +++ b/src/helpers/format.rs @@ -12,7 +12,7 @@ pub fn format(map: HashMap) -> String { } } - if !result.ends_with(":") { + if result.ends_with(":") { result.pop(); } diff --git a/src/helpers/levels.rs b/src/helpers/levels.rs index b9e8e58..eb5da08 100644 --- a/src/helpers/levels.rs +++ b/src/helpers/levels.rs @@ -5,6 +5,8 @@ use base64::{Engine as _, engine::general_purpose}; use flate2::read::GzDecoder; +use std::collections::HashMap; + pub static DEFAULT_EXTRA_STRING: LazyLock = LazyLock::new(|| { let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29"); @@ -19,7 +21,6 @@ macro_rules! object_prop_bool { }; } -use std::collections::HashMap; #[derive(Clone)] pub struct ObjectData { @@ -54,8 +55,8 @@ pub enum PortalSpeed { VeryFast } -impl PortalSpeed { - pub fn portal_speed(&self) -> f64 { +impl Into for PortalSpeed { + fn into(self) -> f64 { match self { PortalSpeed::Slow => 251.16, PortalSpeed::Normal => 311.58, @@ -78,12 +79,12 @@ pub fn id_to_portal_speed(id: i32) -> Option { } pub fn get_seconds_from_xpos(pos: f64, start_speed: PortalSpeed, portals: Vec) -> f64 { - let mut speed; + let mut speed: f64; let mut last_obj_pos = 0.0; let mut last_segment = 0.0; let mut segments = 0.0; - speed = start_speed.portal_speed(); + speed = start_speed.into(); if portals.is_empty() { return pos / speed @@ -97,7 +98,7 @@ pub fn get_seconds_from_xpos(pos: f64, start_speed: PortalSpeed, portals: Vec String { return String::from("gdps-server | https://git.reidlab.online/reidlab/gdps-server"); } +#[get("/")] +async fn files(file: PathBuf) -> Option { + NamedFile::open(Path::new("public/").join(file)).await.ok() +} + #[launch] fn rocket() -> _ { - fs::create_dir_all(&CONFIG.db.data_folder).expect("failed to create data directory!"); + // this is a bit scuffed + fs::create_dir_all(&CONFIG.db.data_folder).expect("failed to create data directory! (probably a permission err)"); fs::create_dir_all(format!("{}/levels", &CONFIG.db.data_folder)).expect("failed to create data directory for levels"); + rocket::build() .configure(rocket::Config::figment().merge(("port", CONFIG.general.port))) + // actual website .mount("/", routes![ - index, + template_endpoints::index::index ]) + // assets + .mount("/", routes![ + files + ]) + // GEOMETRY DASH https://www.youtube.com/watch?v=_pLrtsf5yfE .mount(CONFIG.general.append_path.as_str(), routes![ endpoints::accounts::login_account::login_account, endpoints::accounts::register_account::register_account, @@ -39,6 +60,9 @@ fn rocket() -> _ { endpoints::users::get_users::get_users, + endpoints::levels::get_levels::get_levels, endpoints::levels::upload_level::upload_level ]) + // so templates work i think + .attach(Template::fairing()) } \ No newline at end of file diff --git a/src/template_endpoints.rs b/src/template_endpoints.rs new file mode 100644 index 0000000..5d4ca1d --- /dev/null +++ b/src/template_endpoints.rs @@ -0,0 +1 @@ +pub mod index; \ No newline at end of file diff --git a/src/template_endpoints/index.rs b/src/template_endpoints/index.rs new file mode 100644 index 0000000..029c427 --- /dev/null +++ b/src/template_endpoints/index.rs @@ -0,0 +1,18 @@ +use rocket_dyn_templates::{Template, context}; + +use rand::Rng; + +#[get("/")] +pub fn index() -> Template { + let silly_strings: Vec<&str> = vec![ + "the trianges consume", + "geomtry das" + ]; + + let mut rng = rand::thread_rng(); + let random_index = rng.gen_range(0..silly_strings.len()); + + let silly_string = silly_strings[random_index]; + + Template::render("index", context! { silly_string: silly_string }) +} diff --git a/templates/index.html.hbs b/templates/index.html.hbs new file mode 100644 index 0000000..ee2b5c4 --- /dev/null +++ b/templates/index.html.hbs @@ -0,0 +1,34 @@ + + + + + + + + + gdps-server + + + +

+ + gdps-server +

+
+

+ gdps-server is a WIP Geometry Dash private server written in Rust. +

+

+ You can check out other shit here: +
+ +

  • The Git repository
  • + +

    +
    +
    + {{ silly_string }} +
    + + + \ No newline at end of file