feat: WebUI (#155)

This commit is contained in:
Sainan 2024-05-04 14:44:23 +02:00 committed by GitHub
parent 675e6c4583
commit c31d82b61e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 251 additions and 10 deletions

View File

@ -12,6 +12,7 @@ import { steamPacksController } from "@/src/controllers/misc/steamPacksControlle
import { customRouter } from "@/src/routes/custom"; import { customRouter } from "@/src/routes/custom";
import { dynamicController } from "@/src/routes/dynamic"; import { dynamicController } from "@/src/routes/dynamic";
import { statsRouter } from "@/src/routes/stats"; import { statsRouter } from "@/src/routes/stats";
import { webuiRouter } from "@/src/routes/webui";
import { connectDatabase } from "@/src/services/mongoService"; import { connectDatabase } from "@/src/services/mongoService";
import { registerLogFileCreationListener } from "@/src/utils/logger"; import { registerLogFileCreationListener } from "@/src/utils/logger";
@ -35,6 +36,8 @@ app.use("/:id/dynamic", dynamicController);
app.post("/pay/steamPacks.php", steamPacksController); app.post("/pay/steamPacks.php", steamPacksController);
app.use("/stats", statsRouter); app.use("/stats", statsRouter);
app.use("/", webuiRouter);
app.use(unknownEndpointHandler); app.use(unknownEndpointHandler);
//app.use(errorHandler) //app.use(errorHandler)

View File

@ -0,0 +1,25 @@
import { RequestHandler } from "express";
import { MinItem, warframes, weapons } from "@/src/services/itemDataService";
interface ListedItem {
uniqueName: string;
name: string;
}
function reduceItems(items: MinItem[]): ListedItem[] {
return items.map((item: MinItem): ListedItem => {
return {
uniqueName: item.uniqueName,
name: item.name
};
});
}
const getItemListsController: RequestHandler = (_req, res) => {
res.json({
warframes: reduceItems(warframes),
weapons: reduceItems(weapons)
});
};
export { getItemListsController };

View File

@ -1,9 +1,12 @@
import { addItemController } from "@/src/controllers/custom/addItemController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import express from "express"; import express from "express";
import { getItemListsController } from "@/src/controllers/custom/getItemListsController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { addItemController } from "@/src/controllers/custom/addItemController";
const customRouter = express.Router(); const customRouter = express.Router();
customRouter.get("/getItemLists", getItemListsController);
customRouter.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
customRouter.post("/addItem", addItemController); customRouter.post("/addItem", addItemController);

24
src/routes/webui.ts Normal file
View File

@ -0,0 +1,24 @@
import express from "express";
import path from "path";
const webuiRouter = express.Router();
webuiRouter.get("/", (_req, res) => {
res.redirect("/webui/");
});
const rootDir = path.join(__dirname, "../..");
webuiRouter.get("/webui/", (req, res) => {
if (req.path != "/webui/") {
res.redirect("/webui/");
} else {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
}
});
webuiRouter.get("/webui/script.js", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/script.js"));
});
export { webuiRouter };

View File

@ -1,21 +1,32 @@
import { getIndexAfter } from "@/src/helpers/stringHelpers"; import { getIndexAfter } from "@/src/helpers/stringHelpers";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import Items, { Buildable, Category, Item, Warframe, Weapon } from "warframe-items"; import Items, { Buildable, Category, MinimalItem, Warframe, Weapon } from "warframe-items";
type MinWeapon = Omit<Weapon, "patchlogs">; export type MinWarframe = Omit<Warframe, "patchlogs">;
type MinItem = Omit<Item, "patchlogs">; export type MinWeapon = Omit<Weapon, "patchlogs">;
export type MinItem = Omit<MinimalItem, "patchlogs">;
export const weapons: MinWeapon[] = (new Items({ category: ["Primary", "Secondary", "Melee"] }) as Weapon[]).map( export const warframes: MinWarframe[] = Array.from(new Items({ category: ["Warframes"] }) as Warframe[])
item => { .filter(item => {
return item.uniqueName.substring(0, 30) != "/Lotus/Powersuits/EntratiMech/";
})
.map(item => {
const next = { ...item }; const next = { ...item };
delete next.patchlogs; delete next.patchlogs;
return next; return next;
} });
);
export const weapons: MinWeapon[] = Array.from(
new Items({ category: ["Primary", "Secondary", "Melee"] }) as Weapon[]
).map(item => {
const next = { ...item };
delete next.patchlogs;
return next;
});
export type WeaponTypeInternal = "LongGuns" | "Pistols" | "Melee"; export type WeaponTypeInternal = "LongGuns" | "Pistols" | "Melee";
export const items: MinItem[] = new Items({ category: ["All"] }).map(item => { export const items: MinItem[] = Array.from(new Items({ category: ["All"] }) as MinimalItem[]).map(item => {
const next = { ...item }; const next = { ...item };
delete next.patchlogs; delete next.patchlogs;
return next; return next;

65
static/webui/index.html Normal file
View File

@ -0,0 +1,65 @@
<!doctype html>
<html lang="en" data-bs-theme="dark">
<head>
<title>OpenWF WebUI</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
</head>
<body>
<div class="container pt-3 pb-3">
<h1>OpenWF WebUI</h1>
<div id="login-view">
<p>Login using your OpenWF account credentials.</p>
<form onsubmit="doLogin();return false;">
<label for="email">Email address</label>
<input class="form-control" type="email" id="email" required />
<br />
<label for="password">Password</label>
<input class="form-control" type="password" id="password" required />
<br />
<button class="btn btn-primary" type="submit">Login</button>
</form>
</div>
<div id="main-view" class="d-none">
<p>Hello, <b class="displayname"></b>! <a href="#" onclick="logout();">Logout</a></p>
<div class="d-flex">
<div class="card m-1 w-50">
<h5 class="card-header">Acquire Warframe</h5>
<form class="card-body row" onsubmit="doAcquireWarframe();return false;">
<div class="col-xxl-10">
<input class="form-control" id="warframe-to-acquire" list="datalist-warframes" />
</div>
<div class="col-xxl-2">
<button class="btn btn-primary" type="submit">Acquire</button>
</div>
</form>
</div>
<div class="card m-1 w-50">
<h5 class="card-header">Acquire Weapon</h5>
<form class="card-body row" onsubmit="doAcquireWeapon();return false;">
<div class="col-xxl-10">
<input class="form-control" id="weapon-to-acquire" list="datalist-weapons" />
</div>
<div class="col-xxl-2">
<button class="btn btn-primary" type="submit">Acquire</button>
</div>
</form>
</div>
</div>
</div>
</div>
<datalist id="datalist-warframes"></datalist>
<datalist id="datalist-weapons"></datalist>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"
></script>
<script src="https://cdn.jsdelivr.net/gh/angeal185/whirlpool-js/dist/whirlpool-js.min.js"></script>
<script src="script.js"></script>
</body>
</html>

110
static/webui/script.js Normal file
View File

@ -0,0 +1,110 @@
function doLogin() {
localStorage.setItem("email", $("#email").val());
localStorage.setItem("password", $("#password").val());
loginFromLocalStorage();
}
function loginFromLocalStorage() {
const req = $.post({
url: "/api/login.php",
contentType: "text/plain",
data: JSON.stringify({
email: localStorage.getItem("email"),
password: wp.encSync(localStorage.getItem("password"), "hex"),
time: parseInt(new Date() / 1000),
s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind
lang: "en",
date: 1501230947855458660, // ???
ClientType: "",
PS: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==" // anti-cheat data
})
});
req.done(data => {
$("#login-view").addClass("d-none");
$("#main-view").removeClass("d-none");
$(".displayname").text(data.DisplayName);
window.accountId = data.id;
});
req.fail(() => {
logout();
alert("Login failed");
});
}
function logout() {
localStorage.removeItem("email");
localStorage.removeItem("password");
$("#login-view").removeClass("d-none");
$("#main-view").addClass("d-none");
}
if (localStorage.getItem("email") && localStorage.getItem("password")) {
loginFromLocalStorage();
}
const req = $.get("/custom/getItemLists");
req.done(data => {
for (const [type, items] of Object.entries(data)) {
items.forEach(item => {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
});
}
});
function getKey(input) {
return document
.getElementById(input.getAttribute("list"))
.querySelector("[value='" + input.value.split("'").join("\\'") + "']")
?.getAttribute("data-key");
}
function doAcquireWarframe() {
const uniqueName = getKey(document.getElementById("warframe-to-acquire"));
if (!uniqueName) {
$("#warframe-to-acquire").addClass("is-invalid");
return;
}
const req = $.post({
url: "/custom/addItem",
contentType: "application/json",
data: JSON.stringify({
type: "Powersuit",
internalName: uniqueName,
accountId: window.accountId
})
});
req.done(() => {
alert("Warframe added to your inventory! Visit navigation to force an inventory update.");
});
}
$("#warframe-to-acquire").on("input", () => {
$("#warframe-to-acquire").removeClass("is-invalid");
});
function doAcquireWeapon() {
const uniqueName = getKey(document.getElementById("weapon-to-acquire"));
if (!uniqueName) {
$("#weapon-to-acquire").addClass("is-invalid");
return;
}
const req = $.post({
url: "/custom/addItem",
contentType: "application/json",
data: JSON.stringify({
type: "Weapon",
internalName: uniqueName,
accountId: window.accountId
})
});
req.done(() => {
alert("Weapon added to your inventory! Visit navigation to force an inventory update.");
});
}
$("#weapon-to-acquire").on("input", () => {
$("#weapon-to-acquire").removeClass("is-invalid");
});