feat: WebUI
This is an initial version of a web interface, allowing the user to visit http://localhost, log in with the same credentials as in the game, and make use of the /custom/addItem endpoint.
This commit is contained in:
parent
675e6c4583
commit
ce321e2eb4
@ -12,6 +12,7 @@ import { steamPacksController } from "@/src/controllers/misc/steamPacksControlle
|
||||
import { customRouter } from "@/src/routes/custom";
|
||||
import { dynamicController } from "@/src/routes/dynamic";
|
||||
import { statsRouter } from "@/src/routes/stats";
|
||||
import { webuiRouter } from "@/src/routes/webui";
|
||||
import { connectDatabase } from "@/src/services/mongoService";
|
||||
import { registerLogFileCreationListener } from "@/src/utils/logger";
|
||||
|
||||
@ -35,6 +36,8 @@ app.use("/:id/dynamic", dynamicController);
|
||||
app.post("/pay/steamPacks.php", steamPacksController);
|
||||
app.use("/stats", statsRouter);
|
||||
|
||||
app.use("/", webuiRouter);
|
||||
|
||||
app.use(unknownEndpointHandler);
|
||||
|
||||
//app.use(errorHandler)
|
||||
|
25
src/controllers/custom/getItemListsController.ts
Normal file
25
src/controllers/custom/getItemListsController.ts
Normal 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 };
|
@ -1,9 +1,12 @@
|
||||
import { addItemController } from "@/src/controllers/custom/addItemController";
|
||||
import { createAccountController } from "@/src/controllers/custom/createAccountController";
|
||||
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();
|
||||
|
||||
customRouter.get("/getItemLists", getItemListsController);
|
||||
|
||||
customRouter.post("/createAccount", createAccountController);
|
||||
customRouter.post("/addItem", addItemController);
|
||||
|
||||
|
24
src/routes/webui.ts
Normal file
24
src/routes/webui.ts
Normal 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 };
|
@ -1,21 +1,32 @@
|
||||
import { getIndexAfter } from "@/src/helpers/stringHelpers";
|
||||
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">;
|
||||
type MinItem = Omit<Item, "patchlogs">;
|
||||
export type MinWarframe = Omit<Warframe, "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(
|
||||
item => {
|
||||
export const warframes: MinWarframe[] = Array.from(new Items({ category: ["Warframes"] }) as Warframe[])
|
||||
.filter(item => {
|
||||
return item.uniqueName.substring(0, 30) != "/Lotus/Powersuits/EntratiMech/";
|
||||
})
|
||||
.map(item => {
|
||||
const next = { ...item };
|
||||
delete next.patchlogs;
|
||||
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 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 };
|
||||
delete next.patchlogs;
|
||||
return next;
|
||||
|
65
static/webui/index.html
Normal file
65
static/webui/index.html
Normal 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
110
static/webui/script.js
Normal 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");
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user