mirror of
https://git.ethanthesleepy.one/ethanaobrien/ew
synced 2026-07-12 08:42:20 +08:00
398 lines
12 KiB
Rust
398 lines
12 KiB
Rust
use actix_web::{
|
|
HttpResponse,
|
|
HttpRequest,
|
|
http::header::HeaderValue,
|
|
http::header::ContentType
|
|
};
|
|
use json::{JsonValue, object};
|
|
use lazy_static::lazy_static;
|
|
use include_dir::{include_dir, Dir};
|
|
use std::fs;
|
|
|
|
use crate::include_file;
|
|
use crate::router::{userdata, items};
|
|
|
|
fn get_config() -> JsonValue {
|
|
let args = crate::get_args();
|
|
object!{
|
|
import: !args.disable_imports,
|
|
export: !args.disable_exports
|
|
}
|
|
}
|
|
|
|
fn get_login_token(req: &HttpRequest) -> Option<String> {
|
|
let blank_header = HeaderValue::from_static("");
|
|
let cookies = req.headers().get("Cookie").unwrap_or(&blank_header).to_str().unwrap_or("");
|
|
if cookies.is_empty() {
|
|
return None;
|
|
}
|
|
return Some(cookies.split("ew_token=").last().unwrap_or("").split(';').collect::<Vec<_>>()[0].to_string());
|
|
}
|
|
|
|
fn error(msg: &str) -> HttpResponse {
|
|
let resp = object!{
|
|
result: "ERR",
|
|
message: msg
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn login(_req: HttpRequest, body: String) -> HttpResponse {
|
|
let body = json::parse(&body).unwrap();
|
|
let token = userdata::webui_login(body["uid"].as_i64().unwrap(), &body["password"].to_string());
|
|
|
|
if token.is_err() {
|
|
return error(&token.unwrap_err());
|
|
}
|
|
|
|
let resp = object!{
|
|
result: "OK"
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.insert_header(("Set-Cookie", format!("ew_token={}; SameSite=Strict; HttpOnly", token.unwrap())))
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn import(_req: HttpRequest, body: String) -> HttpResponse {
|
|
if !get_config()["import"].as_bool().unwrap() {
|
|
return error("Importing accounts is disabled on this server.");
|
|
}
|
|
let body = json::parse(&body).unwrap();
|
|
|
|
let result = userdata::webui_import_user(body);
|
|
|
|
if result.is_err() {
|
|
return error(&result.unwrap_err());
|
|
}
|
|
let result = result.unwrap();
|
|
|
|
let resp = object!{
|
|
result: "OK",
|
|
uid: result["uid"].clone(),
|
|
migration_token: result["migration_token"].clone()
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn user(req: HttpRequest) -> HttpResponse {
|
|
let token = get_login_token(&req);
|
|
if token.is_none() {
|
|
return error("Not logged in");
|
|
}
|
|
let data = userdata::webui_get_user(&token.unwrap());
|
|
if data.is_none() {
|
|
return error("Expired login");
|
|
}
|
|
let mut data = data.unwrap();
|
|
|
|
data["userdata"]["user"]["rank"] = items::get_user_rank_data(data["userdata"]["user"]["exp"].as_i64().unwrap())["rank"].clone();
|
|
|
|
let resp = object!{
|
|
result: "OK",
|
|
data: data
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn start_loginbonus(req: HttpRequest, body: String) -> HttpResponse {
|
|
let token = get_login_token(&req);
|
|
if token.is_none() {
|
|
return error("Not logged in");
|
|
}
|
|
let body = json::parse(&body).unwrap();
|
|
let resp = userdata::webui_start_loginbonus(body["bonus_id"].as_i64().unwrap(), &token.unwrap());
|
|
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn set_time(req: HttpRequest, body: String) -> HttpResponse {
|
|
let token = get_login_token(&req);
|
|
if token.is_none() {
|
|
return error("Not logged in");
|
|
}
|
|
let body = json::parse(&body).unwrap();
|
|
let resp = userdata::set_server_time(body["timestamp"].as_i64().unwrap(), &token.unwrap());
|
|
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn logout(req: HttpRequest) -> HttpResponse {
|
|
let token = get_login_token(&req);
|
|
if token.is_some() {
|
|
userdata::webui_logout(&token.unwrap());
|
|
}
|
|
let resp = object!{
|
|
result: "OK"
|
|
};
|
|
HttpResponse::Found()
|
|
.insert_header(ContentType::json())
|
|
.insert_header(("Set-Cookie", "ew_token=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT"))
|
|
.insert_header(("Location", "/login.html"))
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
static WEBUI_ASSETS: Dir<'_> = include_dir!("webui/");
|
|
|
|
pub fn main(req: HttpRequest) -> HttpResponse {
|
|
let path = if req.path().ends_with("/") { format!("{}index.html", req.path()) } else { req.path().to_string() };
|
|
let mut chars = path.chars();
|
|
chars.next();
|
|
let path = chars.as_str();
|
|
|
|
if path == "login.html" {
|
|
let token = get_login_token(&req);
|
|
if token.is_some() {
|
|
let data = userdata::webui_get_user(&token.unwrap());
|
|
if data.is_some() {
|
|
return HttpResponse::Found()
|
|
.insert_header(("Location", "/account.html"))
|
|
.body("");
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(file) = WEBUI_ASSETS.get_file(&path) {
|
|
let body = file.contents();
|
|
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
|
return HttpResponse::Ok()
|
|
.insert_header(ContentType(mime))
|
|
.insert_header(("content-length", body.len()))
|
|
.body(body);
|
|
} else if path.starts_with("webui/images/card-thumbnails") {
|
|
let args = crate::get_args();
|
|
|
|
let file_name = path.split("/").last().unwrap_or("");
|
|
let file_path = format!("{}/{}", args.image_asset_path, file_name).replace("//", "/");
|
|
if args.image_asset_path != "" && let Ok(body) = fs::read(file_path) {
|
|
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
|
return HttpResponse::Ok()
|
|
.insert_header(ContentType(mime))
|
|
.insert_header(("content-length", body.len()))
|
|
.body(body);
|
|
} else {
|
|
if args.image_asset_path != "" {
|
|
println!("File '{file_name}' was requested, but no file was found on the disk!");
|
|
}
|
|
return HttpResponse::SeeOther()
|
|
.insert_header(("location", format!("https://sif2-api.ethanthesleepy.one{}", req.path())))
|
|
.body("");
|
|
}
|
|
}
|
|
|
|
HttpResponse::Found()
|
|
.insert_header(("Location", "/"))
|
|
.body("")
|
|
}
|
|
|
|
pub fn export(req: HttpRequest) -> HttpResponse {
|
|
if !get_config()["export"].as_bool().unwrap() {
|
|
return error("Exporting accounts is disabled on this server.");
|
|
}
|
|
let token = get_login_token(&req);
|
|
if token.is_none() {
|
|
return error("Not logged in");
|
|
}
|
|
let resp = object!{
|
|
result: "OK",
|
|
data: userdata::export_user(&token.unwrap()).unwrap()
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn server_info(_req: HttpRequest) -> HttpResponse {
|
|
let args = crate::get_args();
|
|
|
|
let resp = object!{
|
|
result: "OK",
|
|
data: {
|
|
account_import: get_config()["import"].as_bool().unwrap(),
|
|
links: {
|
|
global: args.global_android,
|
|
japan: args.japan_android,
|
|
ios: {
|
|
global: args.global_ios,
|
|
japan: args.japan_ios
|
|
},
|
|
assets: args.assets_url
|
|
}
|
|
}
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
fn get_query_str(req: &HttpRequest, key: &str, def: &str) -> String {
|
|
let query_str = req.query_string();
|
|
query_str
|
|
.split('&')
|
|
.find(|s| s.starts_with(&format!("{key}=")))
|
|
.and_then(|s| s.split('=').nth(1))
|
|
.unwrap_or(def).to_string()
|
|
}
|
|
|
|
pub fn get_card_info(req: HttpRequest) -> HttpResponse {
|
|
let page = get_query_str(&req, "page", "1").parse::<usize>().unwrap_or(1) - 1;
|
|
let max = get_query_str(&req, "max", "10").parse::<usize>().unwrap_or(10);
|
|
let all = get_query_str(&req, "all", "false");
|
|
let name_query = get_query_str(&req, "query", "");
|
|
|
|
let start = page * max;
|
|
|
|
let items = json::parse(&include_file!("src/router/webui/cards.json")).unwrap();
|
|
|
|
if all == "true" {
|
|
let resp = object!{
|
|
total_pages: 1,
|
|
current: items
|
|
};
|
|
|
|
return HttpResponse::Ok()
|
|
.content_type(ContentType::json())
|
|
.body(json::stringify(resp));
|
|
}
|
|
|
|
let mut filtered_items: Vec<_> = items.members().collect();
|
|
|
|
if !name_query.is_empty() {
|
|
let lowercase_query = name_query.to_lowercase();
|
|
filtered_items.retain(|item| {
|
|
item["name"].to_string().to_lowercase().contains(&lowercase_query)
|
|
});
|
|
}
|
|
|
|
let total_len = filtered_items.len();
|
|
|
|
let page_items: Vec<_> = filtered_items
|
|
.into_iter()
|
|
.skip(start)
|
|
.take(max)
|
|
.cloned()
|
|
.collect();
|
|
|
|
if page_items.is_empty() {
|
|
return HttpResponse::NotFound()
|
|
.finish();
|
|
}
|
|
|
|
let total_pages = (total_len as f64 / max as f64).ceil() as usize;
|
|
let args = crate::get_args();
|
|
|
|
let resp = object!{
|
|
total_pages: total_pages,
|
|
current: page_items,
|
|
image_path: args.image_asset_path
|
|
};
|
|
|
|
HttpResponse::Ok()
|
|
.content_type(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
pub fn get_music_info(req: HttpRequest) -> HttpResponse {
|
|
let page = get_query_str(&req, "page", "1").parse::<usize>().unwrap_or(1) - 1;
|
|
let max = get_query_str(&req, "max", "10").parse::<usize>().unwrap_or(10);
|
|
let lang = get_query_str(&req, "lang", "JP");
|
|
|
|
let start = page * max;
|
|
|
|
let items = if lang == "EN" {
|
|
json::parse(&include_file!("src/router/databases/json/global/music.json")).unwrap()
|
|
} else {
|
|
json::parse(&include_file!("src/router/databases/json/music.json")).unwrap()
|
|
};
|
|
|
|
let page_items: Vec<_> = items.members()
|
|
.skip(start)
|
|
.take(max)
|
|
.cloned()
|
|
.collect();
|
|
|
|
if page_items.is_empty() {
|
|
return HttpResponse::NotFound()
|
|
.finish();
|
|
}
|
|
|
|
let total_items = items.len();
|
|
let total_pages = (total_items as f64 / max as f64).ceil() as usize;
|
|
|
|
let resp = object!{
|
|
total_pages: total_pages,
|
|
current: page_items
|
|
};
|
|
|
|
HttpResponse::Ok()
|
|
.content_type(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref ITEM: JsonValue = json::parse(&include_file!("src/router/webui/item.json")).unwrap();
|
|
static ref LOGIN_BONUS: JsonValue = json::parse(&include_file!("src/router/webui/login_bonus.json")).unwrap();
|
|
}
|
|
|
|
pub fn list_login_bonus(_req: HttpRequest) -> HttpResponse {
|
|
HttpResponse::Ok()
|
|
.content_type(ContentType::json())
|
|
.body(json::stringify(LOGIN_BONUS.clone()))
|
|
}
|
|
|
|
pub fn list_items(_req: HttpRequest) -> HttpResponse {
|
|
HttpResponse::Ok()
|
|
.content_type(ContentType::json())
|
|
.body(json::stringify(ITEM.clone()))
|
|
}
|
|
|
|
pub fn cheat(req: HttpRequest, _body: String) -> HttpResponse {
|
|
let token = get_login_token(&req);
|
|
if token.is_none() {
|
|
return error("Not logged in");
|
|
}
|
|
let key = userdata::webui_login_token(&token.unwrap());
|
|
if key.is_none() {
|
|
return error("Not logged in");
|
|
}
|
|
let key = key.unwrap();
|
|
let mut user = userdata::get_acc_home(&key);
|
|
|
|
for item in ITEM.entries() {
|
|
let id = item.0.parse::<i32>().unwrap_or(0);
|
|
let data = item.1;
|
|
if id == 0 {
|
|
continue;
|
|
}
|
|
let reward_type = data["reward_type"].as_i32().unwrap();
|
|
let limit = if reward_type == 4 {
|
|
items::LIMIT_COINS
|
|
} else if reward_type == 1 {
|
|
items::LIMIT_PRIMOGEMS
|
|
} else {
|
|
items::LIMIT_ITEMS
|
|
};
|
|
items::gift_item_basic(id, limit, reward_type, "You have cheated. Here are \"gifts\".", &mut user);
|
|
}
|
|
|
|
userdata::save_acc_home(&key, user);
|
|
|
|
let resp = object!{
|
|
result: "OK"
|
|
};
|
|
|
|
HttpResponse::Ok()
|
|
.insert_header(ContentType::json())
|
|
.body(json::stringify(resp))
|
|
}
|