Merge branch 'main', commit 'refs/pull/66/head' of https://github.com/MathMan05/JankClient
This commit is contained in:
commit
f36b8b7c50
7 changed files with 126 additions and 126 deletions
62
src/index.ts
62
src/index.ts
|
@ -2,19 +2,20 @@
|
|||
|
||||
import compression from"compression";
|
||||
import express, { Request, Response }from"express";
|
||||
import fs from"node:fs";
|
||||
import fetch from"node-fetch";
|
||||
import fs from"node:fs/promises";
|
||||
import path from"node:path";
|
||||
import{ observe, uptime }from"./stats.js";
|
||||
import{ getApiUrls, inviteResponse }from"./utils.js";
|
||||
import{ fileURLToPath }from"node:url";
|
||||
const devmode = (process.env.NODE_ENV || 'development')==='development';
|
||||
import process from"node:process";
|
||||
|
||||
const devmode = (process.env.NODE_ENV || "development") === "development";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
interface Instance {
|
||||
name: string;
|
||||
[key: string]: any;
|
||||
name: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
@ -55,9 +56,9 @@ async function updateInstances(): Promise<void>{
|
|||
|
||||
updateInstances();
|
||||
|
||||
app.use("/getupdates", (_req: Request, res: Response)=>{
|
||||
app.use("/getupdates", async (_req: Request, res: Response)=>{
|
||||
try{
|
||||
const stats = fs.statSync(path.join(__dirname, "webpage"));
|
||||
const stats = await fs.stat(path.join(__dirname, "webpage"));
|
||||
res.send(stats.mtimeMs.toString());
|
||||
}catch(error){
|
||||
console.error("Error getting updates:", error);
|
||||
|
@ -70,7 +71,7 @@ app.use("/services/oembed", (req: Request, res: Response)=>{
|
|||
});
|
||||
|
||||
app.use("/uptime", (req: Request, res: Response)=>{
|
||||
const instanceUptime = uptime[req.query.name as string];
|
||||
const instanceUptime = uptime.get(req.query.name as string);
|
||||
res.send(instanceUptime);
|
||||
});
|
||||
|
||||
|
@ -106,33 +107,40 @@ app.use("/", async (req: Request, res: Response)=>{
|
|||
return;
|
||||
}
|
||||
const filePath = path.join(__dirname, "webpage", req.path);
|
||||
if(fs.existsSync(filePath)){
|
||||
try{
|
||||
await fs.access(filePath);
|
||||
if(devmode){
|
||||
const filePath2 = path.join(__dirname, "../src/webpage", req.path);
|
||||
if(fs.existsSync(filePath2)){
|
||||
try{
|
||||
await fs.access(filePath2);
|
||||
res.sendFile(filePath2);
|
||||
return;
|
||||
}
|
||||
}catch{}
|
||||
}
|
||||
res.sendFile(filePath);
|
||||
}else if(fs.existsSync(`${filePath}.html`)){
|
||||
if(devmode){
|
||||
const filePath2 = path.join(__dirname, "../src/webpage", req.path);
|
||||
if(fs.existsSync(filePath2+".html")){
|
||||
res.sendFile(filePath2+".html");
|
||||
return;
|
||||
}catch{
|
||||
try{
|
||||
await fs.access(`${filePath}.html`);
|
||||
if(devmode){
|
||||
const filePath2 = path.join(__dirname, "../src/webpage", req.path);
|
||||
try{
|
||||
await fs.access(filePath2 + ".html");
|
||||
res.sendFile(filePath2 + ".html");
|
||||
return;
|
||||
}catch{}
|
||||
}
|
||||
}
|
||||
res.sendFile(`${filePath}.html`);
|
||||
}else{
|
||||
if(req.path.startsWith("/src/webpage")){
|
||||
const filePath2 = path.join(__dirname, "..", req.path);
|
||||
if(fs.existsSync(`${filePath2}`)){
|
||||
res.sendFile(filePath2);
|
||||
return;
|
||||
res.sendFile(`${filePath}.html`);
|
||||
}catch{
|
||||
if(req.path.startsWith("/src/webpage")){
|
||||
const filePath2 = path.join(__dirname, "..", req.path);
|
||||
try{
|
||||
await fs.access(filePath2);
|
||||
res.sendFile(filePath2);
|
||||
return;
|
||||
}catch{}
|
||||
}
|
||||
res.sendFile(path.join(__dirname, "webpage", "index.html"));
|
||||
}
|
||||
res.sendFile(path.join(__dirname, "webpage", "index.html"));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -141,4 +149,4 @@ app.listen(PORT, ()=>{
|
|||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
|
||||
export{ getApiUrls };
|
||||
export{ getApiUrls };
|
58
src/stats.ts
58
src/stats.ts
|
@ -1,8 +1,8 @@
|
|||
import fs from"node:fs";
|
||||
import path from"node:path";
|
||||
import fetch from"node-fetch";
|
||||
import{ getApiUrls }from"./utils.js";
|
||||
import{ fileURLToPath }from"node:url";
|
||||
import{ setTimeout, clearTimeout }from"node:timers";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
@ -12,10 +12,6 @@ interface UptimeEntry {
|
|||
online: boolean;
|
||||
}
|
||||
|
||||
interface UptimeObject {
|
||||
[key: string]: UptimeEntry[];
|
||||
}
|
||||
|
||||
interface Instance {
|
||||
name: string;
|
||||
urls?: { api: string };
|
||||
|
@ -28,37 +24,46 @@ interface Instance {
|
|||
};
|
||||
}
|
||||
|
||||
const uptimeObject: UptimeObject = loadUptimeObject();
|
||||
const uptimeObject: Map<string, UptimeEntry[]> = loadUptimeObject();
|
||||
export{ uptimeObject as uptime };
|
||||
|
||||
function loadUptimeObject(): UptimeObject{
|
||||
function loadUptimeObject(): Map<string, UptimeEntry[]>{
|
||||
const filePath = path.join(__dirname, "..", "uptime.json");
|
||||
if(fs.existsSync(filePath)){
|
||||
try{
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
return new Map(Object.entries(data));
|
||||
}catch(error){
|
||||
console.error("Error reading uptime.json:", error);
|
||||
return{};
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
return{};
|
||||
return new Map();
|
||||
}
|
||||
|
||||
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function saveUptimeObject(): void{
|
||||
fs.writeFile(
|
||||
path.join(__dirname, "..", "uptime.json"),
|
||||
JSON.stringify(uptimeObject),
|
||||
error=>{
|
||||
if(error){
|
||||
console.error("Error saving uptime.json:", error);
|
||||
if(saveTimeout){
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
saveTimeout = setTimeout(()=>{
|
||||
const data = Object.fromEntries(uptimeObject);
|
||||
fs.writeFile(
|
||||
path.join(__dirname, "..", "uptime.json"),
|
||||
JSON.stringify(data),
|
||||
error=>{
|
||||
if(error){
|
||||
console.error("Error saving uptime.json:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}, 5000); // Batch updates every 5 seconds
|
||||
}
|
||||
|
||||
function removeUndefinedKey(): void{
|
||||
if(uptimeObject.undefined){
|
||||
delete uptimeObject.undefined;
|
||||
if(uptimeObject.has("undefined")){
|
||||
uptimeObject.delete("undefined");
|
||||
saveUptimeObject();
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +90,7 @@ async function resolveInstance(
|
|||
return;
|
||||
}
|
||||
activeInstances.add(instance.name);
|
||||
await checkHealth(instance, api); // Ensure health is checked immediately
|
||||
await checkHealth(instance, api);
|
||||
scheduleHealthCheck(instance, api);
|
||||
}catch(error){
|
||||
console.error("Error resolving instance:", error);
|
||||
|
@ -127,7 +132,6 @@ async function checkHealth(
|
|||
const response = await fetch(`${api}/ping`, { method: "HEAD" });
|
||||
console.log(`Checking health for ${instance.name}: ${response.status}`);
|
||||
if(response.ok || tries > 3){
|
||||
console.log(`Setting status for ${instance.name} to ${response.ok}`);
|
||||
setStatus(instance, response.ok);
|
||||
}else{
|
||||
retryHealthCheck(instance, api, tries);
|
||||
|
@ -151,7 +155,7 @@ function retryHealthCheck(
|
|||
}
|
||||
|
||||
function updateInactiveInstances(activeInstances: Set<string>): void{
|
||||
for(const key of Object.keys(uptimeObject)){
|
||||
for(const key of uptimeObject.keys()){
|
||||
if(!activeInstances.has(key)){
|
||||
setStatus(key, false);
|
||||
}
|
||||
|
@ -159,7 +163,7 @@ function updateInactiveInstances(activeInstances: Set<string>): void{
|
|||
}
|
||||
|
||||
function calcStats(instance: Instance): void{
|
||||
const obj = uptimeObject[instance.name];
|
||||
const obj = uptimeObject.get(instance.name);
|
||||
if(!obj)return;
|
||||
|
||||
const now = Date.now();
|
||||
|
@ -235,11 +239,11 @@ function calculateUptimeStats(
|
|||
|
||||
function setStatus(instance: string | Instance, status: boolean): void{
|
||||
const name = typeof instance === "string" ? instance : instance.name;
|
||||
let obj = uptimeObject[name];
|
||||
let obj = uptimeObject.get(name);
|
||||
|
||||
if(!obj){
|
||||
obj = [];
|
||||
uptimeObject[name] = obj;
|
||||
uptimeObject.set(name, obj);
|
||||
}
|
||||
|
||||
const lastEntry = obj.at(-1);
|
||||
|
@ -251,4 +255,4 @@ function setStatus(instance: string | Instance, status: boolean): void{
|
|||
calcStats(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
src/utils.ts
70
src/utils.ts
|
@ -1,23 +1,22 @@
|
|||
import fetch from"node-fetch";
|
||||
import{ Request, Response }from"express";
|
||||
|
||||
interface ApiUrls {
|
||||
api: string;
|
||||
gateway: string;
|
||||
cdn: string;
|
||||
wellknown: string;
|
||||
api: string;
|
||||
gateway: string;
|
||||
cdn: string;
|
||||
wellknown: string;
|
||||
}
|
||||
|
||||
interface Invite {
|
||||
guild: {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
id: string;
|
||||
};
|
||||
inviter?: {
|
||||
username: string;
|
||||
};
|
||||
guild: {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
id: string;
|
||||
};
|
||||
inviter?: {
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function getApiUrls(url: string): Promise<ApiUrls | null>{
|
||||
|
@ -25,15 +24,11 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
|
|||
url += "/";
|
||||
}
|
||||
try{
|
||||
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(
|
||||
res=>res.json() as Promise<ApiUrls>
|
||||
);
|
||||
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json());
|
||||
const api = info.api;
|
||||
const apiUrl = new URL(api);
|
||||
const policies: any = await fetch(
|
||||
`${api}${
|
||||
apiUrl.pathname.includes("api") ? "" : "api"
|
||||
}/policies/instance/domains`
|
||||
`${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`
|
||||
).then(res=>res.json());
|
||||
return{
|
||||
api: policies.apiEndpoint,
|
||||
|
@ -47,14 +42,11 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
|
|||
}
|
||||
}
|
||||
|
||||
export async function inviteResponse(
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void>{
|
||||
export async function inviteResponse(req: Request, res: Response): Promise<void>{
|
||||
let url: URL;
|
||||
if(URL.canParse(req.query.url as string)){
|
||||
try{
|
||||
url = new URL(req.query.url as string);
|
||||
}else{
|
||||
}catch{
|
||||
const scheme = req.secure ? "https" : "http";
|
||||
const host = `${scheme}://${req.get("Host")}`;
|
||||
url = new URL(host);
|
||||
|
@ -70,45 +62,37 @@ export async function inviteResponse(
|
|||
if(!instance){
|
||||
throw new Error("Instance not specified");
|
||||
}
|
||||
|
||||
const urls = await getApiUrls(instance);
|
||||
if(!urls){
|
||||
throw new Error("Failed to get API URLs");
|
||||
}
|
||||
|
||||
const invite = await fetch(`${urls.api}/invites/${code}`).then(
|
||||
res=>res.json() as Promise<Invite>
|
||||
);
|
||||
const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise<Invite>);
|
||||
const title = invite.guild.name;
|
||||
const description = invite.inviter
|
||||
? `${invite.inviter.username} has invited you to ${invite.guild.name}${
|
||||
invite.guild.description ? `\n${invite.guild.description}` : ""
|
||||
}`
|
||||
: `You've been invited to ${invite.guild.name}${
|
||||
invite.guild.description ? `\n${invite.guild.description}` : ""
|
||||
}`;
|
||||
? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`
|
||||
: `You've been invited to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`;
|
||||
const thumbnail = invite.guild.icon
|
||||
? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png`
|
||||
: "";
|
||||
|
||||
const jsonResponse = {
|
||||
res.json({
|
||||
type: "link",
|
||||
version: "1.0",
|
||||
title,
|
||||
thumbnail,
|
||||
description,
|
||||
};
|
||||
|
||||
res.json(jsonResponse);
|
||||
});
|
||||
}catch(error){
|
||||
console.error("Error processing invite response:", error);
|
||||
const jsonResponse = {
|
||||
res.json({
|
||||
type: "link",
|
||||
version: "1.0",
|
||||
title: "Jank Client",
|
||||
thumbnail: "/logo.webp",
|
||||
description: "A spacebar client that has DMs, replying and more",
|
||||
url: url.toString(),
|
||||
};
|
||||
res.json(jsonResponse);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,9 +17,8 @@ class Emoji{
|
|||
get guild(){
|
||||
if(this.owner instanceof Guild){
|
||||
return this.owner;
|
||||
}else{
|
||||
return undefined;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
get localuser(){
|
||||
if(this.owner instanceof Guild){
|
||||
|
@ -83,7 +82,7 @@ class Emoji{
|
|||
array[i] = read8();
|
||||
}
|
||||
//console.log(array);
|
||||
return new TextDecoder("utf-8").decode(array.buffer);
|
||||
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
|
||||
}
|
||||
const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
|
||||
[];
|
||||
|
|
|
@ -11,7 +11,7 @@ offset: number
|
|||
private readonly maxDist = 6000;
|
||||
HTMLElements: [HTMLElement, string][] = [];
|
||||
div: HTMLDivElement | null = null;
|
||||
timeout: NodeJS.Timeout | null = null;
|
||||
timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
beenloaded = false;
|
||||
scrollBottom = 0;
|
||||
scrollTop = 0;
|
||||
|
@ -239,7 +239,6 @@ offset: number
|
|||
try{
|
||||
if(!this.div){
|
||||
res(false);
|
||||
return false;
|
||||
}
|
||||
const out = (await Promise.allSettled([
|
||||
this.watchForTop(),
|
||||
|
@ -250,11 +249,9 @@ offset: number
|
|||
this.timeout = setTimeout(this.updatestuff.bind(this), 300);
|
||||
}
|
||||
res(Boolean(changed));
|
||||
return Boolean(changed);
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
res(false);
|
||||
return false;
|
||||
}finally{
|
||||
setTimeout(()=>{
|
||||
this.changePromise = undefined;
|
||||
|
@ -281,9 +278,13 @@ offset: number
|
|||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
await new Promise(resolve=>setTimeout(resolve, 1000));
|
||||
await new Promise(resolve=>{
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
element.classList.remove("jumped");
|
||||
await new Promise(resolve=>setTimeout(resolve, 100));
|
||||
await new Promise(resolve=>{
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
element.classList.add("jumped");
|
||||
}else{
|
||||
element.scrollIntoView();
|
||||
|
@ -296,7 +297,9 @@ offset: number
|
|||
await this.firstElement(id);
|
||||
this.updatestuff();
|
||||
await this.watchForChange();
|
||||
await new Promise(resolve=>setTimeout(resolve, 100));
|
||||
await new Promise(resolve=>{
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
await this.focus(id, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,7 +199,7 @@ function adduser(user: typeof Specialuser.prototype.json){
|
|||
return user;
|
||||
}
|
||||
const instancein = document.getElementById("instancein") as HTMLInputElement;
|
||||
let timeout: string | number | NodeJS.Timeout | undefined;
|
||||
let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null;
|
||||
// let instanceinfo;
|
||||
const stringURLMap = new Map<string, string>();
|
||||
|
||||
|
@ -231,8 +231,8 @@ async function getapiurls(str: string): Promise<
|
|||
if(stringURLMap.size!==0){
|
||||
res();
|
||||
}
|
||||
},100)
|
||||
})
|
||||
},100);
|
||||
});
|
||||
}
|
||||
if(val){
|
||||
str = val;
|
||||
|
@ -335,7 +335,7 @@ async function checkInstance(instance?: string){
|
|||
gateway: string;
|
||||
login: string;
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
if(instanceinfo){
|
||||
instanceinfo.value = instanceValue;
|
||||
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
|
||||
|
@ -360,10 +360,12 @@ async function checkInstance(instance?: string){
|
|||
|
||||
if(instancein){
|
||||
console.log(instancein);
|
||||
instancein.addEventListener("keydown", _=>{
|
||||
instancein.addEventListener("keydown", ()=>{
|
||||
const verify = document.getElementById("verify");
|
||||
verify!.textContent = "Waiting to check Instance";
|
||||
clearTimeout(timeout);
|
||||
if(timeout !== null && typeof timeout !== "string"){
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(()=>checkInstance(), 1000);
|
||||
});
|
||||
if(localStorage.getItem("instanceinfo")){
|
||||
|
@ -410,7 +412,9 @@ async function login(username: string, password: string, captcha: string){
|
|||
|
||||
if(response.captcha_sitekey){
|
||||
const capt = document.getElementById("h-captcha");
|
||||
if(!capt!.children.length){
|
||||
if(capt!.children.length){
|
||||
eval("hcaptcha.reset()");
|
||||
}else{
|
||||
const capty = document.createElement("div");
|
||||
capty.classList.add("h-captcha");
|
||||
|
||||
|
@ -419,8 +423,6 @@ async function login(username: string, password: string, captcha: string){
|
|||
script.src = "https://js.hcaptcha.com/1/api.js";
|
||||
capt!.append(script);
|
||||
capt!.append(capty);
|
||||
}else{
|
||||
eval("hcaptcha.reset()");
|
||||
}
|
||||
}else{
|
||||
console.log(response);
|
||||
|
@ -434,6 +436,7 @@ async function login(username: string, password: string, captcha: string){
|
|||
"",
|
||||
"",
|
||||
function(this: HTMLInputElement){
|
||||
// eslint-disable-next-line no-invalid-this
|
||||
onetimecode = this.value;
|
||||
},
|
||||
],
|
||||
|
@ -453,18 +456,18 @@ async function login(username: string, password: string, captcha: string){
|
|||
}),
|
||||
})
|
||||
.then(r=>r.json())
|
||||
.then(response=>{
|
||||
if(response.message){
|
||||
alert(response.message);
|
||||
.then(res=>{
|
||||
if(res.message){
|
||||
alert(res.message);
|
||||
}else{
|
||||
console.warn(response);
|
||||
if(!response.token)return;
|
||||
console.warn(res);
|
||||
if(!res.token)return;
|
||||
adduser({
|
||||
serverurls: JSON.parse(
|
||||
localStorage.getItem("instanceinfo")!
|
||||
),
|
||||
email: username,
|
||||
token: response.token,
|
||||
token: res.token,
|
||||
}).username = username;
|
||||
const redir = new URLSearchParams(
|
||||
window.location.search
|
||||
|
@ -579,7 +582,7 @@ export function getInstances(){
|
|||
}
|
||||
|
||||
fetch("/instances.json")
|
||||
.then(_=>_.json())
|
||||
.then(res=>res.json())
|
||||
.then(
|
||||
(json: {
|
||||
name: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue