From 4d8a41ba47cb097f372016cee6fd757e2d926689 Mon Sep 17 00:00:00 2001
From: reidlab
Date: Wed, 20 Sep 2023 18:46:37 -0700
Subject: [PATCH] reuploading levels
---
Cargo.lock | 323 ++++++++++++++++++++-
Cargo.toml | 3 +
config.example.toml | 4 +-
migrations/2023-09-03-032651_levels/up.sql | 11 +-
public/style.css | 2 +
readme.md | 8 +-
src/config.rs | 5 +-
src/db/models.rs | 2 -
src/db/schema.rs | 1 -
src/endpoints/levels/download_level.rs | 2 +-
src/endpoints/levels/get_levels.rs | 1 -
src/endpoints/levels/upload_level.rs | 1 -
src/helpers.rs | 3 +-
src/helpers/levels.rs | 23 ++
src/helpers/reupload.rs | 59 ++++
src/main.rs | 11 +-
src/template_endpoints.rs | 3 +-
src/template_endpoints/reupload.rs | 130 +++++++++
templates/index.html.hbs | 1 +
templates/reupload.html.hbs | 33 +++
20 files changed, 604 insertions(+), 22 deletions(-)
create mode 100644 src/helpers/reupload.rs
create mode 100644 src/template_endpoints/reupload.rs
create mode 100644 templates/reupload.html.hbs
diff --git a/Cargo.lock b/Cargo.lock
index 90221a8..4d3039f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -172,6 +172,22 @@ dependencies = [
"version_check",
]
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
[[package]]
name = "cpufeatures"
version = "0.2.9"
@@ -399,6 +415,30 @@ 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.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@@ -485,9 +525,12 @@ dependencies = [
"password-auth",
"rand",
"regex",
+ "reqwest",
"rocket",
"rocket_dyn_templates",
+ "roxmltree",
"serde",
+ "serde_json",
"sha",
"toml",
]
@@ -643,6 +686,29 @@ dependencies = [
"want",
]
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -690,6 +756,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "ipnet"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
+
[[package]]
name = "is-terminal"
version = "0.4.9"
@@ -853,6 +925,24 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
[[package]]
name = "normpath"
version = "1.1.1"
@@ -906,6 +996,50 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+[[package]]
+name = "openssl"
+version = "0.10.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
+dependencies = [
+ "bitflags 2.4.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[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.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
[[package]]
name = "overload"
version = "0.1.1"
@@ -1044,6 +1178,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1193,6 +1333,43 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
+[[package]]
+name = "reqwest"
+version = "0.11.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-tls",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winreg",
+]
+
[[package]]
name = "rocket"
version = "0.5.0-rc.3"
@@ -1287,6 +1464,15 @@ dependencies = [
"uncased",
]
+[[package]]
+name = "roxmltree"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8f595a457b6b8c6cda66a48503e92ee8d19342f905948f29c383200ec9eb1d8"
+dependencies = [
+ "xmlparser",
+]
+
[[package]]
name = "rustix"
version = "0.38.9"
@@ -1321,6 +1507,15 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "schannel"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -1333,6 +1528,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "security-framework"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "serde"
version = "1.0.188"
@@ -1355,9 +1573,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.105"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
@@ -1373,6 +1591,18 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
[[package]]
name = "sha"
version = "1.0.3"
@@ -1548,6 +1778,21 @@ dependencies = [
"time-core",
]
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
[[package]]
name = "tokio"
version = "1.27.0"
@@ -1577,6 +1822,16 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
[[package]]
name = "tokio-stream"
version = "0.1.12"
@@ -1741,18 +1996,44 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+[[package]]
+name = "url"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
[[package]]
name = "valuable"
version = "0.1.0"
@@ -1821,6 +2102,18 @@ dependencies = [
"wasm-bindgen-shared",
]
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
@@ -1850,6 +2143,16 @@ version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+[[package]]
+name = "web-sys"
+version = "0.3.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2031,6 +2334,22 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "winreg"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "xmlparser"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
+
[[package]]
name = "yansi"
version = "0.5.1"
diff --git a/Cargo.toml b/Cargo.toml
index d7b232b..5161ef9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,8 +12,11 @@ maplit = "1.0.2"
password-auth = "0.3.0"
rand = "0.8.5"
regex = "1.9.4"
+reqwest = { version = "0.11.20", features = ["blocking"] }
rocket = "=0.5.0-rc.3"
rocket_dyn_templates = { version = "0.1.0-rc.3", features = ["handlebars"] }
+roxmltree = "0.18.0"
serde = { version = "1.0.188", features = ["derive"] }
+serde_json = "1.0.107"
sha = "1.0.3"
toml = "0.7.6"
diff --git a/config.example.toml b/config.example.toml
index d448962..16a518f 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -28,4 +28,6 @@ data_folder = "data"
# max amount of objects in a level
max_objects = 80_000
# object ids to block
-blocklist = [ 31 ] # start position
\ No newline at end of file
+blocklist = [ 31 ] # start position
+# if reuploading levels is allowed
+reupload = true
\ No newline at end of file
diff --git a/migrations/2023-09-03-032651_levels/up.sql b/migrations/2023-09-03-032651_levels/up.sql
index 3b53884..531b2b3 100644
--- a/migrations/2023-09-03-032651_levels/up.sql
+++ b/migrations/2023-09-03-032651_levels/up.sql
@@ -24,12 +24,11 @@ CREATE TABLE levels (
song_id INTEGER NOT NULL,
- length INTEGER NOT NULL,
- length_real DOUBLE PRECISION NOT NULL,
- objects INTEGER NOT NULL,
- coins INTEGER NOT NULL DEFAULT 0,
- has_ldm INTEGER NOT NULL DEFAULT 0,
- two_player INTEGER NOT NULL DEFAULT 0,
+ length INTEGER NOT NULL,
+ objects INTEGER NOT NULL,
+ coins INTEGER NOT NULL DEFAULT 0,
+ has_ldm INTEGER NOT NULL DEFAULT 0,
+ two_player INTEGER NOT NULL DEFAULT 0,
downloads INTEGER NOT NULL DEFAULT 0,
likes INTEGER NOT NULL DEFAULT 0,
diff --git a/public/style.css b/public/style.css
index a2b7024..f2dfc52 100644
--- a/public/style.css
+++ b/public/style.css
@@ -9,6 +9,8 @@
line-height: 1;
font-weight: 400;
+ color-scheme: light dark;
+
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
diff --git a/readme.md b/readme.md
index 3bd4777..83cb720 100644
--- a/readme.md
+++ b/readme.md
@@ -34,7 +34,6 @@ _these features are implemented_
## todo
-- __we're doing song uploading/rework and level reuploading next__
- swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!) this would also make the `28` (upload) and `29` (update) responses work on downloadlevel and the `4` (recent) on getlevels
- 2.2 friends only unlisted
- add more old endpoints + better support for older versions
@@ -43,4 +42,9 @@ _these features are implemented_
- moderation utilities
- ip actions
- better song support
-- return "-1" instead of panicking for stuff
\ No newline at end of file
+- return "-1" instead of panicking for stuff
+- authentication caching (ip? redis?)
+- idfk where to put this but i need to rant about this. why cant you have `get` and `post` in the same function for rocket. like??? why??
+- use log instead of println
+- find what the fuck level info is. gddocs just says "a random gzip string" like bro what
+- unscuff parsing
\ No newline at end of file
diff --git a/src/config.rs b/src/config.rs
index 8af07f6..4b73286 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,5 +1,7 @@
use serde::Deserialize;
+
use std::fs;
+
use std::sync::LazyLock;
#[derive(Deserialize)]
@@ -30,7 +32,8 @@ pub struct ConfigDB {
#[derive(Deserialize)]
pub struct ConfigLevels {
pub max_objects: i32,
- pub blocklist: Vec
+ pub blocklist: Vec,
+ pub reupload: bool
}
impl Config {
diff --git a/src/db/models.rs b/src/db/models.rs
index 55a12b9..7e6497f 100644
--- a/src/db/models.rs
+++ b/src/db/models.rs
@@ -108,7 +108,6 @@ pub struct Level {
pub editor_time_copies: i32,
pub song_id: i32,
pub length: i32,
- pub length_real: f64,
pub objects: i32,
pub coins: i32,
pub has_ldm: i32,
@@ -143,7 +142,6 @@ pub struct NewLevel {
pub editor_time_copies: i32,
pub song_id: i32,
pub length: i32,
- pub length_real: f64,
pub objects: i32,
pub coins: i32,
pub has_ldm: i32,
diff --git a/src/db/schema.rs b/src/db/schema.rs
index f116654..1a25502 100644
--- a/src/db/schema.rs
+++ b/src/db/schema.rs
@@ -46,7 +46,6 @@ diesel::table! {
editor_time_copies -> Int4,
song_id -> Int4,
length -> Int4,
- length_real -> Float8,
objects -> Int4,
coins -> Int4,
has_ldm -> Int4,
diff --git a/src/endpoints/levels/download_level.rs b/src/endpoints/levels/download_level.rs
index ebcc15b..bc8e692 100644
--- a/src/endpoints/levels/download_level.rs
+++ b/src/endpoints/levels/download_level.rs
@@ -93,7 +93,7 @@ pub fn download_level(input: Form) -> status::Custom<&'static
let xor_pass: String;
if input.gameVersion.unwrap_or(19) >= 20 {
- xor_pass = helpers::encryption::cyclic_xor_string(&level.password.clone().unwrap_or(String::from("0")), "26364")
+ xor_pass = general_purpose::URL_SAFE.encode(helpers::encryption::cyclic_xor_string(&level.password.clone().unwrap_or(String::from("0")), "26364"))
} else {
xor_pass = level.password.clone().unwrap_or(String::from("0"));
}
diff --git a/src/endpoints/levels/get_levels.rs b/src/endpoints/levels/get_levels.rs
index 00602be..714c728 100644
--- a/src/endpoints/levels/get_levels.rs
+++ b/src/endpoints/levels/get_levels.rs
@@ -45,7 +45,6 @@ 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;
diff --git a/src/endpoints/levels/upload_level.rs b/src/endpoints/levels/upload_level.rs
index 2ebdcba..ece7568 100644
--- a/src/endpoints/levels/upload_level.rs
+++ b/src/endpoints/levels/upload_level.rs
@@ -183,7 +183,6 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str
editor_time_copies: input.wt2.unwrap_or(0),
song_id: song_id_val,
length: level_length_val,
- length_real: level_length_secs,
objects: objects_val as i32,
coins: coins_val as i32,
has_ldm: input.ldm.unwrap_or(0).clamp(0, 1),
diff --git a/src/helpers.rs b/src/helpers.rs
index d583aa1..1c3391f 100644
--- a/src/helpers.rs
+++ b/src/helpers.rs
@@ -3,4 +3,5 @@ pub mod clean;
pub mod difficulty;
pub mod encryption;
pub mod format;
-pub mod levels;
\ No newline at end of file
+pub mod levels;
+pub mod reupload;
\ No newline at end of file
diff --git a/src/helpers/levels.rs b/src/helpers/levels.rs
index 5cf3777..acce802 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 roxmltree::Document;
+
use std::collections::HashMap;
pub static DEFAULT_EXTRA_STRING: LazyLock = LazyLock::new(|| {
@@ -13,6 +15,12 @@ pub static DEFAULT_EXTRA_STRING: LazyLock = LazyLock::new(|| {
return string;
});
+pub static DEFAULT_LEVEL_INFO: LazyLock = LazyLock::new(|| {
+ let string = String::from("");
+
+ return string;
+});
+
macro_rules! object_prop_bool {
($key:expr, $name:ident) => {
pub fn $name(&self) -> bool {
@@ -167,6 +175,21 @@ pub fn parse(raw_level_data: &str) -> Vec> {
.collect()
}
+pub fn gmd_parse(gmd_file: &str) -> HashMap {
+ let doc = Document::parse(gmd_file).expect("failed to parse gmd file");
+ let root = doc.root_element();
+
+ let mut result = Vec::new();
+
+ for child in root.children().filter(|node| node.node_type() != roxmltree::NodeType::Text) {
+ if let Some(child_text) = child.children().next() {
+ result.push(child_text.text().unwrap_or("").to_string());
+ }
+ }
+
+ return array_to_hash(result);
+}
+
pub fn decode(level_data: String) -> Vec> {
let decoded_bytes = general_purpose::URL_SAFE.decode(level_data).expect("couldnt decode b64");
diff --git a/src/helpers/reupload.rs b/src/helpers/reupload.rs
new file mode 100644
index 0000000..67ffc68
--- /dev/null
+++ b/src/helpers/reupload.rs
@@ -0,0 +1,59 @@
+use std::sync::RwLock;
+
+use diesel::prelude::*;
+
+use crate::db;
+
+pub const REUPLOAD_USER_NAME: &str = "reupload";
+
+pub static REUPLOAD_ACCOUNT_ID: RwLock = RwLock::new(0);
+
+pub fn init() {
+ let connection = &mut db::establish_connection_pg();
+
+ use crate::schema::{accounts, users};
+ use crate::models::{Account, NewAccount, User, NewUser};
+
+ match accounts::table
+ .filter(accounts::username.eq(REUPLOAD_USER_NAME))
+ .select(accounts::id)
+ .get_result::(connection) {
+ Ok(reupload_acc_id) => {
+ let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
+ *write_lock = reupload_acc_id;
+
+ println!("reupload account found, id: {}", reupload_acc_id);
+ },
+ Err(_) => {
+ let new_account = NewAccount {
+ username: REUPLOAD_USER_NAME.to_string(),
+ gjp2: "!".to_string(),
+ password: "!".to_string(),
+ email: "".to_string()
+ };
+
+ let inserted_account = diesel::insert_into(accounts::table)
+ .values(&new_account)
+ .get_result::(connection)
+ .expect("Fatal error saving the new account");
+
+ let reupload_acc_id = inserted_account.id;
+
+ let new_user = NewUser {
+ account_id: inserted_account.id,
+ username: REUPLOAD_USER_NAME.to_string(),
+ registered: 1
+ };
+
+ diesel::insert_into(users::table)
+ .values(&new_user)
+ .get_result::(connection)
+ .expect("Fatal error saving the new user");
+
+ let mut write_lock = REUPLOAD_ACCOUNT_ID.write().expect("poisoned lock!!");
+ *write_lock = reupload_acc_id;
+
+ println!("created reupload account, id: {}", reupload_acc_id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index cd1b439..f8abf9b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,6 +34,10 @@ async fn files(file: PathBuf) -> Option {
#[launch]
fn rocket() -> _ {
+ // init stuff
+ crate::helpers::reupload::init();
+
+ // data directories
// this is a bit scuffed
fs::create_dir_all(&CONFIG.db.data_folder).expect("failed to create data directory!");
fs::create_dir_all(format!("{}/levels", &CONFIG.db.data_folder)).expect("failed to create data directory for levels");
@@ -46,13 +50,16 @@ fn rocket() -> _ {
.merge(("limits", Limits::new().limit("forms", 10.megabytes()))))
// actual website
.mount("/", routes![
- template_endpoints::index::index
+ template_endpoints::index::index,
+
+ template_endpoints::reupload::post_reupload,
+ template_endpoints::reupload::get_reupload
])
// assets
.mount("/", routes![
files
])
- // GEOMETRY DASH https://www.youtube.com/watch?v=_pLrtsf5yfE
+ // 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,
diff --git a/src/template_endpoints.rs b/src/template_endpoints.rs
index 5d4ca1d..50567bf 100644
--- a/src/template_endpoints.rs
+++ b/src/template_endpoints.rs
@@ -1 +1,2 @@
-pub mod index;
\ No newline at end of file
+pub mod index;
+pub mod reupload;
\ No newline at end of file
diff --git a/src/template_endpoints/reupload.rs b/src/template_endpoints/reupload.rs
new file mode 100644
index 0000000..c46b8b7
--- /dev/null
+++ b/src/template_endpoints/reupload.rs
@@ -0,0 +1,130 @@
+use rocket_dyn_templates::{Template, context};
+
+use rocket::form::Form;
+
+use reqwest;
+
+use serde::Deserialize;
+
+use serde_json;
+
+use std::fs;
+
+use base64::{Engine as _, engine::general_purpose};
+
+use diesel::prelude::*;
+
+use crate::helpers;
+use crate::db;
+
+#[derive(Deserialize)]
+struct LevelResults {
+ records: Vec
+}
+
+#[derive(Deserialize, Debug)]
+struct LevelRecord {
+ level_string_available: bool,
+ real_date: String,
+ length: Option,
+ id: i32
+}
+
+#[derive(FromForm)]
+pub struct FormReupload {
+ level_id: i32
+}
+
+#[post("/tools/reupload", data = "")]
+pub async fn post_reupload(input: Form) -> Template {
+ let connection = &mut db::establish_connection_pg();
+
+ let disabled = !crate::CONFIG.levels.reupload;
+
+ let error: Option = None;
+ if !disabled {
+ let remote_level_id = input.level_id;
+
+ let resp = reqwest::get(format!("https://history.geometrydash.eu/api/v1/level/{}", remote_level_id)).await.expect("failed to fetch level from remote server");
+ if !resp.status().is_success() {
+ return Template::render("reupload", context! {
+ error: Some(format!("Recieved status code: {}", resp.status()))
+ })
+ }
+
+ let text = resp.text().await.expect("failed to parse response as text");
+ let data: LevelResults = serde_json::from_str(&text).expect("failed to parse response as json");
+
+ let level: LevelRecord = match data.records
+ .into_iter()
+ .filter(|record| record.level_string_available)
+ .max_by_key(|record| record.real_date.clone())
+ .map(|record| record) {
+ Some(level) => level,
+ None => {
+ return Template::render("reupload", context! {
+ error: Some(String::from("No level string available"))
+ })
+ }
+ };
+
+ let gmd_file = reqwest::get(format!("https://history.geometrydash.eu/level/{}/{}/download/", remote_level_id, level.id)).await.expect("failed to fetch gmd file from remote server");
+ let level_data = helpers::levels::gmd_parse(&gmd_file.text().await.expect("failed to parse gmd file as text"));
+
+ use crate::schema::levels::dsl::*;
+ use crate::models::{Level, NewLevel};
+
+ println!("{:?}", level_data.get("k3"));
+
+ let new_level = NewLevel {
+ name: level_data.get("k2").expect("level name not found").to_string(),
+ user_id: crate::helpers::reupload::REUPLOAD_ACCOUNT_ID.read().expect("poisoned lock!!").to_string().parse::().expect("reupload account id not int (shouldnt ever happen)"),
+ description: String::from_utf8(general_purpose::URL_SAFE.decode(general_purpose::URL_SAFE.decode(level_data.get("k3").expect("level description not found")).expect("couldnt decode base64")).expect("couldnt decode base64")).expect("invalid utf-8 sequence (how)"),
+ original: None,
+ game_version: level_data.get("k17").expect("level game version not found").to_string().parse::().expect("level game version not int"),
+ binary_version: level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::().expect("level binary version not int"),
+ password: Some(level_data.get("k41").expect("level password not found").to_string()),
+ requested_stars: level_data.get("k66").expect("level requested stars not found").to_string().parse::().expect("level requested stars not int"),
+ unlisted: 0,
+ version: level_data.get("k16").expect("level version not found").to_string().parse::().expect("level version not int"),
+ extra_data: level_data.get("extra_string").unwrap_or(&crate::helpers::levels::DEFAULT_EXTRA_STRING).to_string().into_bytes(),
+ level_info: crate::helpers::levels::DEFAULT_LEVEL_INFO.to_string().into_bytes(),
+ editor_time: level_data.get("k80").unwrap_or(&String::from("0")).parse::().expect("level editor time not int"),
+ editor_time_copies: level_data.get("k81").unwrap_or(&String::from("0")).parse::().expect("level editor time copies not int"),
+ song_id: if level_data.get("k8").unwrap_or(&String::from("0")).parse::().expect("level song id not int") == 0 {
+ level_data.get("k45").expect("level song id doesnt fucking exist").parse::().expect("level song id not int")
+ } else {
+ level_data.get("k8").expect("level song id doesnt fucking exist").parse::().expect("level song id not int")
+ },
+ length: level.length.expect("level length doesnt fucking exist"),
+ objects: level_data.get("k48").expect("level object count doesnt exist").parse::().expect("object count not int"),
+ coins: level_data.get("k64").unwrap_or(&String::from("0")).parse::().expect("coins not int"),
+ has_ldm: level_data.get("k72").unwrap_or(&String::from("0")).parse::().expect("ldm not int"),
+ two_player: level_data.get("k43").unwrap_or(&String::from("0")).parse::().expect("two player not int")
+ };
+
+ let inserted_level = diesel::insert_into(levels)
+ .values(&new_level)
+ .get_result::(connection)
+ .expect("failed to insert level");
+
+ fs::write(format!("{}/levels/{}.lvl", crate::CONFIG.db.data_folder, inserted_level.id), general_purpose::URL_SAFE.decode(level_data.get("k4").expect("no level data?!").as_bytes()).expect("user provided invalid level string")).expect("couldnt write level to file");
+
+ return Template::render("reupload", context! {
+ level_id: inserted_level.id
+ })
+ }
+
+ Template::render("reupload", context! {
+ disabled: disabled
+ })
+}
+
+#[get("/tools/reupload")]
+pub fn get_reupload() -> Template {
+ let disabled = !crate::CONFIG.levels.reupload;
+
+ Template::render("reupload", context! {
+ disabled: disabled
+ })
+}
\ No newline at end of file
diff --git a/templates/index.html.hbs b/templates/index.html.hbs
index ee2b5c4..612426d 100644
--- a/templates/index.html.hbs
+++ b/templates/index.html.hbs
@@ -23,6 +23,7 @@
The Git repository
+ Level reuploading
diff --git a/templates/reupload.html.hbs b/templates/reupload.html.hbs
new file mode 100644
index 0000000..98da58d
--- /dev/null
+++ b/templates/reupload.html.hbs
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+ Level Reupload
+
+
+
+ Level Reupload
+
+ {{#if error}}
+ Error while uploading: {{ error }}
+ {{/if}}
+ {{#if level_id}}
+ Uploaded successfully! Level ID: {{ level_id }}
+ {{/if}}
+ {{#if disabled}}
+ Reuploading levels has been disabled.
+ {{/if}}
+
+
+
+ Only vanilla servers are supported.
+
+
+
\ No newline at end of file