Compare commits

...

37 commits
v0.1.0 ... rust

Author SHA1 Message Date
c25f2032d7 migrated to new version of sync script 2025-04-02 23:18:53 +02:00
9ef64b5b82 added more request logging code 2024-06-30 00:34:55 +02:00
c20e276de2 Merge branch 'rust' of github.com:J-onasJones/jonas_jones-api into rust 2024-06-29 23:29:10 +02:00
2c8a9db949 added request logger 2024-06-29 23:27:05 +02:00
350c9f722a added IP in brackets 2024-06-28 05:12:47 +02:00
cdcf27b0fa added request logging 2024-06-28 05:00:38 +02:00
41fa898a67 Updated dependencies 2024-05-15 13:10:18 +02:00
d2a89d5067 roadmap draft 2024-02-14 03:41:25 +01:00
3b5378c6f2 Fixed version (0verdue!!!) 2024-02-12 04:41:23 +01:00
1cdb34503d Added docker-compose file 2024-02-12 04:34:34 +01:00
4ecbd7aa98 fixed docker compose 2024-02-12 04:32:37 +01:00
a63f933dd9 Fixed dependencies 2024-02-12 04:21:05 +01:00
48da0d7f95 Fixed docker compose
and python lib requirements
2024-02-12 04:05:44 +01:00
5e9ed3738c fixed logs 2024-02-12 03:49:41 +01:00
7bee1f5bae added update runner thread 2024-02-12 03:46:08 +01:00
bdfd5a74a6 Added docker compose instructions 2024-02-11 08:44:20 +01:00
4c62ba2845 added /status route 2024-02-11 06:28:44 +01:00
Jonas_Jones
852e184b89 removed console spam 2024-01-28 03:38:11 +01:00
Jonas_Jones
a851b18875 changed update to run path 2024-01-28 03:31:10 +01:00
Jonas_Jones
c18ab8a8da reverted changes 2024-01-28 03:27:38 +01:00
Jonas_Jones
462ee68763 Fixed warnings 2024-01-28 03:10:49 +01:00
Jonas_Jones
f201bc7fda Added new branch /v1/update 2024-01-28 03:10:41 +01:00
5f2026af0d version bump 2024-01-04 00:49:32 +01:00
5c6a2ac53f Added mcmod update branch 2024-01-04 00:48:38 +01:00
e8d1b934d7 code cleanup 2024-01-03 12:01:22 +01:00
8e61e81f06 bumped version 2024-01-03 12:00:54 +01:00
5eb6080694 Fixed getlangs & language filter
It was a placeholder in v0.2.0 as not implemented
2024-01-03 12:00:32 +01:00
83e1b85811 Updated dependencies 2024-01-03 11:29:35 +01:00
bc09ff5f3c version bump that I forgot about 2024-01-03 11:28:36 +01:00
2188f2acd5 code cleanup 2024-01-03 00:59:22 +01:00
169be592ab Removed dollar signs in front of bash commands 2024-01-03 00:56:18 +01:00
5aefb1a7c7 Added /v1/projects api branch 2024-01-03 00:49:29 +01:00
ce7aa20a57 code cleanup 2024-01-03 00:48:58 +01:00
5c7c572641 Added error 501 2024-01-01 17:18:08 +01:00
3c1d88bd42 Added last_update object 2024-01-01 11:46:39 +01:00
Jonas_Jones
69ea09b223 Added favicon route 2023-12-16 05:50:38 +01:00
Jonas_Jones
431c6c080a Fixed typo 2023-12-16 05:26:43 +01:00
23 changed files with 2051 additions and 82 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@
/.vscode
.env
/resources

718
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "jonas_jones-api"
version = "0.1.0"
version = "0.4.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -11,9 +11,13 @@ parking_lot = "0.12.1"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.35", features = ["macros"] }
dotenv = "0.15.0"
lastfm = "0.6.1"
lastfm = "0.10.0"
log = "0.4.20"
chrono = "0.4.31"
toml = "0.8.8"
reqwest = { version = "0.11.22", features = ["json"] }
reqwest = { version = "0.12.5", features = ["json", "blocking"] }
serde_json = "1.0.108"
regex = "1"
git2 = "0.19.0"
ip2location = "0.5.0"
sha2 = "0.10.8"

View file

@ -9,28 +9,59 @@ As of now, the project has no proper production build and unless the proper envi
Clone the repository and install the dependencies.
```bash
$ git clone git@github.com:J-onasJones/jonas_jones-api.git
$ cd jonas_jones-api
$ cargo build
git clone git@github.com:J-onasJones/jonas_jones-api.git
cd jonas_jones-api
cargo build
```
## Usage
To run the API, simply run the following command.
```bash
$ cargo run
cargo run
```
If you want to run the API in a production environment, you will need to set the following environment variables.
- API_PORT
- API_IP =
- API_IP
- LASTFM_API_KEY
- LASTFM_API_SECRET
```bash
$ export API_PORT={port}
$ export API_IP={ip_address}
$ export LASTFM_API_KEY={lastfm_api_key}
$ export LASTFM_API_SECRET={lastfm_api_secret}
export API_PORT={port}
export API_IP={ip_address}
export LASTFM_API_KEY={lastfm_api_key}
export LASTFM_API_SECRET={lastfm_api_secret}
```
## Docker Compose
`docker-compose.yaml` (folder paths need adjusting):
```yaml
version: '3.8'
services:
arch-linux:
image: archlinux:latest
container_name: jonas_jones-api
ports:
- "3030:3030"
volumes:
- /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api
- /home/jonas_jones/.config/rclone/:/root/.config/rclone/
command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"]
```
run container:
```sh
docker-compose up -d
```
## Roadmap
- analytics backend. track request origin through IP from header (store IP hash, region and time)
- rewrite all scripts in rust
- DB implementation for projects, kcomebacks, minecraft mod versions
- session backend, auth token system
- implementation for dashboard front-end with analytics/config
- complete minecraft mod implementation

11
docker-compose.yaml Normal file
View file

@ -0,0 +1,11 @@
version: '3.8'
services:
arch-linux:
image: archlinux:latest
container_name: jonas_jones-api
ports:
- "3030:3030"
volumes:
- /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api
- /home/jonas_jones/.config/rclone/:/root/.config/rclone/
command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"]

9
requirements.txt Normal file
View file

@ -0,0 +1,9 @@
requests
datetime
python-dotenv
spotipy
praw
spotipy
pylast
typing
markdown

View file

@ -26,3 +26,7 @@ impl warp::reject::Reject for UnauthorizedError {}
pub struct ForbiddenError;
impl warp::reject::Reject for ForbiddenError {}
#[derive(Debug)]
pub struct NotImplementedError;
impl warp::reject::Reject for NotImplementedError {}

BIN
src/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

14
src/iplookup.rs Normal file
View file

@ -0,0 +1,14 @@
use ip2location::{DB, Record};
pub fn ip_lookup(ip: &str) -> String {
let database_path = "resources/IP2LOCATION-LITE-DB5.IPV6.BIN/IP2LOCATION-LITE-DB5.IPV6.BIN";
let mut db = DB::from_file(database_path).unwrap();
let geo_info = db.ip_lookup(ip.parse().unwrap()).unwrap();
let record = if let Record::LocationDb(rec) = geo_info {
Some(rec)
} else { None };
return record.unwrap().country.unwrap().short_name.to_string();
}

View file

@ -1,19 +1,50 @@
use dotenv::dotenv;
use tokio::time::{sleep, Duration};
use v1::{run_sync_all_command};
use std::fs;
pub mod v1;
pub mod logger;
pub mod tools;
pub mod server;
pub mod error_responses;
pub mod iplookup;
pub mod request_logger;
pub use logger::Logger;
pub use tools::parse_ip;
async fn periodic_script_runner() {
loop {
Logger::info("Running periodic scripts...");
// Run all Functions
let _ = run_sync_all_command();
// Sleep for 6 hours
sleep(Duration::from_secs(6 * 60 * 60)).await;
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
// load .env file
// Load .env file
dotenv().ok();
server::serve().await;
// Start the api
let server_task = tokio::spawn(async {
server::serve().await;
});
// periodic script runner
let second_task = tokio::spawn(async {
// check if the local repository exists, if not, clone it
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
v1::run_setup().unwrap();
};
periodic_script_runner().await;
});
// Wait for both tasks to complete
let _ = tokio::try_join!(server_task, second_task);
}

71
src/request_logger.rs Normal file
View file

@ -0,0 +1,71 @@
use chrono::Utc;
use reqwest::Client;
use reqwest::blocking::get;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::fs::OpenOptions;
use std::io::{BufReader, BufWriter};
use warp::http::Method;
use std::path::Path;
#[derive(Serialize, Deserialize)]
struct RequestLog {
timestamp: String,
method: String,
pathname: String,
ip_country_code: String,
ip_hash: String,
}
async fn get_ip_country_code(ip: &str) -> Result<String, Box<dyn std::error::Error>> {
let url = format!("http://ip-api.com/json/{}", ip);
let response: Value = get(&url)?.json()?;
if let Some(country_code) = response["countryCode"].as_str() {
Ok(country_code.to_string())
} else {
Err("Could not fetch country code".into())
}
}
async fn hash_ip(ip: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(ip);
format!("{:x}", hasher.finalize())
}
pub async fn log_request(client: &Client, ip: &str, pathname: &str, method: &Method, file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let method_str = method.as_str();
let timestamp = Utc::now().to_rfc3339();
let ip_country_code = get_ip_country_code(ip).await?;
let ip_hash = hash_ip(ip).await;
let log_entry = RequestLog {
timestamp,
method: method_str.to_string(),
pathname: pathname.to_string(),
ip_country_code,
ip_hash,
};
let path = Path::new(file_path);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
let mut logs: Vec<RequestLog> = if path.exists() {
let reader = BufReader::new(&file);
serde_json::from_reader(reader)?
} else {
Vec::new()
};
logs.push(log_entry);
let writer = BufWriter::new(&file);
serde_json::to_writer_pretty(writer, &logs)?;
Ok(())
}

View file

@ -1,13 +1,19 @@
use std::convert::Infallible;
use std::net::SocketAddr;
use std::env;
use reqwest::StatusCode;
use lastfm::reqwest::StatusCode;
use warp::filters::path::FullPath;
use warp::http::Method;
use warp::Filter;
use warp::reply::Reply;
use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError};
use reqwest::Client;
use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError};
use crate::v1::get_v1_routes;
use crate::{Logger, parse_ip};
use crate::{parse_ip, request_logger, Logger};
use crate::iplookup::ip_lookup;
pub async fn serve() {
@ -18,9 +24,40 @@ pub async fn serve() {
let socket_addr = parse_ip();
let favicon = warp::path("favicon.ico").and(warp::fs::file("./src/favicon.png"));
// /status => 200 OK
let status = warp::path("status")
.map(|| warp::reply());
// Middleware filter to log request details
let log_request = warp::any()
.and(warp::method())
.and(warp::path::full())
.and(warp::addr::remote())
.and(warp::header::optional::<String>("x-forwarded-for"))
.map(|method: Method, path: FullPath, addr: Option<SocketAddr>, fwd_for: Option<String>| {
let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown")));
let path_str = path.as_str().to_string(); // Convert to owned String
let method_clone = method.clone();
let method_str = method_clone.clone().as_str().to_string();
let client_ip_clone = client_ip.clone(); // Clone for use outside the async block
let path_str_clone = path_str.clone();
/*tokio::spawn(async move {
let client = Client::new();
if let Err(e) = request_logger::log_request(&client, &client_ip, &path_str, &method_clone, "requests.json").await {
eprintln!("Failed to log request: {:?}", e);
}
});*/
Logger::info(&format!("{} {} from {} ({})", method_str, path_str_clone, ip_lookup(&client_ip_clone), client_ip_clone));
});
// GET (any) => reply with return from handle_path
let routes = get_v1_routes()
.recover(handle_rejection);
let routes = log_request
.clone().untuple_one().and(favicon.or(status.or(get_v1_routes())
.recover(handle_rejection)));
async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible> {
@ -30,6 +67,8 @@ pub async fn serve() {
(StatusCode::BAD_REQUEST, "Bad Request")
} else if err.is_not_found() || err.find::<NotFoundError>().is_some() {
(StatusCode::NOT_FOUND, "Not Found")
} else if err.find::<NotImplementedError>().is_some() {
(StatusCode::NOT_IMPLEMENTED, "Not Implemented")
} else {
(StatusCode::INTERNAL_SERVER_ERROR, "Unhandled Rejection") // Default case
};
@ -39,7 +78,7 @@ pub async fn serve() {
message: message.into(),
});
Ok(warp::reply::with_status(json, code))
Ok(warp::reply::with_status(json, StatusCode::from_u16(code.as_u16()).unwrap()))
}

8
src/v1/analytics/mod.rs Normal file
View file

@ -0,0 +1,8 @@
use warp::Filter;
pub fn get_analytics_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("v1").and(warp::path("analytics"))
.and((warp::path("logcdnrequest").map(|| "Please refer to the wiki at https://wiki.jonasjones.dev/Api/"))
.or(warp::path("ping").map(|| "pong"))
.or(warp::path("version").map(|| warp::reply::json(&[option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")]))))
}

7
src/v1/auth/mod.rs Normal file
View file

@ -0,0 +1,7 @@
use warp::Filter;
pub fn get_auth_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("v1").and(warp::path("auth"))
.and((warp::path("requestsession").and(warp))
.or(warp::path("login")))
}

View file

@ -323,8 +323,6 @@ async fn filter_type_handler(params: HashMap<String, String>) -> Result<impl war
}
async fn filter_gettypes_handler() -> Result<impl warp::Reply, warp::Rejection> {
println!("Received parameters - parameters: gettypes");
// fetch the data
let data = fetch_data().await.unwrap();

View file

@ -1,5 +1,3 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use warp::Filter;
@ -10,7 +8,7 @@ mod upcoming;
use filter::get_kcomebacks_filter_routes;
use upcoming::get_kcomebacks_upcoming_routes;
use crate::error_responses::{InternalServerError, BadRequestError, NotFoundError};
use crate::error_responses::InternalServerError;
pub fn get_kcomebacks_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("v1").and(warp::path("kcomebacks"))

View file

@ -2,7 +2,7 @@ use std::{collections::HashMap, ops::Add};
use warp::Filter;
use crate::{v1::kcomebacks::filter::filter_daterange_handler, error_responses::BadRequestError};
use crate::v1::kcomebacks::filter::filter_daterange_handler;
pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("upcoming")

View file

@ -1,12 +1,17 @@
mod builtin;
mod debug;
mod kcomebacks;
mod updates;
mod projects;
mod run;
pub use run::setup as run_setup;
pub use run::run_sync_all_command;
pub use builtin::get_builtin_routes as get_v1_builtin_routes;
pub use debug::get_debug_routes as get_v1_debug_routes;
pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes;
pub use updates::get_updates_routes as get_v1_updates_routes;
pub use projects::get_project_routes as get_v1_project_routes;
pub use run::get_run_routes as get_v1_updates_routes;
use warp::Filter;
@ -14,5 +19,6 @@ pub fn get_v1_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error =
return get_v1_builtin_routes()
.or(get_v1_debug_routes())
.or(get_v1_kcomebacks_routes())
.or(get_v1_project_routes())
.or(get_v1_updates_routes());
}

View file

@ -0,0 +1,566 @@
use std::collections::{HashMap, HashSet};
use serde_json::{Value, json};
use warp::Filter;
use crate::{error_responses::BadRequestError, v1::projects::{fetch_data, create_json_response, Project as EntryProject}};
pub fn get_project_filter_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("filter")
.and((warp::path("getall").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_getall_handler))
.or(warp::path("lastupdaterange").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_lastupdaterange_handler))
.or(warp::path("title").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_title_handler))
.or(warp::path("description").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_description_handler))
.or(warp::path("search").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_search_handler))
.or(warp::path("status").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_status_handler))
.or(warp::path("statuscolor").and(warp::get().and(warp::query::<HashMap<String, String>>()).and_then(filter_statuscolor_handler)))
.or(warp::path("category").and(warp::get().and(warp::query::<HashMap<String, String>>()).and_then(filter_category_handler)))
.or(warp::path("language").and(warp::get().and(warp::query::<HashMap<String, String>>()).and_then(filter_language_handler)))
.or(warp::path("getlangs").and(warp::get().and_then(filter_getlangs_handler)))
.or(warp::path("getstatuses").and(warp::get().and_then(filter_getstatuses_handler)))
.or(warp::path("getcolors").and(warp::get().and_then(filter_getcolors_handler)))
.or(warp::path("getcategories").and(warp::get().and_then(filter_getcategories_handler))))
}
async fn filter_getall_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameters from the HashMap
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if params.len() > 2
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty()
|| offset.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap() + 1).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_lastupdaterange_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameters from the HashMap
let start = params.get("start").unwrap_or(&"".to_string()).to_string();
let end = params.get("end").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// Parse the start and end dates (from unix time) to i64 integers
let start = start.parse::<i64>().unwrap_or(-1);
let end = end.parse::<i64>().unwrap_or(-1);
// check if the parameters are valid
if params.len() > 4
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty()
|| offset.is_empty()
|| start == -1
|| end == -1
|| end < start
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.last_update >= start && project.last_update <= end)
.collect()
}
_ => Vec::new(),
};
println!("{:?}", filtered_data);
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_title_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameters from the HashMap
let title = params.get("title").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty()
|| offset.is_empty()
|| title.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.title.to_lowercase().contains(title.to_lowercase().as_str()))
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_description_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameters from the HashMap
let description = params.get("description").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty()
|| offset.is_empty()
|| description.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.description.to_lowercase().contains(description.to_lowercase().as_str()))
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_search_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameters from the HashMap
let search = params.get("search").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty()
|| offset.is_empty()
|| search.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.title.to_lowercase().contains(search.to_lowercase().as_str()) || project.description.to_lowercase().contains(search.to_lowercase().as_str()))
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_status_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameres from the HashMap
let status = params.get("status").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if status.is_empty()
|| params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty() || offset.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.status == status)
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_statuscolor_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameres from the HashMap
let statuscolor = params.get("statuscolor").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if statuscolor.is_empty()
|| params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty() || offset.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.statuscolor == statuscolor)
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_category_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameres from the HashMap
let category = params.get("category").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if category.is_empty()
|| params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty() || offset.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.categories.iter().any(|cat| cat == category.as_str()))
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_language_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
// Access the parameres from the HashMap
let language = params.get("language").unwrap_or(&"".to_string()).to_string();
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
// check if the parameters are valid
if language.is_empty()
|| params.len() > 3
|| !limit.parse::<i32>().is_ok()
|| !offset.parse::<i32>().is_ok()
|| limit.parse::<i32>().unwrap() < 0
|| limit.parse::<i32>().unwrap() > 50
|| offset.parse::<i32>().unwrap() < 0
|| limit.is_empty() || offset.is_empty()
{
return Err(warp::reject::custom(BadRequestError));
}
// fetch the data
let data = fetch_data().await.unwrap();
// filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.filter(|project| project.languages.contains_key(&language))
.collect()
}
_ => Vec::new(),
};
// get the total number of results
let total_results = filtered_data.len();
// apply the limit and offset
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
// return the data
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
}
async fn filter_getlangs_handler() -> Result<impl warp::Reply, warp::Rejection> {
// fetch the data
let data = fetch_data().await.unwrap();
//filter the data
let filtered_data: Vec<EntryProject> = match data {
Value::Array(items) => {
items
.iter()
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
.filter(|project| project.visible)
.collect()
}
_ => Vec::new(),
};
// filter the data
let mut languages_set: HashSet<String> = HashSet::new();
for project in filtered_data {
for language in project.languages.keys() {
languages_set.insert(language.clone());
}
}
// get the total number of results
let total_results = languages_set.len();
// json response
let json_response = json!({
"results": languages_set,
"total_results": total_results,
});
// return the data
Ok(warp::reply::json(&json_response))
}
async fn filter_getstatuses_handler() -> Result<impl warp::Reply, warp::Rejection> {
// fetch the data
let data = fetch_data().await.unwrap();
let mut all_statuses: Vec<String> = Vec::new();
for i in 1..data.as_array().unwrap().len() {
if !data[i]["status"].as_str().unwrap().to_string().is_empty() {
all_statuses.push(data[i]["status"].as_str().unwrap().to_string());
}
}
// remove duplicates
all_statuses.sort();
all_statuses.dedup();
// get the total number of results
let total_results = all_statuses.len();
// json response
let json_response = json!({
"results": all_statuses,
"total_results": total_results,
});
Ok(warp::reply::json(&json_response))
}
async fn filter_getcolors_handler() -> Result<impl warp::Reply, warp::Rejection> {
// fetch the data
let data = fetch_data().await.unwrap();
let mut all_colors: Vec<String> = Vec::new();
for i in 1..data.as_array().unwrap().len() {
if !data[i]["statuscolor"].as_str().unwrap().to_string().is_empty() {
all_colors.push(data[i]["statuscolor"].as_str().unwrap().to_string());
}
}
// remove duplicates
all_colors.sort();
all_colors.dedup();
// get the total number of results
let total_results = all_colors.len();
// json response
let json_response = json!({
"results": all_colors,
"total_results": total_results,
});
Ok(warp::reply::json(&json_response))
}
async fn filter_getcategories_handler() -> Result<impl warp::Reply, warp::Rejection> {
// fetch the data
let data = fetch_data().await.unwrap();
let mut all_categories: Vec<String> = Vec::new();
for i in 1..data.as_array().unwrap().len() {
if !data[i]["categories"].as_array().unwrap().is_empty() {
for j in 0..data[i]["categories"].as_array().unwrap().len() {
if !data[i]["categories"][j].as_str().unwrap().to_string().is_empty() {
all_categories.push(data[i]["categories"][j].as_str().unwrap().to_string());
}
}
}
}
// remove duplicates
all_categories.sort();
all_categories.dedup();
// get the total number of results
let total_results = all_categories.len();
// json response
let json_response = json!({
"results": all_categories,
"total_results": total_results,
});
Ok(warp::reply::json(&json_response))
}

76
src/v1/projects/mod.rs Normal file
View file

@ -0,0 +1,76 @@
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use warp::Filter;
use reqwest::Error;
use std::collections::HashMap;
mod filter;
use filter::get_project_filter_routes;
use crate::error_responses::InternalServerError;
pub fn get_project_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("v1").and(warp::path("projects"))
.and(warp::path("last_update").and(warp::get()).and_then(last_update)
.or(warp::path("start_update").map(|| "Not implemented yet"))
.or(get_project_filter_routes())
)
}
// get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json
pub async fn fetch_data() -> Result<serde_json::Value, Error> {
let url = "https://cdn.jonasjones.dev/api/projects/projects.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())
}
}
async fn last_update() -> Result<impl warp::Reply, warp::Rejection> {
// get the value of last_update of the first element of the json that fetch_data() returns
let last_update_value = fetch_data().await.unwrap()[0]["last_update"].clone();
// get the value from last_update_value and return it as a json if it's Ok, otherwise return an InternalServerError
match last_update_value {
serde_json::Value::String(last_update) => Ok(warp::reply::json(&last_update)),
_ => Err(warp::reject::custom(InternalServerError)),
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Project {
pub title: String,
pub description: String,
pub status: String,
pub statuscolor: String,
pub categories: Vec<String>,
pub languages: HashMap<String, i32>,
pub gh_api: String,
pub version: String,
pub backgroud: String,
pub links: HashMap<String, String>,
pub visible: bool,
pub last_update: i64,
}
pub fn create_json_response(items: Vec<&Project>, total_results: usize) -> Value {
// Serialize the vector of items to a JSON array
let results_array: Vec<Value> = items.into_iter().map(|item| json!(item)).collect();
// Build the final JSON object with "results" and "total_results" fields
let json_response = json!({
"results": results_array,
"total_results": total_results,
});
json_response
}

284
src/v1/run/mod.rs Normal file
View file

@ -0,0 +1,284 @@
use std::fs;
// use std::io::BufRead;
use std::process::{Stdio, Command};
use serde::{Deserialize, Serialize};
use serde_json::json;
// use tokio::sync::mpsc;
use tokio::task;
use warp::Filter;
use crate::error_responses::InternalServerError;
use crate::Logger;
// DiscographyQuery
#[derive(Debug, Deserialize, Serialize)]
pub struct DiscographyQuery {
artists: String,
}
pub fn get_run_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("v1").and(warp::path("run"))
// update/kcomebacks
// update/projects
// update/makediscography?artists=artist1,artist2,artist3
// update/synclikedsongs
.and((warp::path("kcomebacks").and_then(update_kcomebacks))
.or(warp::path("projects").and_then(update_projects))
.or(warp::path("makediscography").map(||"Not implemented yet"))
.or(warp::path("synclikedsongs").and_then(sync_liked_songs))
.or(warp::path("sync_all").and_then(sync_all))
)
}
async fn update_kcomebacks() -> Result<impl warp::Reply, warp::Rejection> {
// check if the local repository exists, if not, clone it
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
setup().unwrap();
};
if let Err(err) = run_kcomebacks_command() {
// Handle the error here
eprintln!("Error: {}", err);
// Return an appropriate response or error
return Err(warp::reject::custom(InternalServerError));
}
Ok(warp::reply::json(&json!({"status": "updating..."})))
}
async fn update_projects() -> Result<impl warp::Reply, warp::Rejection> {
// check if the local repository exists, if not, clone it
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
setup().unwrap();
};
if let Err(err) = run_projects_command() {
// Handle the error here
eprintln!("Error: {}", err);
// Return an appropriate response or error
return Err(warp::reject::custom(InternalServerError));
}
Ok(warp::reply::json(&json!({"status": "updating..."})))
}
async fn sync_liked_songs() -> Result<impl warp::Reply, warp::Rejection> {
// check if the local repository exists, if not, clone it
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
setup().unwrap();
};
if let Err(err) = run_likedsongs_command() {
// Handle the error here
eprintln!("Error: {}", err);
// Return an appropriate response or error
return Err(warp::reject::custom(InternalServerError));
}
Ok(warp::reply::json(&json!({"status": "updating..."})))
}
async fn sync_all() -> Result<impl warp::Reply, warp::Rejection> {
// check if the local repository exists, if not, clone it
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
setup().unwrap();
};
if let Err(err) = run_sync_all_command() {
// Handle the error here
eprintln!("Error: {}", err);
// Return an appropriate response or error
return Err(warp::reject::custom(InternalServerError));
}
Ok(warp::reply::json(&json!({"status": "syncing..."})))
}
pub fn setup() -> Result<(), git2::Error> {
let repository_url = "https://github.com/JonasunderscoreJones/turbo-octo-potato.git";
let local_directory = "resources/turbo_octo_potato";
git2::Repository::clone(repository_url, local_directory)?;
Ok(())
}
// fn run_command() -> Result<(), std::io::Error> {
// let (tx, mut rx) = mpsc::channel(1);
// task::spawn_blocking(move || {
// let mut child = Command::new("python3")
// .arg(&py_file)
// .arg(&args)
// .current_dir("resources/turbo_octo_potato")
// .stdout(Stdio::piped())
// .spawn()
// .expect("failed to execute child");
// let stdout = child.stdout.as_mut().unwrap();
// let mut reader = std::io::BufReader::new(stdout);
// let mut line = String::new();
// loop {
// let len = reader.read_line(&mut line).unwrap();
// if len == 0 {
// break;
// }
// tx.blocking_send(line.clone()).unwrap();
// line.clear();
// }
// child.wait().unwrap();
// });
// task::spawn(async move {
// while let Some(line) = rx.recv().await {
// println!("{}", line);
// }
// });
// Ok(())
// }
// run_command with python file and args as parameters
pub fn run_kcomebacks_command() -> Result<(), std::io::Error> {
// let (tx, mut rx) = mpsc::channel(1);
task::spawn_blocking(move || {
let mut child = Command::new("python3")
.arg("rpopfetch.py")
.arg("--cdn")
.current_dir("resources/turbo_octo_potato")
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
// let stdout = child.stdout.as_mut().unwrap();
// let mut reader = std::io::BufReader::new(stdout);
// let mut line = String::new();
// loop {
// let len = reader.read_line(&mut line).unwrap();
// if len == 0 {
// break;
// }
// tx.blocking_send(line.clone()).unwrap();
// line.clear();
// }
child.wait().unwrap();
});
// task::spawn(async move {
// while let Some(line) = rx.recv().await {
// Logger::info(&format!("[/v1/kcomebacks/update]: {}", line));
// }
// });
Logger::info("Updating kcomebacks...");
Ok(())
}
pub fn run_projects_command() -> Result<(), std::io::Error> {
// let (tx, mut rx) = mpsc::channel(1);
task::spawn_blocking(move || {
let mut child = Command::new("python3")
.arg("update_projects.py")
.arg("--cdn")
.current_dir("resources/turbo_octo_potato")
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
// let stdout = child.stdout.as_mut().unwrap();
// let mut reader = std::io::BufReader::new(stdout);
// let mut line = String::new();
// loop {
// let len = reader.read_line(&mut line).unwrap();
// if len == 0 {
// break;
// }
// tx.blocking_send(line.clone()).unwrap();
// line.clear();
// }
child.wait().unwrap();
});
// task::spawn(async move {
// while let Some(line) = rx.recv().await {
// Logger::info(&format!("[/v1/projects/update]: {}", line));
// }
// });
Logger::info("Updating projects...");
Ok(())
}
pub fn run_likedsongs_command() -> Result<(), std::io::Error> {
// let (tx, mut rx) = mpsc::channel(1);
task::spawn_blocking(move || {
let mut child = Command::new("python3")
.arg("likedsongsync2.py")
.current_dir("resources/turbo_octo_potato")
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
// let stdout = child.stdout.as_mut().unwrap();
// let mut reader = std::io::BufReader::new(stdout);
// let mut line = String::new();
// loop {
// let len = reader.read_line(&mut line).unwrap();
// if len == 0 {
// break;
// }
// tx.blocking_send(line.clone()).unwrap();
// line.clear();
// }
child.wait().unwrap();
});
// task::spawn(async move {
// while let Some(line) = rx.recv().await {
// Logger::info(&format!("[/v1/synclikedsongs]: {}", line));
// }
// });
Logger::info("Syncing liked songs...");
Ok(())
}
pub fn run_sync_all_command() -> Result<(), std::io::Error> {
task::spawn_blocking(move || {
let mut child = Command::new("python3")
.arg("script_interval_runner.py")
.current_dir("resources/turbo_octo_potato")
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
child.wait().unwrap();
});
Logger::info("Running all Sync Scripts...");
Ok(())
}

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>,
}
// 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();
}
}
// Respond with a message or perform other actions as needed
"Headers received".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),
}
}