reuploading levels
This commit is contained in:
parent
fd88a4e374
commit
4d8a41ba47
20 changed files with 604 additions and 22 deletions
|
@ -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<i32>
|
||||
pub blocklist: Vec<i32>,
|
||||
pub reupload: bool
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -93,7 +93,7 @@ pub fn download_level(input: Form<FormDownloadLevel>) -> 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"));
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ pub fn get_levels(input: Form<FormGetLevels>) -> 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;
|
||||
|
|
|
@ -183,7 +183,6 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> 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),
|
||||
|
|
|
@ -3,4 +3,5 @@ pub mod clean;
|
|||
pub mod difficulty;
|
||||
pub mod encryption;
|
||||
pub mod format;
|
||||
pub mod levels;
|
||||
pub mod levels;
|
||||
pub mod reupload;
|
|
@ -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<String> = LazyLock::new(|| {
|
||||
|
@ -13,6 +15,12 @@ pub static DEFAULT_EXTRA_STRING: LazyLock<String> = LazyLock::new(|| {
|
|||
return string;
|
||||
});
|
||||
|
||||
pub static DEFAULT_LEVEL_INFO: LazyLock<String> = 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<HashMap<String, String>> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn gmd_parse(gmd_file: &str) -> HashMap<String, String> {
|
||||
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<HashMap<String, String>> {
|
||||
let decoded_bytes = general_purpose::URL_SAFE.decode(level_data).expect("couldnt decode b64");
|
||||
|
||||
|
|
59
src/helpers/reupload.rs
Normal file
59
src/helpers/reupload.rs
Normal file
|
@ -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<i32> = 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::<i32, >(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::<Account, >(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::<User, >(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);
|
||||
}
|
||||
}
|
||||
}
|
11
src/main.rs
11
src/main.rs
|
@ -34,6 +34,10 @@ async fn files(file: PathBuf) -> Option<NamedFile> {
|
|||
|
||||
#[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,
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod index;
|
||||
pub mod index;
|
||||
pub mod reupload;
|
130
src/template_endpoints/reupload.rs
Normal file
130
src/template_endpoints/reupload.rs
Normal file
|
@ -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<LevelRecord>
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct LevelRecord {
|
||||
level_string_available: bool,
|
||||
real_date: String,
|
||||
length: Option<i32>,
|
||||
id: i32
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct FormReupload {
|
||||
level_id: i32
|
||||
}
|
||||
|
||||
#[post("/tools/reupload", data = "<input>")]
|
||||
pub async fn post_reupload(input: Form<FormReupload>) -> Template {
|
||||
let connection = &mut db::establish_connection_pg();
|
||||
|
||||
let disabled = !crate::CONFIG.levels.reupload;
|
||||
|
||||
let error: Option<String> = 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::<i32>().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::<i32>().expect("level game version not int"),
|
||||
binary_version: level_data.get("k50").unwrap_or(&String::from("0")).to_string().parse::<i32>().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::<i32>().expect("level requested stars not int"),
|
||||
unlisted: 0,
|
||||
version: level_data.get("k16").expect("level version not found").to_string().parse::<i32>().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::<i32>().expect("level editor time not int"),
|
||||
editor_time_copies: level_data.get("k81").unwrap_or(&String::from("0")).parse::<i32>().expect("level editor time copies not int"),
|
||||
song_id: if level_data.get("k8").unwrap_or(&String::from("0")).parse::<i32>().expect("level song id not int") == 0 {
|
||||
level_data.get("k45").expect("level song id doesnt fucking exist").parse::<i32>().expect("level song id not int")
|
||||
} else {
|
||||
level_data.get("k8").expect("level song id doesnt fucking exist").parse::<i32>().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::<i32>().expect("object count not int"),
|
||||
coins: level_data.get("k64").unwrap_or(&String::from("0")).parse::<i32>().expect("coins not int"),
|
||||
has_ldm: level_data.get("k72").unwrap_or(&String::from("0")).parse::<i32>().expect("ldm not int"),
|
||||
two_player: level_data.get("k43").unwrap_or(&String::from("0")).parse::<i32>().expect("two player not int")
|
||||
};
|
||||
|
||||
let inserted_level = diesel::insert_into(levels)
|
||||
.values(&new_level)
|
||||
.get_result::<Level, >(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
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue