Added mcmod update branch

This commit is contained in:
J-onasJones 2024-01-04 00:48:38 +01:00
parent e8d1b934d7
commit 5c6a2ac53f
4 changed files with 219 additions and 18 deletions

39
Cargo.lock generated
View file

@ -17,6 +17,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -527,6 +536,7 @@ dependencies = [
"lastfm",
"log",
"parking_lot",
"regex",
"reqwest",
"serde",
"serde_json",
@ -886,6 +896,35 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.11.22"

View file

@ -17,3 +17,4 @@ chrono = "0.4.31"
toml = "0.8.8"
reqwest = { version = "0.11.22", features = ["json"] }
serde_json = "1.0.108"
regex = "1"

View file

@ -1,29 +1,144 @@
use std::collections::HashMap;
use std::net::IpAddr;
use reqwest::Error;
use serde_json::json;
use serde::{Deserialize, Serialize};
use warp::Filter;
use regex::Regex;
use crate::error_responses::BadRequestError;
pub fn get_mods_paths() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
// any path that starts with /v1/updates/minecraft/mods/{modname}/{loadername}/{version} calls handle_path
warp::path("v1").and(warp::path("updates")).and(warp::path("minecraft")).and(warp::path("mods"))
.and(warp::path::param())
.and(warp::path::param())
.and(warp::path::param())
.and(warp::path::end())
.and(warp::addr::remote())
.map(handle_path)
.and(warp::get().and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::end()).and(warp::filters::header::headers_cloned()).and(warp::query::<HashMap<String, String>>()).and_then(handle_path))
}
fn handle_path(modname: String, loadername: String, version: String, remote_ip: Option<std::net::SocketAddr>) -> String {
format!("modname: {}, loadername: {}, version: {}, IP: {}", modname, loadername, version, remote_ip.unwrap_or(std::net::SocketAddr::from(([0, 0, 0, 0], 0))).ip())
#[derive(Debug, Deserialize, Serialize)]
struct ModData {
package: String,
name: String,
versions: Vec<HashMap<String, HashMap<String, ModVersion>>>,
}
// fn handle_with_headers(
// headers: warp::http::HeaderMap,
// ) -> String {
// // Iterate through the headers and print them
// for (name, value) in headers.iter() {
// println!("Header: {}: {}", name, value.to_str().unwrap_or("Invalid UTF-8"));
// }
#[derive(Debug, Deserialize, Serialize, Clone)]
struct ModVersion {
recommended: String,
latest: String,
all: Vec<String>,
}
// // Respond with a message or perform other actions as needed
// "Headers received".to_string()
// }
// get json data from https://https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json
pub async fn fetch_data() -> Result<serde_json::Value, Error> {
let url = "https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json";
let response = reqwest::get(url).await?;
if response.status().is_success() {
// Parse the JSON response
let json_data: serde_json::Value = response.json().await?;
return Ok(json_data);
} else {
// Handle non-successful status codes
Err(response.error_for_status().unwrap_err())
}
}
fn is_valid_ip(ip_str: &str) -> bool {
if let Ok(ip) = ip_str.parse::<IpAddr>() {
match ip {
IpAddr::V4(_) => true,
IpAddr::V6(_) => true,
}
} else {
false
}
}
fn is_valid_minecraft_version(version: &str) -> bool {
// Define the regex pattern for the Minecraft version
let pattern = Regex::new(r"^1\.\d{1,2}(\.\d)?$").unwrap();
// Check if the provided version matches the pattern
pattern.is_match(version)
}
fn get_header_forward_for_ip(headers: warp::http::HeaderMap) -> String {
// check if the header X-Forward-For exists and return the ip, if not, return an empty string
if let Some(forwarded_for) = headers.get("X-Forwarded-For") {
if let Ok(ip) = forwarded_for.to_str() {
// Extract the first IP address from the comma-separated list
if let Some(first_ip) = ip.split(',').next() {
return first_ip.trim().to_string();
}
}
}
String::new()
}
async fn handle_path(modpackage: String, loadername: String, mcversion: String, modversion: String, headers: warp::http::HeaderMap, params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Retrieve the IP from the header and check if it's valid
let mut client_ip = get_header_forward_for_ip(headers);
if !is_valid_ip(&client_ip) {
client_ip = params.get("ip").unwrap_or(&"".to_string()).to_string();
if !is_valid_ip(&client_ip) {
client_ip = "Not valid".to_string();
}
}
// check if the minecraft version is valid
if !is_valid_minecraft_version(&mcversion) {
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
// convert the raw list of data into a list of ModData and ModVersion
let mods_data: Vec<ModData> = serde_json::from_value(data).unwrap();
// get the mod data from the requested mod
let mod_data: ModData = mods_data.into_iter().find(|mod_data| mod_data.package == modpackage).unwrap();
// get the version data from the requested loader and remove the other loaders
let version_data: HashMap<std::string::String, ModVersion> = mod_data.versions.into_iter().find(|version_data| version_data.contains_key(&loadername)).unwrap().remove(&loadername).unwrap();
// turn version_data into an object of String: ModVersion key value pairs
let version_data: HashMap<std::string::String, ModVersion> = version_data.into_iter().map(|(key, value)| (key, value)).collect();
// get the version data for the current minecraft version
let version_data: ModVersion = version_data.get(&mcversion).unwrap().clone();
// get recommended and latest version
let recommended_version = version_data.recommended.clone();
let latest_version = version_data.latest.clone();
// determine whether the client is up to date
let mut up_to_date = false;
if modversion == recommended_version {
up_to_date = true;
}
// determine if telemetry is enabled by checking if the client_ip is valid
let mut telemetry = false;
if is_valid_ip(&client_ip) {
telemetry = true;
}
// create the response
let response = json!({
"promos": {
"latest": latest_version,
"recommended": recommended_version
},
"upToDate": up_to_date,
"telemetry_enabled": telemetry
});
//TODO: Add way to process telemetry data
// return the data
return Ok(warp::reply::json(&response));
}

View file

@ -0,0 +1,46 @@
use std::fs::OpenOptions;
use std::io::{self, Write};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::Path;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct IpInfo {
region: String,
// Add other fields as needed
}
fn get_ip_hash(ip: &str) -> u64 {
let mut hasher = DefaultHasher::new();
ip.hash(&mut hasher);
hasher.finish()
}
pub fn log_ip_info(ip_address: &str, file_path: &str, mod_package: &str) -> io::Result<()> {
let ip_hash = get_ip_hash(ip_address);
let ip_info = match get_ip_info(ip_address) {
Ok(info) => info,
Err(err) => {
IpInfo { region: "Unknown".to_string() } // Default to "Unknown" in case of an error
}
};
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(file_path)?;
writeln!(file, "{} {} {}", ip_hash, ip_info.region, mod_package)?;
Ok(())
}
fn main() {
let file_path = "ip_log.txt"; // Replace with your desired file path
// Example usage
match log_ip_info("8.8.8.8", file_path) {
Err(err) => eprintln!("Error: {}", err),
}
}