mirror of
https://github.com/JonasunderscoreJones/jonas_jones-api.git
synced 2025-10-23 09:09:18 +02:00
Compare commits
No commits in common. "rust" and "v0.2.1" have entirely different histories.
17 changed files with 64 additions and 1385 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,4 +5,3 @@
|
|||
/.vscode
|
||||
|
||||
.env
|
||||
/resources
|
||||
|
|
716
Cargo.lock
generated
716
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "jonas_jones-api"
|
||||
version = "0.4.1"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -11,13 +11,9 @@ parking_lot = "0.12.1"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.35", features = ["macros"] }
|
||||
dotenv = "0.15.0"
|
||||
lastfm = "0.10.0"
|
||||
lastfm = "0.7.0"
|
||||
log = "0.4.20"
|
||||
chrono = "0.4.31"
|
||||
toml = "0.8.8"
|
||||
reqwest = { version = "0.12.5", features = ["json", "blocking"] }
|
||||
reqwest = { version = "0.11.22", features = ["json"] }
|
||||
serde_json = "1.0.108"
|
||||
regex = "1"
|
||||
git2 = "0.19.0"
|
||||
ip2location = "0.5.0"
|
||||
sha2 = "0.10.8"
|
||||
|
|
31
README.md
31
README.md
|
@ -34,34 +34,3 @@ 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
|
|
@ -1,11 +0,0 @@
|
|||
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"]
|
|
@ -1,9 +0,0 @@
|
|||
requests
|
||||
datetime
|
||||
python-dotenv
|
||||
spotipy
|
||||
praw
|
||||
spotipy
|
||||
pylast
|
||||
typing
|
||||
markdown
|
|
@ -1,14 +0,0 @@
|
|||
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();
|
||||
}
|
35
src/main.rs
35
src/main.rs
|
@ -1,50 +1,19 @@
|
|||
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();
|
||||
|
||||
// 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);
|
||||
server::serve().await;
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
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(())
|
||||
}
|
|
@ -1,19 +1,13 @@
|
|||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::env;
|
||||
|
||||
use lastfm::reqwest::StatusCode;
|
||||
use warp::filters::path::FullPath;
|
||||
use warp::http::Method;
|
||||
use reqwest::StatusCode;
|
||||
use warp::Filter;
|
||||
use warp::reply::Reply;
|
||||
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError};
|
||||
use crate::v1::get_v1_routes;
|
||||
use crate::{parse_ip, request_logger, Logger};
|
||||
use crate::iplookup::ip_lookup;
|
||||
use crate::{Logger, parse_ip};
|
||||
|
||||
|
||||
pub async fn serve() {
|
||||
|
@ -26,38 +20,9 @@ pub async fn serve() {
|
|||
|
||||
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 = log_request
|
||||
.clone().untuple_one().and(favicon.or(status.or(get_v1_routes())
|
||||
.recover(handle_rejection)));
|
||||
let routes = favicon.or(get_v1_routes())
|
||||
.recover(handle_rejection);
|
||||
|
||||
|
||||
async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible> {
|
||||
|
@ -78,7 +43,7 @@ pub async fn serve() {
|
|||
message: message.into(),
|
||||
});
|
||||
|
||||
Ok(warp::reply::with_status(json, StatusCode::from_u16(code.as_u16()).unwrap()))
|
||||
Ok(warp::reply::with_status(json, code))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
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")]))))
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
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")))
|
||||
}
|
|
@ -2,16 +2,13 @@ mod builtin;
|
|||
mod debug;
|
||||
mod kcomebacks;
|
||||
mod projects;
|
||||
mod run;
|
||||
|
||||
pub use run::setup as run_setup;
|
||||
pub use run::run_sync_all_command;
|
||||
mod updates;
|
||||
|
||||
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 projects::get_project_routes as get_v1_project_routes;
|
||||
pub use run::get_run_routes as get_v1_updates_routes;
|
||||
pub use updates::get_updates_routes as get_v1_updates_routes;
|
||||
|
||||
use warp::Filter;
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@ pub fn get_project_routes() -> impl warp::Filter<Extract = impl warp::Reply, Err
|
|||
|
||||
.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())
|
||||
)
|
||||
.or(get_project_filter_routes()))
|
||||
}
|
||||
|
||||
// get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json
|
||||
|
|
|
@ -1,284 +0,0 @@
|
|||
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(())
|
||||
}
|
|
@ -1,144 +1,29 @@
|
|||
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::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))
|
||||
.and(warp::path::param())
|
||||
.and(warp::path::param())
|
||||
.and(warp::path::param())
|
||||
.and(warp::path::end())
|
||||
.and(warp::addr::remote())
|
||||
.map(handle_path)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct ModData {
|
||||
package: String,
|
||||
name: String,
|
||||
versions: Vec<HashMap<String, HashMap<String, ModVersion>>>,
|
||||
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, Clone)]
|
||||
struct ModVersion {
|
||||
recommended: String,
|
||||
latest: String,
|
||||
all: Vec<String>,
|
||||
}
|
||||
// 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"));
|
||||
// }
|
||||
|
||||
// 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));
|
||||
}
|
||||
// // Respond with a message or perform other actions as needed
|
||||
// "Headers received".to_string()
|
||||
// }
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
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),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue