Merge branch 'main', commit 'refs/pull/66/head' of https://github.com/MathMan05/JankClient

This commit is contained in:
MathMan05 2024-09-25 23:40:54 -05:00
commit f36b8b7c50
7 changed files with 126 additions and 126 deletions

View file

@ -20,11 +20,10 @@
"compression": "^1.7.4", "compression": "^1.7.4",
"eslint-plugin-html": "^8.1.1", "eslint-plugin-html": "^8.1.1",
"express": "^4.19.2", "express": "^4.19.2",
"gulp-sourcemaps": "^3.0.0",
"gulp-swc": "^2.2.0", "gulp-swc": "^2.2.0",
"node-fetch": "^3.3.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"ts-to-jsdoc": "^2.2.0", "ts-to-jsdoc": "^2.2.0"
"gulp-sourcemaps":"^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.10.0", "@eslint/js": "^9.10.0",

View file

@ -2,19 +2,20 @@
import compression from"compression"; import compression from"compression";
import express, { Request, Response }from"express"; import express, { Request, Response }from"express";
import fs from"node:fs"; import fs from"node:fs/promises";
import fetch from"node-fetch";
import path from"node:path"; import path from"node:path";
import{ observe, uptime }from"./stats.js"; import{ observe, uptime }from"./stats.js";
import{ getApiUrls, inviteResponse }from"./utils.js"; import{ getApiUrls, inviteResponse }from"./utils.js";
import{ fileURLToPath }from"node:url"; 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 __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
interface Instance { interface Instance {
name: string; name: string;
[key: string]: any; [key: string]: any;
} }
const app = express(); const app = express();
@ -55,9 +56,9 @@ async function updateInstances(): Promise<void>{
updateInstances(); updateInstances();
app.use("/getupdates", (_req: Request, res: Response)=>{ app.use("/getupdates", async (_req: Request, res: Response)=>{
try{ try{
const stats = fs.statSync(path.join(__dirname, "webpage")); const stats = await fs.stat(path.join(__dirname, "webpage"));
res.send(stats.mtimeMs.toString()); res.send(stats.mtimeMs.toString());
}catch(error){ }catch(error){
console.error("Error getting updates:", 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)=>{ 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); res.send(instanceUptime);
}); });
@ -106,33 +107,40 @@ app.use("/", async (req: Request, res: Response)=>{
return; return;
} }
const filePath = path.join(__dirname, "webpage", req.path); const filePath = path.join(__dirname, "webpage", req.path);
if(fs.existsSync(filePath)){ try{
await fs.access(filePath);
if(devmode){ if(devmode){
const filePath2 = path.join(__dirname, "../src/webpage", req.path); const filePath2 = path.join(__dirname, "../src/webpage", req.path);
if(fs.existsSync(filePath2)){ try{
await fs.access(filePath2);
res.sendFile(filePath2); res.sendFile(filePath2);
return; return;
} }catch{}
} }
res.sendFile(filePath); res.sendFile(filePath);
}else if(fs.existsSync(`${filePath}.html`)){ }catch{
if(devmode){ try{
const filePath2 = path.join(__dirname, "../src/webpage", req.path); await fs.access(`${filePath}.html`);
if(fs.existsSync(filePath2+".html")){ if(devmode){
res.sendFile(filePath2+".html"); const filePath2 = path.join(__dirname, "../src/webpage", req.path);
return; try{
await fs.access(filePath2 + ".html");
res.sendFile(filePath2 + ".html");
return;
}catch{}
} }
} res.sendFile(`${filePath}.html`);
res.sendFile(`${filePath}.html`); }catch{
}else{ if(req.path.startsWith("/src/webpage")){
if(req.path.startsWith("/src/webpage")){ const filePath2 = path.join(__dirname, "..", req.path);
const filePath2 = path.join(__dirname, "..", req.path); try{
if(fs.existsSync(`${filePath2}`)){ await fs.access(filePath2);
res.sendFile(filePath2); res.sendFile(filePath2);
return; 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}`); console.log(`Server running on port ${PORT}`);
}); });
export{ getApiUrls }; export{ getApiUrls };

View file

@ -1,8 +1,8 @@
import fs from"node:fs"; import fs from"node:fs";
import path from"node:path"; import path from"node:path";
import fetch from"node-fetch";
import{ getApiUrls }from"./utils.js"; import{ getApiUrls }from"./utils.js";
import{ fileURLToPath }from"node:url"; import{ fileURLToPath }from"node:url";
import{ setTimeout, clearTimeout }from"node:timers";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@ -12,10 +12,6 @@ interface UptimeEntry {
online: boolean; online: boolean;
} }
interface UptimeObject {
[key: string]: UptimeEntry[];
}
interface Instance { interface Instance {
name: string; name: string;
urls?: { api: string }; urls?: { api: string };
@ -28,37 +24,46 @@ interface Instance {
}; };
} }
const uptimeObject: UptimeObject = loadUptimeObject(); const uptimeObject: Map<string, UptimeEntry[]> = loadUptimeObject();
export{ uptimeObject as uptime }; export{ uptimeObject as uptime };
function loadUptimeObject(): UptimeObject{ function loadUptimeObject(): Map<string, UptimeEntry[]>{
const filePath = path.join(__dirname, "..", "uptime.json"); const filePath = path.join(__dirname, "..", "uptime.json");
if(fs.existsSync(filePath)){ if(fs.existsSync(filePath)){
try{ try{
return JSON.parse(fs.readFileSync(filePath, "utf8")); const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
return new Map(Object.entries(data));
}catch(error){ }catch(error){
console.error("Error reading uptime.json:", 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{ function saveUptimeObject(): void{
fs.writeFile( if(saveTimeout){
path.join(__dirname, "..", "uptime.json"), clearTimeout(saveTimeout);
JSON.stringify(uptimeObject), }
error=>{ saveTimeout = setTimeout(()=>{
if(error){ const data = Object.fromEntries(uptimeObject);
console.error("Error saving uptime.json:", error); 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{ function removeUndefinedKey(): void{
if(uptimeObject.undefined){ if(uptimeObject.has("undefined")){
delete uptimeObject.undefined; uptimeObject.delete("undefined");
saveUptimeObject(); saveUptimeObject();
} }
} }
@ -85,7 +90,7 @@ async function resolveInstance(
return; return;
} }
activeInstances.add(instance.name); activeInstances.add(instance.name);
await checkHealth(instance, api); // Ensure health is checked immediately await checkHealth(instance, api);
scheduleHealthCheck(instance, api); scheduleHealthCheck(instance, api);
}catch(error){ }catch(error){
console.error("Error resolving instance:", error); console.error("Error resolving instance:", error);
@ -127,7 +132,6 @@ async function checkHealth(
const response = await fetch(`${api}/ping`, { method: "HEAD" }); const response = await fetch(`${api}/ping`, { method: "HEAD" });
console.log(`Checking health for ${instance.name}: ${response.status}`); console.log(`Checking health for ${instance.name}: ${response.status}`);
if(response.ok || tries > 3){ if(response.ok || tries > 3){
console.log(`Setting status for ${instance.name} to ${response.ok}`);
setStatus(instance, response.ok); setStatus(instance, response.ok);
}else{ }else{
retryHealthCheck(instance, api, tries); retryHealthCheck(instance, api, tries);
@ -151,7 +155,7 @@ function retryHealthCheck(
} }
function updateInactiveInstances(activeInstances: Set<string>): void{ function updateInactiveInstances(activeInstances: Set<string>): void{
for(const key of Object.keys(uptimeObject)){ for(const key of uptimeObject.keys()){
if(!activeInstances.has(key)){ if(!activeInstances.has(key)){
setStatus(key, false); setStatus(key, false);
} }
@ -159,7 +163,7 @@ function updateInactiveInstances(activeInstances: Set<string>): void{
} }
function calcStats(instance: Instance): void{ function calcStats(instance: Instance): void{
const obj = uptimeObject[instance.name]; const obj = uptimeObject.get(instance.name);
if(!obj)return; if(!obj)return;
const now = Date.now(); const now = Date.now();
@ -235,11 +239,11 @@ function calculateUptimeStats(
function setStatus(instance: string | Instance, status: boolean): void{ function setStatus(instance: string | Instance, status: boolean): void{
const name = typeof instance === "string" ? instance : instance.name; const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject[name]; let obj = uptimeObject.get(name);
if(!obj){ if(!obj){
obj = []; obj = [];
uptimeObject[name] = obj; uptimeObject.set(name, obj);
} }
const lastEntry = obj.at(-1); const lastEntry = obj.at(-1);
@ -251,4 +255,4 @@ function setStatus(instance: string | Instance, status: boolean): void{
calcStats(instance); calcStats(instance);
} }
} }
} }

View file

@ -1,23 +1,22 @@
import fetch from"node-fetch";
import{ Request, Response }from"express"; import{ Request, Response }from"express";
interface ApiUrls { interface ApiUrls {
api: string; api: string;
gateway: string; gateway: string;
cdn: string; cdn: string;
wellknown: string; wellknown: string;
} }
interface Invite { interface Invite {
guild: { guild: {
name: string; name: string;
description?: string; description?: string;
icon?: string; icon?: string;
id: string; id: string;
}; };
inviter?: { inviter?: {
username: string; username: string;
}; };
} }
export async function getApiUrls(url: string): Promise<ApiUrls | null>{ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
@ -25,15 +24,11 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
url += "/"; url += "/";
} }
try{ try{
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json());
res=>res.json() as Promise<ApiUrls>
);
const api = info.api; const api = info.api;
const apiUrl = new URL(api); const apiUrl = new URL(api);
const policies: any = await fetch( const policies: any = await fetch(
`${api}${ `${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`
apiUrl.pathname.includes("api") ? "" : "api"
}/policies/instance/domains`
).then(res=>res.json()); ).then(res=>res.json());
return{ return{
api: policies.apiEndpoint, api: policies.apiEndpoint,
@ -47,14 +42,11 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
} }
} }
export async function inviteResponse( export async function inviteResponse(req: Request, res: Response): Promise<void>{
req: Request,
res: Response
): Promise<void>{
let url: URL; let url: URL;
if(URL.canParse(req.query.url as string)){ try{
url = new URL(req.query.url as string); url = new URL(req.query.url as string);
}else{ }catch{
const scheme = req.secure ? "https" : "http"; const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`; const host = `${scheme}://${req.get("Host")}`;
url = new URL(host); url = new URL(host);
@ -70,45 +62,37 @@ export async function inviteResponse(
if(!instance){ if(!instance){
throw new Error("Instance not specified"); throw new Error("Instance not specified");
} }
const urls = await getApiUrls(instance); const urls = await getApiUrls(instance);
if(!urls){ if(!urls){
throw new Error("Failed to get API URLs"); throw new Error("Failed to get API URLs");
} }
const invite = await fetch(`${urls.api}/invites/${code}`).then( const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise<Invite>);
res=>res.json() as Promise<Invite>
);
const title = invite.guild.name; const title = invite.guild.name;
const description = invite.inviter const description = invite.inviter
? `${invite.inviter.username} has invited you to ${invite.guild.name}${ ? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`
invite.guild.description ? `\n${invite.guild.description}` : "" : `You've been invited 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 const thumbnail = invite.guild.icon
? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png` ? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png`
: ""; : "";
const jsonResponse = { res.json({
type: "link", type: "link",
version: "1.0", version: "1.0",
title, title,
thumbnail, thumbnail,
description, description,
}; });
res.json(jsonResponse);
}catch(error){ }catch(error){
console.error("Error processing invite response:", error); console.error("Error processing invite response:", error);
const jsonResponse = { res.json({
type: "link", type: "link",
version: "1.0", version: "1.0",
title: "Jank Client", title: "Jank Client",
thumbnail: "/logo.webp", thumbnail: "/logo.webp",
description: "A spacebar client that has DMs, replying and more", description: "A spacebar client that has DMs, replying and more",
url: url.toString(), url: url.toString(),
}; });
res.json(jsonResponse);
} }
} }

View file

@ -17,9 +17,8 @@ class Emoji{
get guild(){ get guild(){
if(this.owner instanceof Guild){ if(this.owner instanceof Guild){
return this.owner; return this.owner;
}else{
return undefined;
} }
return null;
} }
get localuser(){ get localuser(){
if(this.owner instanceof Guild){ if(this.owner instanceof Guild){
@ -83,7 +82,7 @@ class Emoji{
array[i] = read8(); array[i] = read8();
} }
//console.log(array); //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 }[] }[] = const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
[]; [];

View file

@ -11,7 +11,7 @@ offset: number
private readonly maxDist = 6000; private readonly maxDist = 6000;
HTMLElements: [HTMLElement, string][] = []; HTMLElements: [HTMLElement, string][] = [];
div: HTMLDivElement | null = null; div: HTMLDivElement | null = null;
timeout: NodeJS.Timeout | null = null; timeout: ReturnType<typeof setTimeout> | null = null;
beenloaded = false; beenloaded = false;
scrollBottom = 0; scrollBottom = 0;
scrollTop = 0; scrollTop = 0;
@ -239,7 +239,6 @@ offset: number
try{ try{
if(!this.div){ if(!this.div){
res(false); res(false);
return false;
} }
const out = (await Promise.allSettled([ const out = (await Promise.allSettled([
this.watchForTop(), this.watchForTop(),
@ -250,11 +249,9 @@ offset: number
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
res(Boolean(changed)); res(Boolean(changed));
return Boolean(changed);
}catch(e){ }catch(e){
console.error(e); console.error(e);
res(false); res(false);
return false;
}finally{ }finally{
setTimeout(()=>{ setTimeout(()=>{
this.changePromise = undefined; this.changePromise = undefined;
@ -281,9 +278,13 @@ offset: number
behavior: "smooth", behavior: "smooth",
block: "center", block: "center",
}); });
await new Promise(resolve=>setTimeout(resolve, 1000)); await new Promise(resolve=>{
setTimeout(resolve, 1000);
});
element.classList.remove("jumped"); element.classList.remove("jumped");
await new Promise(resolve=>setTimeout(resolve, 100)); await new Promise(resolve=>{
setTimeout(resolve, 100);
});
element.classList.add("jumped"); element.classList.add("jumped");
}else{ }else{
element.scrollIntoView(); element.scrollIntoView();
@ -296,7 +297,9 @@ offset: number
await this.firstElement(id); await this.firstElement(id);
this.updatestuff(); this.updatestuff();
await this.watchForChange(); await this.watchForChange();
await new Promise(resolve=>setTimeout(resolve, 100)); await new Promise(resolve=>{
setTimeout(resolve, 100);
});
await this.focus(id, true); await this.focus(id, true);
} }
} }

View file

@ -199,7 +199,7 @@ function adduser(user: typeof Specialuser.prototype.json){
return user; return user;
} }
const instancein = document.getElementById("instancein") as HTMLInputElement; 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; // let instanceinfo;
const stringURLMap = new Map<string, string>(); const stringURLMap = new Map<string, string>();
@ -231,8 +231,8 @@ async function getapiurls(str: string): Promise<
if(stringURLMap.size!==0){ if(stringURLMap.size!==0){
res(); res();
} }
},100) },100);
}) });
} }
if(val){ if(val){
str = val; str = val;
@ -335,7 +335,7 @@ async function checkInstance(instance?: string){
gateway: string; gateway: string;
login: string; login: string;
value: string; value: string;
} };
if(instanceinfo){ if(instanceinfo){
instanceinfo.value = instanceValue; instanceinfo.value = instanceValue;
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
@ -360,10 +360,12 @@ async function checkInstance(instance?: string){
if(instancein){ if(instancein){
console.log(instancein); console.log(instancein);
instancein.addEventListener("keydown", _=>{ instancein.addEventListener("keydown", ()=>{
const verify = document.getElementById("verify"); const verify = document.getElementById("verify");
verify!.textContent = "Waiting to check Instance"; verify!.textContent = "Waiting to check Instance";
clearTimeout(timeout); if(timeout !== null && typeof timeout !== "string"){
clearTimeout(timeout);
}
timeout = setTimeout(()=>checkInstance(), 1000); timeout = setTimeout(()=>checkInstance(), 1000);
}); });
if(localStorage.getItem("instanceinfo")){ if(localStorage.getItem("instanceinfo")){
@ -410,7 +412,9 @@ async function login(username: string, password: string, captcha: string){
if(response.captcha_sitekey){ if(response.captcha_sitekey){
const capt = document.getElementById("h-captcha"); const capt = document.getElementById("h-captcha");
if(!capt!.children.length){ if(capt!.children.length){
eval("hcaptcha.reset()");
}else{
const capty = document.createElement("div"); const capty = document.createElement("div");
capty.classList.add("h-captcha"); 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"; script.src = "https://js.hcaptcha.com/1/api.js";
capt!.append(script); capt!.append(script);
capt!.append(capty); capt!.append(capty);
}else{
eval("hcaptcha.reset()");
} }
}else{ }else{
console.log(response); console.log(response);
@ -434,6 +436,7 @@ async function login(username: string, password: string, captcha: string){
"", "",
"", "",
function(this: HTMLInputElement){ function(this: HTMLInputElement){
// eslint-disable-next-line no-invalid-this
onetimecode = this.value; onetimecode = this.value;
}, },
], ],
@ -453,18 +456,18 @@ async function login(username: string, password: string, captcha: string){
}), }),
}) })
.then(r=>r.json()) .then(r=>r.json())
.then(response=>{ .then(res=>{
if(response.message){ if(res.message){
alert(response.message); alert(res.message);
}else{ }else{
console.warn(response); console.warn(res);
if(!response.token)return; if(!res.token)return;
adduser({ adduser({
serverurls: JSON.parse( serverurls: JSON.parse(
localStorage.getItem("instanceinfo")! localStorage.getItem("instanceinfo")!
), ),
email: username, email: username,
token: response.token, token: res.token,
}).username = username; }).username = username;
const redir = new URLSearchParams( const redir = new URLSearchParams(
window.location.search window.location.search
@ -579,7 +582,7 @@ export function getInstances(){
} }
fetch("/instances.json") fetch("/instances.json")
.then(_=>_.json()) .then(res=>res.json())
.then( .then(
(json: { (json: {
name: string; name: string;