diff --git a/src/stats.ts b/src/stats.ts index dde2b2c..fd2ab5e 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -8,244 +8,248 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); interface UptimeEntry { -time: number; -online: boolean; + time: number; + online: boolean; } interface UptimeObject { -[key: string]: UptimeEntry[]; + [key: string]: UptimeEntry[]; } interface Instance { -name: string; -urls?: { api: string }; -url?: string; -online?: boolean; -uptime?: { -daytime: number; -weektime: number; -alltime: number; -}; + name: string; + urls?: { api: string }; + url?: string; + online?: boolean; + uptime?: { + daytime: number; + weektime: number; + alltime: number; + }; } let uptimeObject: UptimeObject = loadUptimeObject(); export { uptimeObject as uptime }; function loadUptimeObject(): UptimeObject { -const filePath = path.join(__dirname, "..", "uptime.json"); -if (fs.existsSync(filePath)) { -try { -return JSON.parse(fs.readFileSync(filePath, "utf8")); -} catch (error) { -console.error("Error reading uptime.json:", error); -return {}; -} -} -return {}; + const filePath = path.join(__dirname, "..", "uptime.json"); + if (fs.existsSync(filePath)) { + try { + return JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch (error) { + console.error("Error reading uptime.json:", error); + return {}; + } + } + return {}; } function saveUptimeObject(): void { -fs.writeFile( -`${__dirname}/uptime.json`, -JSON.stringify(uptimeObject), -(error) => { -if (error) { -console.error("Error saving uptime.json:", error); -} -} -); + fs.writeFile( + path.join(__dirname, "..", "uptime.json"), + JSON.stringify(uptimeObject), + (error) => { + if (error) { + console.error("Error saving uptime.json:", error); + } + } + ); } function removeUndefinedKey(): void { -if (uptimeObject.undefined) { -delete uptimeObject.undefined; -saveUptimeObject(); -} + if (uptimeObject.undefined) { + delete uptimeObject.undefined; + saveUptimeObject(); + } } removeUndefinedKey(); export async function observe(instances: Instance[]): Promise { - const activeInstances = new Set(); - const instancePromises = instances.map((instance) => - resolveInstance(instance, activeInstances) - ); - await Promise.allSettled(instancePromises); - updateInactiveInstances(activeInstances); - } + const activeInstances = new Set(); + const instancePromises = instances.map((instance) => + resolveInstance(instance, activeInstances) + ); + await Promise.allSettled(instancePromises); + updateInactiveInstances(activeInstances); +} - async function resolveInstance( - instance: Instance, - activeInstances: Set - ): Promise { - try { - calcStats(instance); - const api = await getApiUrl(instance); - if (!api) { - handleUnresolvedApi(instance); - return; - } - activeInstances.add(instance.name); - scheduleHealthCheck(instance, api); - } catch (error) { - console.error("Error resolving instance:", error); - } - } +async function resolveInstance( + instance: Instance, + activeInstances: Set +): Promise { + try { + calcStats(instance); + const api = await getApiUrl(instance); + if (!api) { + handleUnresolvedApi(instance); + return; + } + activeInstances.add(instance.name); + await checkHealth(instance, api); // Ensure health is checked immediately + scheduleHealthCheck(instance, api); + } catch (error) { + console.error("Error resolving instance:", error); + } +} - async function getApiUrl(instance: Instance): Promise { - if (instance.urls) { - return instance.urls.api; - } - if (instance.url) { - const urls = await getApiUrls(instance.url); - return urls ? urls.api : null; - } - return null; - } +async function getApiUrl(instance: Instance): Promise { + if (instance.urls) { + return instance.urls.api; + } + if (instance.url) { + const urls = await getApiUrls(instance.url); + return urls ? urls.api : null; + } + return null; +} - function handleUnresolvedApi(instance: Instance): void { - setStatus(instance, false); - console.warn(`${instance.name} does not resolve api URL`, instance); - setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30); - } +function handleUnresolvedApi(instance: Instance): void { + setStatus(instance, false); + console.warn(`${instance.name} does not resolve api URL`, instance); + setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30); +} - function scheduleHealthCheck(instance: Instance, api: string): void { - const checkInterval = 1000 * 60 * 30; - const initialDelay = Math.random() * 1000 * 60 * 10; - setTimeout(() => { - checkHealth(instance, api); - setInterval(() => checkHealth(instance, api), checkInterval); - }, initialDelay); - } +function scheduleHealthCheck(instance: Instance, api: string): void { + const checkInterval = 1000 * 60 * 30; + const initialDelay = Math.random() * 1000 * 60 * 10; + setTimeout(() => { + checkHealth(instance, api); + setInterval(() => checkHealth(instance, api), checkInterval); + }, initialDelay); +} - async function checkHealth( - instance: Instance, - api: string, - tries = 0 - ): Promise { - try { - const response = await fetch(`${api}ping`, { method: "HEAD" }); - if (response.ok || tries > 3) { - setStatus(instance, response.ok); - } else { - retryHealthCheck(instance, api, tries); - } - } catch (error) { - console.error("Error checking health:", error); - if (tries > 3) { - setStatus(instance, false); - } else { - retryHealthCheck(instance, api, tries); - } - } - } +async function checkHealth( + instance: Instance, + api: string, + tries = 0 +): Promise { + try { + 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); + } + } catch (error) { + console.error(`Error checking health for ${instance.name}:`, error); + if (tries > 3) { + setStatus(instance, false); + } else { + retryHealthCheck(instance, api, tries); + } + } +} - function retryHealthCheck( - instance: Instance, - api: string, - tries: number - ): void { - setTimeout(() => checkHealth(instance, api, tries + 1), 30000); - } +function retryHealthCheck( + instance: Instance, + api: string, + tries: number +): void { + setTimeout(() => checkHealth(instance, api, tries + 1), 30000); +} - function updateInactiveInstances(activeInstances: Set): void { - for (const key of Object.keys(uptimeObject)) { - if (!activeInstances.has(key)) { - setStatus(key, false); - } - } - } +function updateInactiveInstances(activeInstances: Set): void { + for (const key of Object.keys(uptimeObject)) { + if (!activeInstances.has(key)) { + setStatus(key, false); + } + } +} - function calcStats(instance: Instance): void { - const obj = uptimeObject[instance.name]; - if (!obj) return; +function calcStats(instance: Instance): void { + const obj = uptimeObject[instance.name]; + if (!obj) return; - const now = Date.now(); - const day = now - 1000 * 60 * 60 * 24; - const week = now - 1000 * 60 * 60 * 24 * 7; + const now = Date.now(); + const day = now - 1000 * 60 * 60 * 24; + const week = now - 1000 * 60 * 60 * 24 * 7; - let totalTimePassed = 0; - let alltime = 0; - let daytime = 0; - let weektime = 0; - let online = false; + let totalTimePassed = 0; + let alltime = 0; + let daytime = 0; + let weektime = 0; + let online = false; - for (let i = 0; i < obj.length; i++) { - const entry = obj[i]; - online = entry.online; - const stamp = entry.time; - const nextStamp = obj[i + 1]?.time || now; - const timePassed = nextStamp - stamp; + for (let i = 0; i < obj.length; i++) { + const entry = obj[i]; + online = entry.online; + const stamp = entry.time; + const nextStamp = obj[i + 1]?.time || now; + const timePassed = nextStamp - stamp; - totalTimePassed += timePassed; - alltime += Number(online) * timePassed; + totalTimePassed += timePassed; + alltime += Number(online) * timePassed; - if (stamp + timePassed > week) { - const weekTimePassed = Math.min(timePassed, nextStamp - week); - weektime += Number(online) * weekTimePassed; + if (stamp + timePassed > week) { + const weekTimePassed = Math.min(timePassed, nextStamp - week); + weektime += Number(online) * weekTimePassed; - if (stamp + timePassed > day) { - const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); - daytime += Number(online) * dayTimePassed; - } - } - } + if (stamp + timePassed > day) { + const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); + daytime += Number(online) * dayTimePassed; + } + } + } - instance.online = online; - instance.uptime = calculateUptimeStats( - totalTimePassed, - alltime, - daytime, - weektime, - online - ); - } + instance.online = online; + instance.uptime = calculateUptimeStats( + totalTimePassed, + alltime, + daytime, + weektime, + online + ); +} - function calculateUptimeStats( - totalTimePassed: number, - alltime: number, - daytime: number, - weektime: number, - online: boolean - ): { daytime: number; weektime: number; alltime: number } { - const dayInMs = 1000 * 60 * 60 * 24; - const weekInMs = dayInMs * 7; +function calculateUptimeStats( + totalTimePassed: number, + alltime: number, + daytime: number, + weektime: number, + online: boolean +): { daytime: number; weektime: number; alltime: number } { + const dayInMs = 1000 * 60 * 60 * 24; + const weekInMs = dayInMs * 7; - alltime /= totalTimePassed; + alltime /= totalTimePassed; - if (totalTimePassed > dayInMs) { - daytime = daytime || (online ? dayInMs : 0); - daytime /= dayInMs; + if (totalTimePassed > dayInMs) { + daytime = daytime || (online ? dayInMs : 0); + daytime /= dayInMs; - if (totalTimePassed > weekInMs) { - weektime = weektime || (online ? weekInMs : 0); - weektime /= weekInMs; - } else { - weektime = alltime; - } - } else { - weektime = alltime; - daytime = alltime; - } + if (totalTimePassed > weekInMs) { + weektime = weektime || (online ? weekInMs : 0); + weektime /= weekInMs; + } else { + weektime = alltime; + } + } else { + weektime = alltime; + daytime = alltime; + } - return { daytime, weektime, alltime }; - } + return { daytime, weektime, alltime }; +} - function setStatus(instance: string | Instance, status: boolean): void { - const name = typeof instance === "string" ? instance : instance.name; - let obj = uptimeObject[name]; +function setStatus(instance: string | Instance, status: boolean): void { + const name = typeof instance === "string" ? instance : instance.name; + let obj = uptimeObject[name]; - if (!obj) { - obj = []; - uptimeObject[name] = obj; - } + if (!obj) { + obj = []; + uptimeObject[name] = obj; + } - if (obj.at(-1)?.online !== status) { - obj.push({ time: Date.now(), online: status }); - saveUptimeObject(); - } + const lastEntry = obj.at(-1); + if (!lastEntry || lastEntry.online !== status) { + obj.push({ time: Date.now(), online: status }); + saveUptimeObject(); - if (typeof instance !== "string") { - calcStats(instance); - } - } + if (typeof instance !== "string") { + calcStats(instance); + } + } +} diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index c5d6ce8..2e61a93 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -3,257 +3,257 @@ import { Guild } from "./guild.js"; import { Localuser } from "./localuser.js"; class Emoji { -static emojis: { -name: string; -emojis: { -name: string; -emoji: string; -}[]; -}[]; -name: string; -id: string; -animated: boolean; -owner: Guild | Localuser; -get guild() { -if (this.owner instanceof Guild) { -return this.owner; -} -return; -} -get localuser() { -if (this.owner instanceof Guild) { -return this.owner.localuser; -} else { -return this.owner; -} -} -get info() { -return this.owner.info; -} -constructor( -json: { name: string; id: string; animated: boolean }, -owner: Guild | Localuser -) { -this.name = json.name; -this.id = json.id; -this.animated = json.animated; -this.owner = owner; -} -getHTML(bigemoji: boolean = false) { -const emojiElem = document.createElement("img"); -emojiElem.classList.add("md-emoji"); -emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); -emojiElem.crossOrigin = "anonymous"; -emojiElem.src = -this.info.cdn + -"/emojis/" + -this.id + -"." + -(this.animated ? "gif" : "png") + -"?size=32"; + static emojis: { + name: string; + emojis: { + name: string; + emoji: string; + }[]; + }[]; + name: string; + id: string; + animated: boolean; + owner: Guild | Localuser; + get guild() { + if (this.owner instanceof Guild) { + return this.owner; + } + return; + } + get localuser() { + if (this.owner instanceof Guild) { + return this.owner.localuser; + } else { + return this.owner; + } + } + get info() { + return this.owner.info; + } + constructor( + json: { name: string; id: string; animated: boolean }, + owner: Guild | Localuser + ) { + this.name = json.name; + this.id = json.id; + this.animated = json.animated; + this.owner = owner; + } + getHTML(bigemoji: boolean = false) { + const emojiElem = document.createElement("img"); + emojiElem.classList.add("md-emoji"); + emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); + emojiElem.crossOrigin = "anonymous"; + emojiElem.src = + this.info.cdn + + "/emojis/" + + this.id + + "." + + (this.animated ? "gif" : "png") + + "?size=32"; -emojiElem.alt = this.name; -emojiElem.loading = "lazy"; -return emojiElem; + emojiElem.alt = this.name; + emojiElem.loading = "lazy"; + return emojiElem; + } + static decodeEmojiList(buffer: ArrayBuffer) { + const view = new DataView(buffer, 0); + let i = 0; + function read16() { + const int = view.getUint16(i); + i += 2; + return int; + } + function read8() { + const int = view.getUint8(i); + i += 1; + return int; + } + function readString8() { + return readStringNo(read8()); + } + function readString16() { + return readStringNo(read16()); + } + function readStringNo(length: number) { + const array = new Uint8Array(length); + + for (let i = 0; i < length; i++) { + array[i] = read8(); + } + //console.log(array); + return new TextDecoder("utf-8").decode(array.buffer); + } + const build: { name: string; emojis: { name: string; emoji: string }[] }[] = + []; + let cats = read16(); + + for (; cats !== 0; cats--) { + const name = readString16(); + const emojis: { + name: string; + skin_tone_support: boolean; + emoji: string; + }[] = []; + let emojinumber = read16(); + for (; emojinumber !== 0; emojinumber--) { + //console.log(emojis); + const name = readString8(); + const len = read8(); + const skin_tone_support = len > 127; + const emoji = readStringNo(len - Number(skin_tone_support) * 128); + emojis.push({ + name, + skin_tone_support, + emoji, + }); + } + build.push({ + name, + emojis, + }); + } + this.emojis = build; + console.log(build); + } + static grabEmoji() { + fetch("/emoji.bin") + .then((e) => { + return e.arrayBuffer(); + }) + .then((e) => { + Emoji.decodeEmojiList(e); + }); + } + static async emojiPicker( + x: number, + y: number, + localuser: Localuser + ): Promise { + let res: (r: Emoji | string) => void; + const promise: Promise = new Promise((r) => { + res = r; + }); + const menu = document.createElement("div"); + menu.classList.add("flexttb", "emojiPicker"); + menu.style.top = y + "px"; + menu.style.left = x + "px"; + + const title = document.createElement("h2"); + title.textContent = Emoji.emojis[0].name; + title.classList.add("emojiTitle"); + menu.append(title); + const selection = document.createElement("div"); + selection.classList.add("flexltr", "dontshrink", "emojirow"); + const body = document.createElement("div"); + body.classList.add("emojiBody"); + + let isFirst = true; + localuser.guilds + .filter((guild) => guild.id != "@me" && guild.emojis.length > 0) + .forEach((guild) => { + const select = document.createElement("div"); + select.classList.add("emojiSelect"); + + if (guild.properties.icon) { + const img = document.createElement("img"); + img.classList.add("pfp", "servericon", "emoji-server"); + img.crossOrigin = "anonymous"; + img.src = + localuser.info.cdn + + "/icons/" + + guild.properties.id + + "/" + + guild.properties.icon + + ".png?size=48"; + img.alt = "Server: " + guild.properties.name; + select.appendChild(img); + } else { + const div = document.createElement("span"); + div.textContent = guild.properties.name + .replace(/'s /g, " ") + .replace(/\w+/g, (word) => word[0]) + .replace(/\s/g, ""); + select.append(div); + } + + selection.append(select); + + const clickEvent = () => { + title.textContent = guild.properties.name; + body.innerHTML = ""; + for (const emojit of guild.emojis) { + const emojiElem = document.createElement("div"); + emojiElem.classList.add("emojiSelect"); + + const emojiClass = new Emoji( + { + id: emojit.id as string, + name: emojit.name, + animated: emojit.animated as boolean, + }, + localuser + ); + emojiElem.append(emojiClass.getHTML()); + body.append(emojiElem); + + emojiElem.addEventListener("click", () => { + res(emojiClass); + if (Contextmenu.currentmenu !== "") { + Contextmenu.currentmenu.remove(); + } + }); + } + }; + + select.addEventListener("click", clickEvent); + if (isFirst) { + clickEvent(); + isFirst = false; + } + }); + + setTimeout(() => { + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } + document.body.append(menu); + Contextmenu.currentmenu = menu; + Contextmenu.keepOnScreen(menu); + }, 10); + + let i = 0; + for (const thing of Emoji.emojis) { + const select = document.createElement("div"); + select.textContent = thing.emojis[0].emoji; + select.classList.add("emojiSelect"); + selection.append(select); + const clickEvent = () => { + title.textContent = thing.name; + body.innerHTML = ""; + for (const emojit of thing.emojis) { + const emoji = document.createElement("div"); + emoji.classList.add("emojiSelect"); + emoji.textContent = emojit.emoji; + body.append(emoji); + emoji.onclick = (_) => { + res(emojit.emoji); + if (Contextmenu.currentmenu !== "") { + Contextmenu.currentmenu.remove(); + } + }; + } + }; + select.onclick = clickEvent; + if (i === 0) { + clickEvent(); + } + i++; + } + menu.append(selection); + menu.append(body); + return promise; + } } -static decodeEmojiList(buffer: ArrayBuffer) { -const view = new DataView(buffer, 0); -let i = 0; -function read16() { -const int = view.getUint16(i); -i += 2; -return int; -} -function read8() { -const int = view.getUint8(i); -i += 1; -return int; -} -function readString8() { -return readStringNo(read8()); -} -function readString16() { -return readStringNo(read16()); -} -function readStringNo(length: number) { -const array = new Uint8Array(length); - -for (let i = 0; i < length; i++) { -array[i] = read8(); -} -//console.log(array); -return new TextDecoder("utf-8").decode(array.buffer); -} -const build: { name: string; emojis: { name: string; emoji: string }[] }[] = -[]; -let cats = read16(); - -for (; cats !== 0; cats--) { -const name = readString16(); -const emojis: { -name: string; -skin_tone_support: boolean; -emoji: string; -}[] = []; -let emojinumber = read16(); -for (; emojinumber !== 0; emojinumber--) { -//console.log(emojis); -const name = readString8(); -const len = read8(); -const skin_tone_support = len > 127; -const emoji = readStringNo(len - Number(skin_tone_support) * 128); -emojis.push({ -name, -skin_tone_support, -emoji, -}); -} -build.push({ -name, -emojis, -}); -} -this.emojis = build; -console.log(build); -} -static grabEmoji() { -fetch("/emoji.bin") -.then((e) => { -return e.arrayBuffer(); -}) -.then((e) => { -Emoji.decodeEmojiList(e); -}); -} -static async emojiPicker( -x: number, -y: number, -localuser: Localuser -): Promise { - let res: (r: Emoji | string) => void; - const promise: Promise = new Promise((r) => { - res = r; - }); - const menu = document.createElement("div"); - menu.classList.add("flexttb", "emojiPicker"); - menu.style.top = y + "px"; - menu.style.left = x + "px"; - - const title = document.createElement("h2"); - title.textContent = Emoji.emojis[0].name; - title.classList.add("emojiTitle"); - menu.append(title); - const selection = document.createElement("div"); - selection.classList.add("flexltr", "dontshrink", "emojirow"); - const body = document.createElement("div"); - body.classList.add("emojiBody"); - - let isFirst = true; - localuser.guilds - .filter((guild) => guild.id != "@me" && guild.emojis.length > 0) - .forEach((guild) => { - const select = document.createElement("div"); - select.classList.add("emojiSelect"); - - if (guild.properties.icon) { - const img = document.createElement("img"); - img.classList.add("pfp", "servericon", "emoji-server"); - img.crossOrigin = "anonymous"; - img.src = - localuser.info.cdn + - "/icons/" + - guild.properties.id + - "/" + - guild.properties.icon + - ".png?size=48"; - img.alt = "Server: " + guild.properties.name; - select.appendChild(img); - } else { - const div = document.createElement("span"); - div.textContent = guild.properties.name - .replace(/'s /g, " ") - .replace(/\w+/g, (word) => word[0]) - .replace(/\s/g, ""); - select.append(div); - } - - selection.append(select); - - const clickEvent = () => { - title.textContent = guild.properties.name; - body.innerHTML = ""; - for (const emojit of guild.emojis) { - const emojiElem = document.createElement("div"); - emojiElem.classList.add("emojiSelect"); - - const emojiClass = new Emoji( - { - id: emojit.id as string, - name: emojit.name, - animated: emojit.animated as boolean, - }, - localuser - ); - emojiElem.append(emojiClass.getHTML()); - body.append(emojiElem); - - emojiElem.addEventListener("click", () => { - res(emojiClass); - if (Contextmenu.currentmenu !== "") { - Contextmenu.currentmenu.remove(); - } - }); - } - }; - - select.addEventListener("click", clickEvent); - if (isFirst) { - clickEvent(); - isFirst = false; - } - }); - - setTimeout(() => { - if (Contextmenu.currentmenu != "") { - Contextmenu.currentmenu.remove(); - } - document.body.append(menu); - Contextmenu.currentmenu = menu; - Contextmenu.keepOnScreen(menu); - }, 10); - - let i = 0; - for (const thing of Emoji.emojis) { - const select = document.createElement("div"); - select.textContent = thing.emojis[0].emoji; - select.classList.add("emojiSelect"); - selection.append(select); - const clickEvent = () => { - title.textContent = thing.name; - body.innerHTML = ""; - for (const emojit of thing.emojis) { - const emoji = document.createElement("div"); - emoji.classList.add("emojiSelect"); - emoji.textContent = emojit.emoji; - body.append(emoji); - emoji.onclick = (_) => { - res(emojit.emoji); - if (Contextmenu.currentmenu !== "") { - Contextmenu.currentmenu.remove(); - } - }; - } - }; - select.onclick = clickEvent; - if (i === 0) { - clickEvent(); - } - i++; - } - menu.append(selection); - menu.append(body); - return promise; - } - } - Emoji.grabEmoji(); - export { Emoji }; +Emoji.grabEmoji(); +export { Emoji };