From e12b99c38b8086f4672fa3c8897be0241d2446ae Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Thu, 19 Sep 2024 12:49:50 -0500 Subject: [PATCH] lint and merge --- eslint.config.cjs | 4 +- package.json | 4 +- src/index.ts | 138 +- src/stats.ts | 363 ++-- src/utils.ts | 150 +- src/webpage/audio.ts | 324 +-- src/webpage/channel.ts | 2511 ++++++++++++----------- src/webpage/contextmenu.ts | 166 +- src/webpage/dialog.ts | 488 ++--- src/webpage/direct.ts | 470 ++--- src/webpage/embed.ts | 774 ++++--- src/webpage/emoji.ts | 439 ++-- src/webpage/file.ts | 294 +-- src/webpage/guild.ts | 996 ++++----- src/webpage/home.html | 104 +- src/webpage/home.ts | 134 +- src/webpage/index.html | 126 +- src/webpage/index.ts | 338 +-- src/webpage/infiniteScroller.ts | 612 +++--- src/webpage/invite.ts | 238 +-- src/webpage/jsontypes.ts | 38 +- src/webpage/localuser.ts | 3394 +++++++++++++++---------------- src/webpage/login.html | 114 +- src/webpage/login.ts | 861 ++++---- src/webpage/markdown.ts | 1572 +++++++------- src/webpage/member.ts | 412 ++-- src/webpage/message.ts | 1193 ++++++----- src/webpage/permissions.ts | 652 +++--- src/webpage/register.ts | 236 +-- src/webpage/role.ts | 304 +-- src/webpage/service.ts | 162 +- src/webpage/settings.ts | 2128 +++++++++---------- src/webpage/snowflake.ts | 38 +- src/webpage/user.ts | 876 ++++---- 34 files changed, 10323 insertions(+), 10330 deletions(-) diff --git a/eslint.config.cjs b/eslint.config.cjs index 69d4693..75b1f41 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -267,8 +267,8 @@ module.exports = [ parser: tsParser, globals: global, }, - files: ["webpage/*.ts"], - ignores: ["!*.js", "!*.ts"], + files: ["src/*.ts","src/**/*.ts",], + ignores: ["dist/", "node_modules/"], plugins: { unicorn, sonarjs, diff --git a/package.json b/package.json index e7ff411..f183dfa 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "gulp-copy": "^5.0.0", "gulp-typescript": "^6.0.0-alpha.1", "typescript": "^5.6.2", - "typescript-eslint": "^7.18.0" + "typescript-eslint": "^8.6.0" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 97be27a..66323b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,13 @@ #!/usr/bin/env node -import compression from "compression"; -import express, { Request, Response } from "express"; -import fs from "node:fs"; -import fetch from "node-fetch"; -import path from "path"; -import { observe, uptime } from "./stats.js"; -import { getApiUrls, inviteResponse } from "./utils.js"; -import { fileURLToPath } from "url"; +import compression from"compression"; +import express, { Request, Response }from"express"; +import fs from"node:fs"; +import fetch from"node-fetch"; +import path from"node:path"; +import{ observe, uptime }from"./stats.js"; +import{ getApiUrls, inviteResponse }from"./utils.js"; +import{ fileURLToPath }from"node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -18,103 +18,103 @@ name: string; } const app = express(); -import instances from "./webpage/instances.json" with { type: "json" }; +import instances from"./webpage/instances.json" with { type: "json" }; const instanceNames = new Map(); - for (const instance of instances) { +for(const instance of instances){ instanceNames.set(instance.name, instance); - } +} - app.use(compression()); +app.use(compression()); - async function updateInstances(): Promise { - try { +async function updateInstances(): Promise{ + try{ const response = await fetch( - "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json" + "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json" ); const json = (await response.json()) as Instance[]; - for (const instance of json) { - if (!instanceNames.has(instance.name)) { - instances.push(instance as any); - } else { - const existingInstance = instanceNames.get(instance.name); - if (existingInstance) { - for (const key of Object.keys(instance)) { - if (!existingInstance[key]) { - existingInstance[key] = instance[key]; - } - } - } - } + for(const instance of json){ + if(!instanceNames.has(instance.name)){ + instances.push(instance as any); + }else{ + const existingInstance = instanceNames.get(instance.name); + if(existingInstance){ + for(const key of Object.keys(instance)){ + if(!existingInstance[key]){ + existingInstance[key] = instance[key]; + } + } + } + } } observe(instances); - } catch (error) { + }catch(error){ console.error("Error updating instances:", error); - } - } + } +} - updateInstances(); +updateInstances(); - app.use("/getupdates", (_req: Request, res: Response) => { - try { +app.use("/getupdates", (_req: Request, res: Response)=>{ + try{ const stats = fs.statSync(path.join(__dirname, "webpage")); res.send(stats.mtimeMs.toString()); - } catch (error) { + }catch(error){ console.error("Error getting updates:", error); res.status(500).send("Error getting updates"); - } - }); + } +}); - app.use("/services/oembed", (req: Request, res: Response) => { - inviteResponse(req, res); - }); +app.use("/services/oembed", (req: Request, res: Response)=>{ + inviteResponse(req, res); +}); - app.use("/uptime", (req: Request, res: Response) => { - const instanceUptime = uptime[req.query.name as string]; - res.send(instanceUptime); - }); +app.use("/uptime", (req: Request, res: Response)=>{ + const instanceUptime = uptime[req.query.name as string]; + res.send(instanceUptime); +}); - app.use("/", async (req: Request, res: Response) => { - const scheme = req.secure ? "https" : "http"; - const host = `${scheme}://${req.get("Host")}`; - const ref = host + req.originalUrl; +app.use("/", async (req: Request, res: Response)=>{ + const scheme = req.secure ? "https" : "http"; + const host = `${scheme}://${req.get("Host")}`; + const ref = host + req.originalUrl; - if (host && ref) { + if(host && ref){ const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; res.set( - "Link", - `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` + "Link", + `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` ); - } + } - if (req.path === "/") { + if(req.path === "/"){ res.sendFile(path.join(__dirname, "webpage", "home.html")); return; - } + } - if (req.path.startsWith("/instances.json")) { + if(req.path.startsWith("/instances.json")){ res.json(instances); return; - } + } - if (req.path.startsWith("/invite/")) { + if(req.path.startsWith("/invite/")){ res.sendFile(path.join(__dirname, "webpage", "invite.html")); return; - } + } - const filePath = path.join(__dirname, "webpage", req.path); - if (fs.existsSync(filePath)) { + const filePath = path.join(__dirname, "webpage", req.path); + if(fs.existsSync(filePath)){ res.sendFile(filePath); - } else if (fs.existsSync(`${filePath}.html`)) { + }else if(fs.existsSync(`${filePath}.html`)){ res.sendFile(`${filePath}.html`); - } else { + }else{ res.sendFile(path.join(__dirname, "webpage", "index.html")); - } - }); + } +}); - const PORT = process.env.PORT || Number(process.argv[2]) || 8080; - app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); - }); +const PORT = process.env.PORT || Number(process.argv[2]) || 8080; +app.listen(PORT, ()=>{ + console.log(`Server running on port ${PORT}`); +}); - export { getApiUrls }; +export{ getApiUrls }; diff --git a/src/stats.ts b/src/stats.ts index fd2ab5e..42fd207 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -1,8 +1,8 @@ -import fs from "node:fs"; -import path from "path"; -import fetch from "node-fetch"; -import { getApiUrls } from "./utils.js"; -import { fileURLToPath } from "url"; +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"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -28,228 +28,227 @@ interface Instance { }; } -let uptimeObject: UptimeObject = loadUptimeObject(); -export { uptimeObject as uptime }; +const 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 {}; +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{}; } -function saveUptimeObject(): void { - fs.writeFile( - path.join(__dirname, "..", "uptime.json"), - JSON.stringify(uptimeObject), - (error) => { - if (error) { - console.error("Error saving uptime.json:", error); - } - } - ); +function saveUptimeObject(): void{ + 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(); - } +function removeUndefinedKey(): void{ + 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); +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); } 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); - } + 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" }); - 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); - } - } + 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); + 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; + 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; + } - const lastEntry = obj.at(-1); - if (!lastEntry || lastEntry.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/utils.ts b/src/utils.ts index 2514908..05cc353 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ -import fetch from "node-fetch"; -import { Request, Response } from "express"; +import fetch from"node-fetch"; +import{ Request, Response }from"express"; interface ApiUrls { api: string; @@ -20,95 +20,95 @@ username: string; }; } -export async function getApiUrls(url: string): Promise { - if (!url.endsWith("/")) { - url += "/"; +export async function getApiUrls(url: string): Promise{ + if(!url.endsWith("/")){ + url += "/"; } - try { - const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( - (res) => res.json() as Promise + try{ + const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( + res=>res.json() as Promise ); const api = info.api; const apiUrl = new URL(api); const policies: any = await fetch( - `${api}${ - apiUrl.pathname.includes("api") ? "" : "api" - }/policies/instance/domains` - ).then((res) => res.json()); - return { - api: policies.apiEndpoint, - gateway: policies.gateway, - cdn: policies.cdn, - wellknown: url, + `${api}${ + apiUrl.pathname.includes("api") ? "" : "api" + }/policies/instance/domains` + ).then(res=>res.json()); + return{ + api: policies.apiEndpoint, + gateway: policies.gateway, + cdn: policies.cdn, + wellknown: url, }; - } catch (error) { + }catch(error){ console.error("Error fetching API URLs:", error); return null; - } - } + } +} - export async function inviteResponse( - req: Request, - res: Response - ): Promise { - let url: URL; - if (URL.canParse(req.query.url as string)) { - url = new URL(req.query.url as string); - } else { - const scheme = req.secure ? "https" : "http"; - const host = `${scheme}://${req.get("Host")}`; - url = new URL(host); - } +export async function inviteResponse( + req: Request, + res: Response +): Promise{ + let url: URL; + if(URL.canParse(req.query.url as string)){ + url = new URL(req.query.url as string); + }else{ + const scheme = req.secure ? "https" : "http"; + const host = `${scheme}://${req.get("Host")}`; + url = new URL(host); + } - try { - if (url.pathname.startsWith("invite")) { + try{ + if(url.pathname.startsWith("invite")){ throw new Error("Invalid invite URL"); - } + } - const code = url.pathname.split("/")[2]; - const instance = url.searchParams.get("instance"); - if (!instance) { + const code = url.pathname.split("/")[2]; + const instance = url.searchParams.get("instance"); + if(!instance){ throw new Error("Instance not specified"); - } - const urls = await getApiUrls(instance); - if (!urls) { + } + 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 - ); - const title = invite.guild.name; - const description = invite.inviter - ? `${invite.inviter.username} has invited you to ${invite.guild.name}${ + const invite = await fetch(`${urls.api}/invites/${code}`).then( + res=>res.json() as Promise + ); + 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}${ + }` + : `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 thumbnail = invite.guild.icon + ? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png` + : ""; - const jsonResponse = { - type: "link", - version: "1.0", - title, - thumbnail, - description, - }; + const jsonResponse = { + type: "link", + version: "1.0", + title, + thumbnail, + description, + }; - res.json(jsonResponse); - } catch (error) { - console.error("Error processing invite response:", error); - const jsonResponse = { - 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); - } - } + res.json(jsonResponse); + }catch(error){ + console.error("Error processing invite response:", error); + const jsonResponse = { + 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); + } +} diff --git a/src/webpage/audio.ts b/src/webpage/audio.ts index 5652f79..a92bbaf 100644 --- a/src/webpage/audio.ts +++ b/src/webpage/audio.ts @@ -1,164 +1,164 @@ -import { getBulkInfo } from "./login.js"; +import{ getBulkInfo }from"./login.js"; -class Voice { -audioCtx: AudioContext; -info: { wave: string | Function; freq: number }; -playing: boolean; -myArrayBuffer: AudioBuffer; -gainNode: GainNode; -buffer: Float32Array; -source: AudioBufferSourceNode; -constructor(wave: string | Function, freq: number, volume = 1) { -this.audioCtx = new window.AudioContext(); -this.info = { wave, freq }; -this.playing = false; -this.myArrayBuffer = this.audioCtx.createBuffer( -1, -this.audioCtx.sampleRate, -this.audioCtx.sampleRate -); -this.gainNode = this.audioCtx.createGain(); -this.gainNode.gain.value = volume; -this.gainNode.connect(this.audioCtx.destination); -this.buffer = this.myArrayBuffer.getChannelData(0); -this.source = this.audioCtx.createBufferSource(); -this.source.buffer = this.myArrayBuffer; -this.source.loop = true; -this.source.start(); -this.updateWave(); +class Voice{ + audioCtx: AudioContext; + info: { wave: string | Function; freq: number }; + playing: boolean; + myArrayBuffer: AudioBuffer; + gainNode: GainNode; + buffer: Float32Array; + source: AudioBufferSourceNode; + constructor(wave: string | Function, freq: number, volume = 1){ + this.audioCtx = new window.AudioContext(); + this.info = { wave, freq }; + this.playing = false; + this.myArrayBuffer = this.audioCtx.createBuffer( + 1, + this.audioCtx.sampleRate, + this.audioCtx.sampleRate + ); + this.gainNode = this.audioCtx.createGain(); + this.gainNode.gain.value = volume; + this.gainNode.connect(this.audioCtx.destination); + this.buffer = this.myArrayBuffer.getChannelData(0); + this.source = this.audioCtx.createBufferSource(); + this.source.buffer = this.myArrayBuffer; + this.source.loop = true; + this.source.start(); + this.updateWave(); + } + get wave(): string | Function{ + return this.info.wave; + } + get freq(): number{ + return this.info.freq; + } + set wave(wave: string | Function){ + this.info.wave = wave; + this.updateWave(); + } + set freq(freq: number){ + this.info.freq = freq; + this.updateWave(); + } + updateWave(): void{ + const func = this.waveFunction(); + for(let i = 0; i < this.buffer.length; i++){ + this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); + } + } + waveFunction(): Function{ + if(typeof this.wave === "function"){ + return this.wave; + } + switch(this.wave){ + case"sin": + return(t: number, freq: number)=>{ + return Math.sin(t * Math.PI * 2 * freq); + }; + case"triangle": + return(t: number, freq: number)=>{ + return Math.abs(((4 * t * freq) % 4) - 2) - 1; + }; + case"sawtooth": + return(t: number, freq: number)=>{ + return((t * freq) % 1) * 2 - 1; + }; + case"square": + return(t: number, freq: number)=>{ + return(t * freq) % 2 < 1 ? 1 : -1; + }; + case"white": + return(_t: number, _freq: number)=>{ + return Math.random() * 2 - 1; + }; + case"noise": + return(_t: number, _freq: number)=>{ + return 0; + }; + } + return new Function(); + } + play(): void{ + if(this.playing){ + return; + } + this.source.connect(this.gainNode); + this.playing = true; + } + stop(): void{ + if(this.playing){ + this.source.disconnect(); + this.playing = false; + } + } + static noises(noise: string): void{ + switch(noise){ + case"three": { + const voicy = new Voice("sin", 800); + voicy.play(); + setTimeout(_=>{ + voicy.freq = 1000; + }, 50); + setTimeout(_=>{ + voicy.freq = 1300; + }, 100); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case"zip": { + const voicy = new Voice((t: number, freq: number)=>{ + return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); + }, 700); + voicy.play(); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case"square": { + const voicy = new Voice("square", 600, 0.4); + voicy.play(); + setTimeout(_=>{ + voicy.freq = 800; + }, 50); + setTimeout(_=>{ + voicy.freq = 1000; + }, 100); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + case"beep": { + const voicy = new Voice("sin", 800); + voicy.play(); + setTimeout(_=>{ + voicy.stop(); + }, 50); + setTimeout(_=>{ + voicy.play(); + }, 100); + setTimeout(_=>{ + voicy.stop(); + }, 150); + break; + } + } + } + static get sounds(){ + return["three", "zip", "square", "beep"]; + } + static setNotificationSound(sound: string){ + const userinfos = getBulkInfo(); + userinfos.preferences.notisound = sound; + localStorage.setItem("userinfos", JSON.stringify(userinfos)); + } + static getNotificationSound(){ + const userinfos = getBulkInfo(); + return userinfos.preferences.notisound; + } } -get wave(): string | Function { -return this.info.wave; -} -get freq(): number { -return this.info.freq; -} -set wave(wave: string | Function) { -this.info.wave = wave; -this.updateWave(); -} -set freq(freq: number) { -this.info.freq = freq; -this.updateWave(); -} -updateWave(): void { -const func = this.waveFunction(); -for (let i = 0; i < this.buffer.length; i++) { -this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); -} -} -waveFunction(): Function { -if (typeof this.wave === "function") { -return this.wave; -} -switch (this.wave) { -case "sin": -return (t: number, freq: number) => { -return Math.sin(t * Math.PI * 2 * freq); -}; -case "triangle": -return (t: number, freq: number) => { -return Math.abs(((4 * t * freq) % 4) - 2) - 1; -}; -case "sawtooth": -return (t: number, freq: number) => { -return ((t * freq) % 1) * 2 - 1; -}; -case "square": -return (t: number, freq: number) => { -return (t * freq) % 2 < 1 ? 1 : -1; -}; -case "white": -return (_t: number, _freq: number) => { -return Math.random() * 2 - 1; -}; -case "noise": -return (_t: number, _freq: number) => { -return 0; -}; -} -return new Function(); -} -play(): void { -if (this.playing) { -return; -} -this.source.connect(this.gainNode); -this.playing = true; -} -stop(): void { -if (this.playing) { -this.source.disconnect(); -this.playing = false; -} -} -static noises(noise: string): void { -switch (noise) { -case "three": { -const voicy = new Voice("sin", 800); -voicy.play(); -setTimeout((_) => { -voicy.freq = 1000; -}, 50); -setTimeout((_) => { -voicy.freq = 1300; -}, 100); -setTimeout((_) => { -voicy.stop(); -}, 150); -break; -} -case "zip": { -const voicy = new Voice((t: number, freq: number) => { -return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); -}, 700); -voicy.play(); -setTimeout((_) => { -voicy.stop(); -}, 150); -break; -} -case "square": { -const voicy = new Voice("square", 600, 0.4); -voicy.play(); -setTimeout((_) => { -voicy.freq = 800; -}, 50); -setTimeout((_) => { -voicy.freq = 1000; -}, 100); -setTimeout((_) => { -voicy.stop(); -}, 150); -break; -} -case "beep": { -const voicy = new Voice("sin", 800); -voicy.play(); -setTimeout((_) => { -voicy.stop(); -}, 50); -setTimeout((_) => { -voicy.play(); -}, 100); -setTimeout((_) => { -voicy.stop(); -}, 150); -break; -} -} -} -static get sounds() { -return ["three", "zip", "square", "beep"]; -} -static setNotificationSound(sound: string) { -const userinfos = getBulkInfo(); -userinfos.preferences.notisound = sound; -localStorage.setItem("userinfos", JSON.stringify(userinfos)); -} -static getNotificationSound() { -const userinfos = getBulkInfo(); -return userinfos.preferences.notisound; -} -} -export { Voice }; +export{ Voice }; diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 7fec361..54cd8c2 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -1,42 +1,42 @@ "use strict"; -import { Message } from "./message.js"; -import { Voice } from "./audio.js"; -import { Contextmenu } from "./contextmenu.js"; -import { Dialog } from "./dialog.js"; -import { Guild } from "./guild.js"; -import { Localuser } from "./localuser.js"; -import { Permissions } from "./permissions.js"; -import { Settings } from "./settings.js"; -import { Role, RoleList } from "./role.js"; -import { InfiniteScroller } from "./infiniteScroller.js"; -import { SnowFlake } from "./snowflake.js"; -import { -channeljson, -embedjson, -messageCreateJson, -messagejson, -readyjson, -startTypingjson, -} from "./jsontypes.js"; -import { MarkDown } from "./markdown.js"; -import { Member } from "./member.js"; +import{ Message }from"./message.js"; +import{ Voice }from"./audio.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ Dialog }from"./dialog.js"; +import{ Guild }from"./guild.js"; +import{ Localuser }from"./localuser.js"; +import{ Permissions }from"./permissions.js"; +import{ Settings }from"./settings.js"; +import{ Role, RoleList }from"./role.js"; +import{ InfiniteScroller }from"./infiniteScroller.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ + channeljson, + embedjson, + messageCreateJson, + messagejson, + readyjson, + startTypingjson, +}from"./jsontypes.js"; +import{ MarkDown }from"./markdown.js"; +import{ Member }from"./member.js"; declare global { interface NotificationOptions { image?: string | null | undefined; } } -class Channel extends SnowFlake { -editing!: Message | null; -type!: number; -owner!: Guild; -headers!: Localuser["headers"]; -name!: string; -parent_id?: string; -parent: Channel | undefined; -children!: Channel[]; -guild_id!: string; -permission_overwrites!: Map; +class Channel extends SnowFlake{ + editing!: Message | null; + type!: number; + owner!: Guild; + headers!: Localuser["headers"]; + name!: string; + parent_id?: string; + parent: Channel | undefined; + children!: Channel[]; + guild_id!: string; + permission_overwrites!: Map; permission_overwritesar!: [Role, Permissions][]; topic!: string; nsfw!: boolean; @@ -50,57 +50,57 @@ permission_overwrites!: Map; message_notifications!: number; allthewayup!: boolean; static contextmenu = new Contextmenu("channel menu"); - replyingto!: Message | null; - infinite!: InfiniteScroller; - idToPrev: Map = new Map(); - idToNext: Map = new Map(); - messages: Map = new Map(); - static setupcontextmenu() { - this.contextmenu.addbutton("Copy channel id", function (this: Channel) { - navigator.clipboard.writeText(this.id); - }); + replyingto!: Message | null; + infinite!: InfiniteScroller; + idToPrev: Map = new Map(); + idToNext: Map = new Map(); + messages: Map = new Map(); + static setupcontextmenu(){ + this.contextmenu.addbutton("Copy channel id", function(this: Channel){ + navigator.clipboard.writeText(this.id); + }); - this.contextmenu.addbutton("Mark as read", function (this: Channel) { - this.readbottom(); - }); + this.contextmenu.addbutton("Mark as read", function(this: Channel){ + this.readbottom(); + }); - this.contextmenu.addbutton("Settings[temp]", function (this: Channel) { - this.generateSettings(); - }); + this.contextmenu.addbutton("Settings[temp]", function(this: Channel){ + this.generateSettings(); + }); - this.contextmenu.addbutton( - "Delete channel", - function (this: Channel) { - this.deleteChannel(); - }, - null, - function () { - return this.isAdmin(); - } - ); + this.contextmenu.addbutton( + "Delete channel", + function(this: Channel){ + this.deleteChannel(); + }, + null, + function(){ + return this.isAdmin(); + } + ); - this.contextmenu.addbutton( - "Edit channel", - function (this: Channel) { - this.editChannel(); - }, - null, - function () { - return this.isAdmin(); - } - ); + this.contextmenu.addbutton( + "Edit channel", + function(this: Channel){ + this.editChannel(); + }, + null, + function(){ + return this.isAdmin(); + } + ); - this.contextmenu.addbutton( - "Make invite", - function (this: Channel) { - this.createInvite(); - }, - null, - function () { - return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; - } - ); - /* + this.contextmenu.addbutton( + "Make invite", + function(this: Channel){ + this.createInvite(); + }, + null, + function(){ + return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; + } + ); + /* this.contextmenu.addbutton("Test button",function(){ this.localuser.ws.send(JSON.stringify({ "op": 14, @@ -118,55 +118,55 @@ permission_overwrites!: Map; })) },null); /**/ - } - createInvite() { - const div = document.createElement("div"); - div.classList.add("invitediv"); - const text = document.createElement("span"); - div.append(text); - let uses = 0; - let expires = 1800; - const copycontainer = document.createElement("div"); - copycontainer.classList.add("copycontainer"); - const copy = document.createElement("span"); - copy.classList.add("copybutton", "svgtheme", "svg-copy"); - copycontainer.append(copy); - copycontainer.onclick = (_) => { - if (text.textContent) { - navigator.clipboard.writeText(text.textContent); - } - }; - div.append(copycontainer); - const update = () => { - fetch(`${this.info.api}/channels/${this.id}/invites`, { - method: "POST", - headers: this.headers, - body: JSON.stringify({ + } + createInvite(){ + const div = document.createElement("div"); + div.classList.add("invitediv"); + const text = document.createElement("span"); + div.append(text); + let uses = 0; + let expires = 1800; + const copycontainer = document.createElement("div"); + copycontainer.classList.add("copycontainer"); + const copy = document.createElement("span"); + copy.classList.add("copybutton", "svgtheme", "svg-copy"); + copycontainer.append(copy); + copycontainer.onclick = _=>{ + if(text.textContent){ + navigator.clipboard.writeText(text.textContent); + } + }; + div.append(copycontainer); + const update = ()=>{ + fetch(`${this.info.api}/channels/${this.id}/invites`, { + method: "POST", + headers: this.headers, + body: JSON.stringify({ flags: 0, target_type: null, target_user_id: null, max_age: expires + "", max_uses: uses, temporary: uses !== 0, - }), - }) - .then((_) => _.json()) - .then((json) => { + }), + }) + .then(_=>_.json()) + .then(json=>{ const params = new URLSearchParams(""); params.set("instance", this.info.wellknown); const encoded = params.toString(); text.textContent = `${location.origin}/invite/${json.code}?${encoded}`; - }); - }; - update(); - new Dialog([ - "vdiv", - ["title", "Invite people"], - ["text", `to #${this.name} in ${this.guild.properties.name}`], - [ - "select", - "Expire after:", - [ + }); + }; + update(); + new Dialog([ + "vdiv", + ["title", "Invite people"], + ["text", `to #${this.name} in ${this.guild.properties.name}`], + [ + "select", + "Expire after:", + [ "30 Minutes", "1 Hour", "6 Hours", @@ -175,19 +175,18 @@ permission_overwrites!: Map; "7 Days", "30 Days", "Never", - ], - function (e: Event) { - + ], + function(e: Event){ expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0,][(e.srcElement as HTMLSelectElement).selectedIndex]; update(); - }, - 0, - ], - [ - "select", - "Max uses:", - [ + }, + 0, + ], + [ + "select", + "Max uses:", + [ "No limit", "1 use", "5 uses", @@ -195,533 +194,533 @@ permission_overwrites!: Map; "25 uses", "50 uses", "100 uses", - ], - function (e: Event) { + ], + function(e: Event){ uses = [0, 1, 5, 10, 25, 50, 100][(e.srcElement as HTMLSelectElement).selectedIndex]; update(); - }, - 0, - ], - ["html", div], - ]).show(); - } - generateSettings() { - this.sortPerms(); - const settings = new Settings("Settings for " + this.name); + }, + 0, + ], + ["html", div], + ]).show(); + } + generateSettings(){ + this.sortPerms(); + const settings = new Settings("Settings for " + this.name); - const s1 = settings.addButton("roles"); + const s1 = settings.addButton("roles"); - s1.options.push( - new RoleList( - this.permission_overwritesar, - this.guild, - this.updateRolePermissions.bind(this), - true - ) - ); - settings.show(); - } - sortPerms() { - this.permission_overwritesar.sort((a, b) => { - return ( - this.guild.roles.findIndex((_) => _ === a[0]) - - this.guild.roles.findIndex((_) => _ === b[0]) - ); - }); - } - setUpInfiniteScroller() { - this.infinite = new InfiniteScroller( - async (id: string, offset: number): Promise => { - if (offset === 1) { - if (this.idToPrev.has(id)) { + s1.options.push( + new RoleList( + this.permission_overwritesar, + this.guild, + this.updateRolePermissions.bind(this), + true + ) + ); + settings.show(); + } + sortPerms(){ + this.permission_overwritesar.sort((a, b)=>{ + return( + this.guild.roles.indexOf(a[0]) - + this.guild.roles.indexOf(b[0]) + ); + }); + } + setUpInfiniteScroller(){ + this.infinite = new InfiniteScroller( + async (id: string, offset: number): Promise=>{ + if(offset === 1){ + if(this.idToPrev.has(id)){ return this.idToPrev.get(id); - } else { + }else{ await this.grabBefore(id); return this.idToPrev.get(id); - } - } else { - if (this.idToNext.has(id)) { + } + }else{ + if(this.idToNext.has(id)){ return this.idToNext.get(id); - } else if (this.lastmessage?.id !== id) { + }else if(this.lastmessage?.id !== id){ await this.grabAfter(id); return this.idToNext.get(id); - } else { - return; - } - } - }, - async (id: string): Promise => { - //await new Promise(_=>{setTimeout(_,Math.random()*10)}) - const messgage = this.messages.get(id); - try { - if (messgage) { - return messgage.buildhtml(); - } else { - console.error(id + " not found"); - } - } catch (e) { - console.error(e); - } - return document.createElement("div"); - }, - async (id: string) => { - const message = this.messages.get(id); - try { - if (message) { - message.deleteDiv(); - return true; - } - } catch (e) { - console.error(e); - } finally { - } - return false; - }, - this.readbottom.bind(this) - ); - } - constructor( - json: channeljson | -1, - owner: Guild, - id: string = json === -1 ? "" : json.id - ) { - super(id); - if (json === -1) { - return; - } - this.editing; - this.type = json.type; - this.owner = owner; - this.headers = this.owner.headers; - this.name = json.name; - if (json.parent_id) { - this.parent_id = json.parent_id; - } - this.parent = undefined; - this.children = []; - this.guild_id = json.guild_id; - this.permission_overwrites = new Map(); - this.permission_overwritesar = []; - for (const thing of json.permission_overwrites) { - if ( - thing.id === "1182819038095799904" || - thing.id === "1182820803700625444" - ) { - continue; - } - if (!this.permission_overwrites.has(thing.id)) { - //either a bug in the server requires this, or the API is cursed - this.permission_overwrites.set( - thing.id, - new Permissions(thing.allow, thing.deny) - ); - const permission = this.permission_overwrites.get(thing.id); - if (permission) { - const role = this.guild.roleids.get(thing.id); - if (role) { - this.permission_overwritesar.push([role, permission]); - } - } - } - } + }else{ - this.topic = json.topic; - this.nsfw = json.nsfw; - this.position = json.position; - this.lastreadmessageid = undefined; - if (json.last_message_id) { - this.lastmessageid = json.last_message_id; - } else { - this.lastmessageid = undefined; - } - this.setUpInfiniteScroller(); - this.perminfo ??= {}; - } - get perminfo() { - return this.guild.perminfo.channels[this.id]; - } - set perminfo(e) { - this.guild.perminfo.channels[this.id] = e; - } - isAdmin() { - return this.guild.isAdmin(); - } - get guild() { - return this.owner; - } - get localuser() { - return this.guild.localuser; - } - get info() { - return this.owner.info; - } - readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]) { - this.lastreadmessageid = json.last_message_id; - this.mentions = json.mention_count; - this.mentions ??= 0; - this.lastpin = json.last_pin_timestamp; - } - get hasunreads(): boolean { - if (!this.hasPermission("VIEW_CHANNEL")) { - return false; - } - return ( - !!this.lastmessageid && + } + } + }, + async (id: string): Promise=>{ + //await new Promise(_=>{setTimeout(_,Math.random()*10)}) + const messgage = this.messages.get(id); + try{ + if(messgage){ + return messgage.buildhtml(); + }else{ + console.error(id + " not found"); + } + }catch(e){ + console.error(e); + } + return document.createElement("div"); + }, + async (id: string)=>{ + const message = this.messages.get(id); + try{ + if(message){ + message.deleteDiv(); + return true; + } + }catch(e){ + console.error(e); + }finally{ + } + return false; + }, + this.readbottom.bind(this) + ); + } + constructor( + json: channeljson | -1, + owner: Guild, + id: string = json === -1 ? "" : json.id + ){ + super(id); + if(json === -1){ + return; + } + this.editing; + this.type = json.type; + this.owner = owner; + this.headers = this.owner.headers; + this.name = json.name; + if(json.parent_id){ + this.parent_id = json.parent_id; + } + this.parent = undefined; + this.children = []; + this.guild_id = json.guild_id; + this.permission_overwrites = new Map(); + this.permission_overwritesar = []; + for(const thing of json.permission_overwrites){ + if( + thing.id === "1182819038095799904" || + thing.id === "1182820803700625444" + ){ + continue; + } + if(!this.permission_overwrites.has(thing.id)){ + //either a bug in the server requires this, or the API is cursed + this.permission_overwrites.set( + thing.id, + new Permissions(thing.allow, thing.deny) + ); + const permission = this.permission_overwrites.get(thing.id); + if(permission){ + const role = this.guild.roleids.get(thing.id); + if(role){ + this.permission_overwritesar.push([role, permission]); + } + } + } + } + + this.topic = json.topic; + this.nsfw = json.nsfw; + this.position = json.position; + this.lastreadmessageid = undefined; + if(json.last_message_id){ + this.lastmessageid = json.last_message_id; + }else{ + this.lastmessageid = undefined; + } + this.setUpInfiniteScroller(); + this.perminfo ??= {}; + } + get perminfo(){ + return this.guild.perminfo.channels[this.id]; + } + set perminfo(e){ + this.guild.perminfo.channels[this.id] = e; + } + isAdmin(){ + return this.guild.isAdmin(); + } + get guild(){ + return this.owner; + } + get localuser(){ + return this.guild.localuser; + } + get info(){ + return this.owner.info; + } + readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]){ + this.lastreadmessageid = json.last_message_id; + this.mentions = json.mention_count; + this.mentions ??= 0; + this.lastpin = json.last_pin_timestamp; + } + get hasunreads(): boolean{ + if(!this.hasPermission("VIEW_CHANNEL")){ + return false; + } + return( + Boolean(this.lastmessageid) && (!this.lastreadmessageid || SnowFlake.stringToUnixTime(this.lastmessageid) > SnowFlake.stringToUnixTime(this.lastreadmessageid)) && this.type !== 4 - ); - } - hasPermission(name: string, member = this.guild.member): boolean { - if (member.isAdmin()) { - return true; - } - for (const thing of member.roles) { - let premission = this.permission_overwrites.get(thing.id); - if (premission) { - const perm = premission.getPermission(name); - if (perm) { - return perm === 1; - } - } - if (thing.permissions.getPermission(name)) { - return true; - } - } - return false; - } - get canMessage(): boolean { - if ( - this.permission_overwritesar.length === 0 && + ); + } + hasPermission(name: string, member = this.guild.member): boolean{ + if(member.isAdmin()){ + return true; + } + for(const thing of member.roles){ + const premission = this.permission_overwrites.get(thing.id); + if(premission){ + const perm = premission.getPermission(name); + if(perm){ + return perm === 1; + } + } + if(thing.permissions.getPermission(name)){ + return true; + } + } + return false; + } + get canMessage(): boolean{ + if( + this.permission_overwritesar.length === 0 && this.hasPermission("MANAGE_CHANNELS") - ) { - const role = this.guild.roles.find((_) => _.name === "@everyone"); - if (role) { - this.addRoleToPerms(role); - } - } - return this.hasPermission("SEND_MESSAGES"); - } - sortchildren() { - this.children.sort((a, b) => { - return a.position - b.position; - }); - } - resolveparent(_guild: Guild) { - const parentid = this.parent_id; - if (!parentid) return false; - this.parent = this.localuser.channelids.get(parentid); - this.parent ??= undefined; - if (this.parent !== undefined) { - this.parent.children.push(this); - } - return this.parent !== undefined; - } - calculateReorder() { - let position = -1; - const build: { + ){ + const role = this.guild.roles.find(_=>_.name === "@everyone"); + if(role){ + this.addRoleToPerms(role); + } + } + return this.hasPermission("SEND_MESSAGES"); + } + sortchildren(){ + this.children.sort((a, b)=>{ + return a.position - b.position; + }); + } + resolveparent(_guild: Guild){ + const parentid = this.parent_id; + if(!parentid)return false; + this.parent = this.localuser.channelids.get(parentid); + this.parent ??= undefined; + if(this.parent !== undefined){ + this.parent.children.push(this); + } + return this.parent !== undefined; + } + calculateReorder(){ + let position = -1; + const build: { id: string; position: number | undefined; parent_id: string | undefined; }[] = []; - for (const thing of this.children) { - const thisthing: { + for(const thing of this.children){ + const thisthing: { id: string; position: number | undefined; parent_id: string | undefined; } = { id: thing.id, position: undefined, parent_id: undefined }; - if (thing.position < position) { - thing.position = thisthing.position = position + 1; - } - position = thing.position; - if (thing.move_id && thing.move_id !== thing.parent_id) { - thing.parent_id = thing.move_id; - thisthing.parent_id = thing.parent?.id; - thing.move_id = undefined; - //console.log(this.guild.channelids[thisthing.parent_id.id]); - } - if (thisthing.position || thisthing.parent_id) { - build.push(thisthing); - } - } - return build; - } - static dragged: [Channel, HTMLDivElement] | [] = []; - html: WeakRef | undefined; - get visable() { - return this.hasPermission("VIEW_CHANNEL"); - } - createguildHTML(admin = false): HTMLDivElement { - const div = document.createElement("div"); - this.html = new WeakRef(div); - if (!this.visable) { - let quit = true; - for (const thing of this.children) { - if (thing.visable) { - quit = false; - } - } - if (quit) { - return div; - } - } - // @ts-ignore I dont wanna deal with this - div["all"] = this; - div.draggable = admin; - div.addEventListener("dragstart", (e) => { - Channel.dragged = [this, div]; - e.stopImmediatePropagation(); - }); - div.addEventListener("dragend", () => { - Channel.dragged = []; - }); - if (this.type === 4) { - this.sortchildren(); - const caps = document.createElement("div"); + if(thing.position < position){ + thing.position = thisthing.position = position + 1; + } + position = thing.position; + if(thing.move_id && thing.move_id !== thing.parent_id){ + thing.parent_id = thing.move_id; + thisthing.parent_id = thing.parent?.id; + thing.move_id = undefined; + //console.log(this.guild.channelids[thisthing.parent_id.id]); + } + if(thisthing.position || thisthing.parent_id){ + build.push(thisthing); + } + } + return build; + } + static dragged: [Channel, HTMLDivElement] | [] = []; + html: WeakRef | undefined; + get visable(){ + return this.hasPermission("VIEW_CHANNEL"); + } + createguildHTML(admin = false): HTMLDivElement{ + const div = document.createElement("div"); + this.html = new WeakRef(div); + if(!this.visable){ + let quit = true; + for(const thing of this.children){ + if(thing.visable){ + quit = false; + } + } + if(quit){ + return div; + } + } + // @ts-ignore I dont wanna deal with this + div.all = this; + div.draggable = admin; + div.addEventListener("dragstart", e=>{ + Channel.dragged = [this, div]; + e.stopImmediatePropagation(); + }); + div.addEventListener("dragend", ()=>{ + Channel.dragged = []; + }); + if(this.type === 4){ + this.sortchildren(); + const caps = document.createElement("div"); - const decdiv = document.createElement("div"); - const decoration = document.createElement("span"); - decoration.classList.add("svgtheme", "collapse-icon", "svg-category"); - decdiv.appendChild(decoration); + const decdiv = document.createElement("div"); + const decoration = document.createElement("span"); + decoration.classList.add("svgtheme", "collapse-icon", "svg-category"); + decdiv.appendChild(decoration); - const myhtml = document.createElement("p2"); - myhtml.textContent = this.name; - decdiv.appendChild(myhtml); - caps.appendChild(decdiv); - const childrendiv = document.createElement("div"); - if (admin) { - const addchannel = document.createElement("span"); - addchannel.textContent = "+"; - addchannel.classList.add("addchannel"); - caps.appendChild(addchannel); - addchannel.onclick = (_) => { - this.guild.createchannels(this.createChannel.bind(this)); - }; - this.coatDropDiv(decdiv, childrendiv); - } - div.appendChild(caps); - caps.classList.add("capsflex"); - decdiv.classList.add("channeleffects"); - decdiv.classList.add("channel"); + const myhtml = document.createElement("p2"); + myhtml.textContent = this.name; + decdiv.appendChild(myhtml); + caps.appendChild(decdiv); + const childrendiv = document.createElement("div"); + if(admin){ + const addchannel = document.createElement("span"); + addchannel.textContent = "+"; + addchannel.classList.add("addchannel"); + caps.appendChild(addchannel); + addchannel.onclick = _=>{ + this.guild.createchannels(this.createChannel.bind(this)); + }; + this.coatDropDiv(decdiv, childrendiv); + } + div.appendChild(caps); + caps.classList.add("capsflex"); + decdiv.classList.add("channeleffects"); + decdiv.classList.add("channel"); - Channel.contextmenu.bindContextmenu(decdiv, this, undefined); - // @ts-ignore I dont wanna deal with this - decdiv["all"] = this; + Channel.contextmenu.bindContextmenu(decdiv, this); + // @ts-ignore I dont wanna deal with this + decdiv.all = this; - for (const channel of this.children) { - childrendiv.appendChild(channel.createguildHTML(admin)); - } - childrendiv.classList.add("channels"); - setTimeout((_: any) => { - if (!this.perminfo.collapsed) { - childrendiv.style.height = childrendiv.scrollHeight + "px"; - } - }, 100); - div.appendChild(childrendiv); - if (this.perminfo.collapsed) { - decoration.classList.add("hiddencat"); - childrendiv.style.height = "0px"; - } - decdiv.onclick = () => { - if (childrendiv.style.height !== "0px") { - decoration.classList.add("hiddencat"); - this.perminfo.collapsed = true; - this.localuser.userinfo.updateLocal(); - childrendiv.style.height = "0px"; - } else { - decoration.classList.remove("hiddencat"); - this.perminfo.collapsed = false; - this.localuser.userinfo.updateLocal(); - childrendiv.style.height = childrendiv.scrollHeight + "px"; - } - }; - } else { - div.classList.add("channel"); - if (this.hasunreads) { - div.classList.add("cunread"); - } - Channel.contextmenu.bindContextmenu(div, this, undefined); - if (admin) { - this.coatDropDiv(div); - } - // @ts-ignore I dont wanna deal with this - div["all"] = this; - const myhtml = document.createElement("span"); - myhtml.textContent = this.name; - if (this.type === 0) { - const decoration = document.createElement("span"); - div.appendChild(decoration); - decoration.classList.add("space", "svgtheme", "svg-channel"); - } else if (this.type === 2) { - // - const decoration = document.createElement("span"); - div.appendChild(decoration); - decoration.classList.add("space", "svgtheme", "svg-voice"); - } else if (this.type === 5) { - // - const decoration = document.createElement("span"); - div.appendChild(decoration); - decoration.classList.add("space", "svgtheme", "svg-announce"); - } else { - console.log(this.type); - } - div.appendChild(myhtml); - div.onclick = (_) => { - this.getHTML(); - }; - } - return div; - } - get myhtml() { - if (this.html) { - return this.html.deref(); - } else { - return undefined; - } - } - readbottom() { - if (!this.hasunreads) { - return; - } - fetch( - this.info.api + + for(const channel of this.children){ + childrendiv.appendChild(channel.createguildHTML(admin)); + } + childrendiv.classList.add("channels"); + setTimeout((_: any)=>{ + if(!this.perminfo.collapsed){ + childrendiv.style.height = childrendiv.scrollHeight + "px"; + } + }, 100); + div.appendChild(childrendiv); + if(this.perminfo.collapsed){ + decoration.classList.add("hiddencat"); + childrendiv.style.height = "0px"; + } + decdiv.onclick = ()=>{ + if(childrendiv.style.height !== "0px"){ + decoration.classList.add("hiddencat"); + this.perminfo.collapsed = true; + this.localuser.userinfo.updateLocal(); + childrendiv.style.height = "0px"; + }else{ + decoration.classList.remove("hiddencat"); + this.perminfo.collapsed = false; + this.localuser.userinfo.updateLocal(); + childrendiv.style.height = childrendiv.scrollHeight + "px"; + } + }; + }else{ + div.classList.add("channel"); + if(this.hasunreads){ + div.classList.add("cunread"); + } + Channel.contextmenu.bindContextmenu(div, this); + if(admin){ + this.coatDropDiv(div); + } + // @ts-ignore I dont wanna deal with this + div.all = this; + const myhtml = document.createElement("span"); + myhtml.textContent = this.name; + if(this.type === 0){ + const decoration = document.createElement("span"); + div.appendChild(decoration); + decoration.classList.add("space", "svgtheme", "svg-channel"); + }else if(this.type === 2){ + // + const decoration = document.createElement("span"); + div.appendChild(decoration); + decoration.classList.add("space", "svgtheme", "svg-voice"); + }else if(this.type === 5){ + // + const decoration = document.createElement("span"); + div.appendChild(decoration); + decoration.classList.add("space", "svgtheme", "svg-announce"); + }else{ + console.log(this.type); + } + div.appendChild(myhtml); + div.onclick = _=>{ + this.getHTML(); + }; + } + return div; + } + get myhtml(){ + if(this.html){ + return this.html.deref(); + }else{ + + } + } + readbottom(){ + if(!this.hasunreads){ + return; + } + fetch( + this.info.api + "/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack", - { - method: "POST", - headers: this.headers, - body: JSON.stringify({}), - } - ); - this.lastreadmessageid = this.lastmessageid; - this.guild.unreads(); - if (this.myhtml) { - this.myhtml.classList.remove("cunread"); - } - } - coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false) { - div.addEventListener("dragenter", (event) => { - console.log("enter"); - event.preventDefault(); - }); + { + method: "POST", + headers: this.headers, + body: JSON.stringify({}), + } + ); + this.lastreadmessageid = this.lastmessageid; + this.guild.unreads(); + if(this.myhtml){ + this.myhtml.classList.remove("cunread"); + } + } + coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false){ + div.addEventListener("dragenter", event=>{ + console.log("enter"); + event.preventDefault(); + }); - div.addEventListener("dragover", (event) => { - event.preventDefault(); - }); + div.addEventListener("dragover", event=>{ + event.preventDefault(); + }); - div.addEventListener("drop", (event) => { - const that = Channel.dragged[0]; - if (!that) return; - event.preventDefault(); - if (container) { - that.move_id = this.id; - if (that.parent) { - that.parent.children.splice(that.parent.children.indexOf(that), 1); - } - that.parent = this; - (container as HTMLElement).prepend( + div.addEventListener("drop", event=>{ + const that = Channel.dragged[0]; + if(!that)return; + event.preventDefault(); + if(container){ + that.move_id = this.id; + if(that.parent){ + that.parent.children.splice(that.parent.children.indexOf(that), 1); + } + that.parent = this; + (container as HTMLElement).prepend( Channel.dragged[1] as HTMLDivElement - ); - this.children.unshift(that); - } else { - console.log(this, Channel.dragged); - that.move_id = this.parent_id; - if (that.parent) { - that.parent.children.splice(that.parent.children.indexOf(that), 1); - } else { - this.guild.headchannels.splice( - this.guild.headchannels.indexOf(that), - 1 - ); - } - that.parent = this.parent; - if (that.parent) { - const build: Channel[] = []; - for (let i = 0; i < that.parent.children.length; i++) { - build.push(that.parent.children[i]); - if (that.parent.children[i] === this) { - build.push(that); - } - } - that.parent.children = build; - } else { - const build: Channel[] = []; - for (let i = 0; i < this.guild.headchannels.length; i++) { - build.push(this.guild.headchannels[i]); - if (this.guild.headchannels[i] === this) { - build.push(that); - } - } - this.guild.headchannels = build; - } - if (Channel.dragged[1]) { - div.after(Channel.dragged[1]); - } - } - this.guild.calculateReorder(); - }); + ); + this.children.unshift(that); + }else{ + console.log(this, Channel.dragged); + that.move_id = this.parent_id; + if(that.parent){ + that.parent.children.splice(that.parent.children.indexOf(that), 1); + }else{ + this.guild.headchannels.splice( + this.guild.headchannels.indexOf(that), + 1 + ); + } + that.parent = this.parent; + if(that.parent){ + const build: Channel[] = []; + for(let i = 0; i < that.parent.children.length; i++){ + build.push(that.parent.children[i]); + if(that.parent.children[i] === this){ + build.push(that); + } + } + that.parent.children = build; + }else{ + const build: Channel[] = []; + for(let i = 0; i < this.guild.headchannels.length; i++){ + build.push(this.guild.headchannels[i]); + if(this.guild.headchannels[i] === this){ + build.push(that); + } + } + this.guild.headchannels = build; + } + if(Channel.dragged[1]){ + div.after(Channel.dragged[1]); + } + } + this.guild.calculateReorder(); + }); - return div; - } - createChannel(name: string, type: number) { - fetch(this.info.api + "/guilds/" + this.guild.id + "/channels", { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - name, - type, - parent_id: this.id, - permission_overwrites: [], - }), - }); - } - editChannel() { - let name = this.name; - let topic = this.topic; - let nsfw = this.nsfw; - const thisid = this.id; - const thistype = this.type; - const full = new Dialog([ - "hdiv", - [ - "vdiv", - [ - "textbox", - "Channel name:", - this.name, - function (this: HTMLInputElement) { - name = this.value; - }, - ], - [ - "mdbox", - "Channel topic:", - this.topic, - function (this: HTMLTextAreaElement) { - topic = this.value; - }, - ], - [ - "checkbox", - "NSFW Channel", - this.nsfw, - function (this: HTMLInputElement) { - nsfw = this.checked; - }, - ], - [ - "button", - "", - "submit", - () => { - fetch(this.info.api + "/channels/" + thisid, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ + return div; + } + createChannel(name: string, type: number){ + fetch(this.info.api + "/guilds/" + this.guild.id + "/channels", { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + type, + parent_id: this.id, + permission_overwrites: [], + }), + }); + } + editChannel(){ + let name = this.name; + let topic = this.topic; + let nsfw = this.nsfw; + const thisid = this.id; + const thistype = this.type; + const full = new Dialog([ + "hdiv", + [ + "vdiv", + [ + "textbox", + "Channel name:", + this.name, + function(this: HTMLInputElement){ + name = this.value; + }, + ], + [ + "mdbox", + "Channel topic:", + this.topic, + function(this: HTMLTextAreaElement){ + topic = this.value; + }, + ], + [ + "checkbox", + "NSFW Channel", + this.nsfw, + function(this: HTMLInputElement){ + nsfw = this.checked; + }, + ], + [ + "button", + "", + "submit", + ()=>{ + fetch(this.info.api + "/channels/" + thisid, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ name, type: thistype, topic, @@ -730,680 +729,680 @@ permission_overwrites!: Map; nsfw, flags: 0, rate_limit_per_user: 0, - }), - }); - console.log(full); - full.hide(); - }, - ], - ], - ]); - full.show(); - console.log(full); - } - deleteChannel() { - fetch(this.info.api + "/channels/" + this.id, { - method: "DELETE", - headers: this.headers, - }); - } - setReplying(message: Message) { - if (this.replyingto?.div) { - this.replyingto.div.classList.remove("replying"); - } - this.replyingto = message; - if (!this.replyingto?.div) return; - console.log(message); - this.replyingto.div.classList.add("replying"); - this.makereplybox(); - } - makereplybox() { - const replybox = document.getElementById("replybox") as HTMLElement; - if (this.replyingto) { - replybox.innerHTML = ""; - const span = document.createElement("span"); - span.textContent = "Replying to " + this.replyingto.author.username; - const X = document.createElement("button"); - X.onclick = (_) => { - if (this.replyingto?.div) { - this.replyingto.div.classList.remove("replying"); - } - replybox.classList.add("hideReplyBox"); - this.replyingto = null; - replybox.innerHTML = ""; - }; - replybox.classList.remove("hideReplyBox"); - X.textContent = "⦻"; - X.classList.add("cancelReply"); - replybox.append(span); - replybox.append(X); - } else { - replybox.classList.add("hideReplyBox"); - } - } - async getmessage(id: string): Promise { - const message = this.messages.get(id); - if (message) { - return message; - } else { - const gety = await fetch( - this.info.api + + }), + }); + console.log(full); + full.hide(); + }, + ], + ], + ]); + full.show(); + console.log(full); + } + deleteChannel(){ + fetch(this.info.api + "/channels/" + this.id, { + method: "DELETE", + headers: this.headers, + }); + } + setReplying(message: Message){ + if(this.replyingto?.div){ + this.replyingto.div.classList.remove("replying"); + } + this.replyingto = message; + if(!this.replyingto?.div)return; + console.log(message); + this.replyingto.div.classList.add("replying"); + this.makereplybox(); + } + makereplybox(){ + const replybox = document.getElementById("replybox") as HTMLElement; + if(this.replyingto){ + replybox.innerHTML = ""; + const span = document.createElement("span"); + span.textContent = "Replying to " + this.replyingto.author.username; + const X = document.createElement("button"); + X.onclick = _=>{ + if(this.replyingto?.div){ + this.replyingto.div.classList.remove("replying"); + } + replybox.classList.add("hideReplyBox"); + this.replyingto = null; + replybox.innerHTML = ""; + }; + replybox.classList.remove("hideReplyBox"); + X.textContent = "⦻"; + X.classList.add("cancelReply"); + replybox.append(span); + replybox.append(X); + }else{ + replybox.classList.add("hideReplyBox"); + } + } + async getmessage(id: string): Promise{ + const message = this.messages.get(id); + if(message){ + return message; + }else{ + const gety = await fetch( + this.info.api + "/channels/" + this.id + "/messages?limit=1&around=" + id, - { headers: this.headers } - ); - const json = await gety.json(); - return new Message(json[0], this); - } - } - static genid: number = 0; - async getHTML() { - const id = ++Channel.genid; - if (this.localuser.channelfocus) { - this.localuser.channelfocus.infinite.delete(); - } - if (this.guild !== this.localuser.lookingguild) { - this.guild.loadGuild(); - } - if (this.localuser.channelfocus && this.localuser.channelfocus.myhtml) { - this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); - } - if (this.myhtml) { - this.myhtml.classList.add("viewChannel"); - } - this.guild.prevchannel = this; - this.guild.perminfo.prevchannel = this.id; - this.localuser.userinfo.updateLocal(); - this.localuser.channelfocus = this; - const prom = this.infinite.delete(); - history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); + { headers: this.headers } + ); + const json = await gety.json(); + return new Message(json[0], this); + } + } + static genid: number = 0; + async getHTML(){ + const id = ++Channel.genid; + if(this.localuser.channelfocus){ + this.localuser.channelfocus.infinite.delete(); + } + if(this.guild !== this.localuser.lookingguild){ + this.guild.loadGuild(); + } + if(this.localuser.channelfocus && this.localuser.channelfocus.myhtml){ + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + if(this.myhtml){ + this.myhtml.classList.add("viewChannel"); + } + this.guild.prevchannel = this; + this.guild.perminfo.prevchannel = this.id; + this.localuser.userinfo.updateLocal(); + this.localuser.channelfocus = this; + const prom = this.infinite.delete(); + history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); - this.localuser.pageTitle("#" + this.name); - const channelTopic = document.getElementById( - "channelTopic" - ) as HTMLSpanElement; - if (this.topic) { - channelTopic.innerHTML = new MarkDown( - this.topic, - this - ).makeHTML().innerHTML; - channelTopic.removeAttribute("hidden"); - } else channelTopic.setAttribute("hidden", ""); + this.localuser.pageTitle("#" + this.name); + const channelTopic = document.getElementById( + "channelTopic" + ) as HTMLSpanElement; + if(this.topic){ + channelTopic.innerHTML = new MarkDown( + this.topic, + this + ).makeHTML().innerHTML; + channelTopic.removeAttribute("hidden"); + }else channelTopic.setAttribute("hidden", ""); - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - Channel.regenLoadingMessages(); - loading.classList.add("loading"); - this.rendertyping(); - await this.putmessages(); - await prom; - if (id !== Channel.genid) { - return; - } - this.makereplybox(); + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + Channel.regenLoadingMessages(); + loading.classList.add("loading"); + this.rendertyping(); + await this.putmessages(); + await prom; + if(id !== Channel.genid){ + return; + } + this.makereplybox(); - await this.buildmessages(); - //loading.classList.remove("loading"); - (document.getElementById("typebox") as HTMLDivElement).contentEditable = + await this.buildmessages(); + //loading.classList.remove("loading"); + (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + this.canMessage; - } - typingmap: Map = new Map(); - async typingStart(typing: startTypingjson): Promise { - const memb = await Member.new(typing.d.member!, this.guild); - if (!memb) return; - if (memb.id === this.localuser.user.id) { - console.log("you is typing"); - return; - } - console.log("user is typing and you should see it"); - this.typingmap.set(memb, Date.now()); - setTimeout(this.rendertyping.bind(this), 10000); - this.rendertyping(); - } - rendertyping(): void { - const typingtext = document.getElementById("typing") as HTMLDivElement; - let build = ""; - let showing = false; - let i = 0; - const curtime = Date.now() - 5000; - for (const thing of this.typingmap.keys()) { - if ((this.typingmap.get(thing) as number) > curtime) { - if (i !== 0) { - build += ", "; - } - i++; - if (thing.nick) { - build += thing.nick; - } else { - build += thing.user.username; - } - showing = true; - } else { - this.typingmap.delete(thing); - } - } - if (i > 1) { - build += " are typing"; - } else { - build += " is typing"; - } - if (this.localuser.channelfocus === this) { - if (showing) { - typingtext.classList.remove("hidden"); - const typingtext2 = document.getElementById( - "typingtext" - ) as HTMLDivElement; - typingtext2.textContent = build; - } else { - typingtext.classList.add("hidden"); - } - } - } - static regenLoadingMessages() { - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - loading.innerHTML = ""; - for (let i = 0; i < 15; i++) { - const div = document.createElement("div"); - div.classList.add("loadingmessage"); - if (Math.random() < 0.5) { - const pfp = document.createElement("div"); - pfp.classList.add("loadingpfp"); - const username = document.createElement("div"); - username.style.width = Math.floor(Math.random() * 96 * 1.5 + 40) + "px"; - username.classList.add("loadingcontent"); - div.append(pfp, username); - } - const content = document.createElement("div"); - content.style.width = Math.floor(Math.random() * 96 * 3 + 40) + "px"; - content.style.height = Math.floor(Math.random() * 3 + 1) * 20 + "px"; - content.classList.add("loadingcontent"); - div.append(content); - loading.append(div); - } - } - lastmessage: Message | undefined; - async putmessages() { - if (this.allthewayup) { - return; - } - if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { - return; - } - const j = await fetch( - this.info.api + "/channels/" + this.id + "/messages?limit=100", - { - headers: this.headers, - } - ); + } + typingmap: Map = new Map(); + async typingStart(typing: startTypingjson): Promise{ + const memb = await Member.new(typing.d.member!, this.guild); + if(!memb)return; + if(memb.id === this.localuser.user.id){ + console.log("you is typing"); + return; + } + console.log("user is typing and you should see it"); + this.typingmap.set(memb, Date.now()); + setTimeout(this.rendertyping.bind(this), 10000); + this.rendertyping(); + } + rendertyping(): void{ + const typingtext = document.getElementById("typing") as HTMLDivElement; + let build = ""; + let showing = false; + let i = 0; + const curtime = Date.now() - 5000; + for(const thing of this.typingmap.keys()){ + if((this.typingmap.get(thing) as number) > curtime){ + if(i !== 0){ + build += ", "; + } + i++; + if(thing.nick){ + build += thing.nick; + }else{ + build += thing.user.username; + } + showing = true; + }else{ + this.typingmap.delete(thing); + } + } + if(i > 1){ + build += " are typing"; + }else{ + build += " is typing"; + } + if(this.localuser.channelfocus === this){ + if(showing){ + typingtext.classList.remove("hidden"); + const typingtext2 = document.getElementById( + "typingtext" + ) as HTMLDivElement; + typingtext2.textContent = build; + }else{ + typingtext.classList.add("hidden"); + } + } + } + static regenLoadingMessages(){ + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + loading.innerHTML = ""; + for(let i = 0; i < 15; i++){ + const div = document.createElement("div"); + div.classList.add("loadingmessage"); + if(Math.random() < 0.5){ + const pfp = document.createElement("div"); + pfp.classList.add("loadingpfp"); + const username = document.createElement("div"); + username.style.width = Math.floor(Math.random() * 96 * 1.5 + 40) + "px"; + username.classList.add("loadingcontent"); + div.append(pfp, username); + } + const content = document.createElement("div"); + content.style.width = Math.floor(Math.random() * 96 * 3 + 40) + "px"; + content.style.height = Math.floor(Math.random() * 3 + 1) * 20 + "px"; + content.classList.add("loadingcontent"); + div.append(content); + loading.append(div); + } + } + lastmessage: Message | undefined; + async putmessages(){ + if(this.allthewayup){ + return; + } + if(this.lastreadmessageid && this.messages.has(this.lastreadmessageid)){ + return; + } + const j = await fetch( + this.info.api + "/channels/" + this.id + "/messages?limit=100", + { + headers: this.headers, + } + ); - const response = await j.json(); - if (response.length !== 100) { - this.allthewayup = true; - } - let prev: Message | undefined; - for (const thing of response) { - const message = new Message(thing, this); - if (prev) { - this.idToNext.set(message.id, prev.id); - this.idToPrev.set(prev.id, message.id); - } else { - this.lastmessage = message; - this.lastmessageid = message.id; - } - prev = message; - } - } - delChannel(json: channeljson) { - const build: Channel[] = []; - for (const thing of this.children) { - if (thing.id !== json.id) { - build.push(thing); - } - } - this.children = build; - } - async grabAfter(id: string) { - if (id === this.lastmessage?.id) { - return; - } - await fetch( - this.info.api + + const response = await j.json(); + if(response.length !== 100){ + this.allthewayup = true; + } + let prev: Message | undefined; + for(const thing of response){ + const message = new Message(thing, this); + if(prev){ + this.idToNext.set(message.id, prev.id); + this.idToPrev.set(prev.id, message.id); + }else{ + this.lastmessage = message; + this.lastmessageid = message.id; + } + prev = message; + } + } + delChannel(json: channeljson){ + const build: Channel[] = []; + for(const thing of this.children){ + if(thing.id !== json.id){ + build.push(thing); + } + } + this.children = build; + } + async grabAfter(id: string){ + if(id === this.lastmessage?.id){ + return; + } + await fetch( + this.info.api + "/channels/" + this.id + "/messages?limit=100&after=" + id, - { - headers: this.headers, - } - ) - .then((j) => { - return j.json(); - }) - .then((response) => { - let previd: string = id; - for (const i in response) { - let messager: Message; - let willbreak = false; - if (this.messages.has(response[i].id)) { - messager = this.messages.get(response[i].id) as Message; - willbreak = true; - } else { - messager = new Message(response[i], this); - } - this.idToPrev.set(messager.id, previd); - this.idToNext.set(previd, messager.id); - previd = messager.id; - if (willbreak) { - break; - } - } - //out.buildmessages(); - }); - } - topid!: string; - async grabBefore(id: string) { - if (this.topid && id === this.topid) { - return; - } + { + headers: this.headers, + } + ) + .then(j=>{ + return j.json(); + }) + .then(response=>{ + let previd: string = id; + for(const i in response){ + let messager: Message; + let willbreak = false; + if(this.messages.has(response[i].id)){ + messager = this.messages.get(response[i].id) as Message; + willbreak = true; + }else{ + messager = new Message(response[i], this); + } + this.idToPrev.set(messager.id, previd); + this.idToNext.set(previd, messager.id); + previd = messager.id; + if(willbreak){ + break; + } + } + //out.buildmessages(); + }); + } + topid!: string; + async grabBefore(id: string){ + if(this.topid && id === this.topid){ + return; + } - await fetch( - this.info.api + + await fetch( + this.info.api + "/channels/" + this.id + "/messages?before=" + id + "&limit=100", - { - headers: this.headers, - } - ) - .then((j) => { - return j.json(); - }) - .then((response: messagejson[]) => { - if (response.length < 100) { - this.allthewayup = true; - if (response.length === 0) { - this.topid = id; - } - } - let previd = id; - for (const i in response) { - let messager: Message; - let willbreak = false; - if (this.messages.has(response[i].id)) { - console.log("flaky"); - messager = this.messages.get(response[i].id) as Message; - willbreak = true; - } else { - messager = new Message(response[i], this); - } + { + headers: this.headers, + } + ) + .then(j=>{ + return j.json(); + }) + .then((response: messagejson[])=>{ + if(response.length < 100){ + this.allthewayup = true; + if(response.length === 0){ + this.topid = id; + } + } + let previd = id; + for(const i in response){ + let messager: Message; + let willbreak = false; + if(this.messages.has(response[i].id)){ + console.log("flaky"); + messager = this.messages.get(response[i].id) as Message; + willbreak = true; + }else{ + messager = new Message(response[i], this); + } - this.idToNext.set(messager.id, previd); - this.idToPrev.set(previd, messager.id); - previd = messager.id; + this.idToNext.set(messager.id, previd); + this.idToPrev.set(previd, messager.id); + previd = messager.id; - if (Number(i) === response.length - 1 && response.length < 100) { - this.topid = previd; - } - if (willbreak) { - break; - } - } - }); - } - /** + if(Number(i) === response.length - 1 && response.length < 100){ + this.topid = previd; + } + if(willbreak){ + break; + } + } + }); + } + /** * Please dont use this, its not implemented. * @deprecated * @todo **/ - async grabArround(/* id: string */) { - //currently unused and no plans to use it yet - throw new Error("please don't call this, no one has implemented it :P"); - } - async buildmessages() { - this.infinitefocus = false; - this.tryfocusinfinate(); - } - infinitefocus = false; - async tryfocusinfinate() { - if (this.infinitefocus) return; - this.infinitefocus = true; - const messages = document.getElementById("channelw") as HTMLDivElement; - const messageContainers = Array.from( - messages.getElementsByClassName("messagecontainer") - ); - for (const thing of messageContainers) { - thing.remove(); - } - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - const removetitle = document.getElementById("removetitle"); - //messages.innerHTML=""; - let id: string | undefined; - if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { - id = this.lastreadmessageid; - } else if ( - this.lastreadmessageid && + async grabArround(/* id: string */){ + //currently unused and no plans to use it yet + throw new Error("please don't call this, no one has implemented it :P"); + } + async buildmessages(){ + this.infinitefocus = false; + this.tryfocusinfinate(); + } + infinitefocus = false; + async tryfocusinfinate(){ + if(this.infinitefocus)return; + this.infinitefocus = true; + const messages = document.getElementById("channelw") as HTMLDivElement; + const messageContainers = Array.from( + messages.getElementsByClassName("messagecontainer") + ); + for(const thing of messageContainers){ + thing.remove(); + } + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + const removetitle = document.getElementById("removetitle"); + //messages.innerHTML=""; + let id: string | undefined; + if(this.lastreadmessageid && this.messages.has(this.lastreadmessageid)){ + id = this.lastreadmessageid; + }else if( + this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid)) - ) { - } else if (this.lastmessageid && this.messages.has(this.lastmessageid)) { - id = this.goBackIds(this.lastmessageid, 50); - } - if (!id) { - if (!removetitle) { - const title = document.createElement("h2"); - title.id = "removetitle"; - title.textContent = + ){ + }else if(this.lastmessageid && this.messages.has(this.lastmessageid)){ + id = this.goBackIds(this.lastmessageid, 50); + } + if(!id){ + if(!removetitle){ + const title = document.createElement("h2"); + title.id = "removetitle"; + title.textContent = "No messages appear to be here, be the first to say something!"; - title.classList.add("titlespace"); - messages.append(title); - } - this.infinitefocus = false; - loading.classList.remove("loading"); - return; - } else if (removetitle) { - removetitle.remove(); - } - if (this.localuser.channelfocus !== this) { - return; - } - const elements = Array.from(messages.getElementsByClassName("scroller")); - for (const elm of elements) { - elm.remove(); - console.warn("rouge element detected and removed"); - } - messages.append(await this.infinite.getDiv(id)); - this.infinite.updatestuff(); - this.infinite.watchForChange().then(async (_) => { - //await new Promise(resolve => setTimeout(resolve, 0)); - this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P - loading.classList.remove("loading"); - }); - //this.infinite.focus(id.id,false); - } - private goBackIds( - id: string, - back: number, - returnifnotexistant = true - ): string | undefined { - while (back !== 0) { - const nextid = this.idToPrev.get(id); - if (nextid) { - id = nextid; - back--; - } else { - if (returnifnotexistant) { - break; - } else { - return undefined; - } - } - } - return id; - } - private findClosest(id: string | undefined) { - if (!this.lastmessageid || !id) return; - let flake: string | undefined = this.lastmessageid; - const time = SnowFlake.stringToUnixTime(id); - let flaketime = SnowFlake.stringToUnixTime(flake); - while (flake && time < flaketime) { - flake = this.idToPrev.get(flake); + title.classList.add("titlespace"); + messages.append(title); + } + this.infinitefocus = false; + loading.classList.remove("loading"); + return; + }else if(removetitle){ + removetitle.remove(); + } + if(this.localuser.channelfocus !== this){ + return; + } + const elements = Array.from(messages.getElementsByClassName("scroller")); + for(const elm of elements){ + elm.remove(); + console.warn("rouge element detected and removed"); + } + messages.append(await this.infinite.getDiv(id)); + this.infinite.updatestuff(); + this.infinite.watchForChange().then(async _=>{ + //await new Promise(resolve => setTimeout(resolve, 0)); + this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P + loading.classList.remove("loading"); + }); + //this.infinite.focus(id.id,false); + } + private goBackIds( + id: string, + back: number, + returnifnotexistant = true + ): string | undefined{ + while(back !== 0){ + const nextid = this.idToPrev.get(id); + if(nextid){ + id = nextid; + back--; + }else{ + if(returnifnotexistant){ + break; + }else{ + return undefined; + } + } + } + return id; + } + private findClosest(id: string | undefined){ + if(!this.lastmessageid || !id)return; + let flake: string | undefined = this.lastmessageid; + const time = SnowFlake.stringToUnixTime(id); + let flaketime = SnowFlake.stringToUnixTime(flake); + while(flake && time < flaketime){ + flake = this.idToPrev.get(flake); - if (!flake) { - return; - } - flaketime = SnowFlake.stringToUnixTime(flake); - } - return flake; - } - updateChannel(json: channeljson) { - this.type = json.type; - this.name = json.name; - const parent = this.localuser.channelids.get(json.parent_id); - if (parent) { - this.parent = parent; - this.parent_id = parent.id; - } else { - this.parent = undefined; - this.parent_id = undefined; - } + if(!flake){ + return; + } + flaketime = SnowFlake.stringToUnixTime(flake); + } + return flake; + } + updateChannel(json: channeljson){ + this.type = json.type; + this.name = json.name; + const parent = this.localuser.channelids.get(json.parent_id); + if(parent){ + this.parent = parent; + this.parent_id = parent.id; + }else{ + this.parent = undefined; + this.parent_id = undefined; + } - this.children = []; - this.guild_id = json.guild_id; - this.permission_overwrites = new Map(); - for (const thing of json.permission_overwrites) { - if ( - thing.id === "1182819038095799904" || + this.children = []; + this.guild_id = json.guild_id; + this.permission_overwrites = new Map(); + for(const thing of json.permission_overwrites){ + if( + thing.id === "1182819038095799904" || thing.id === "1182820803700625444" - ) { - continue; - } - this.permission_overwrites.set( - thing.id, - new Permissions(thing.allow, thing.deny) - ); - const permisions = this.permission_overwrites.get(thing.id); - if (permisions) { - const role = this.guild.roleids.get(thing.id); - if (role) { - this.permission_overwritesar.push([role, permisions]); - } - } - } - this.topic = json.topic; - this.nsfw = json.nsfw; - } - typingstart() { - if (this.typing > Date.now()) { - return; - } - this.typing = Date.now() + 6000; - fetch(this.info.api + "/channels/" + this.id + "/typing", { - method: "POST", - headers: this.headers, - }); - } - get notification() { - let notinumber: number | null = this.message_notifications; - if (Number(notinumber) === 3) { - notinumber = null; - } - notinumber ??= this.guild.message_notifications; - switch (Number(notinumber)) { - case 0: - return "all"; - case 1: - return "mentions"; - case 2: - return "none"; - case 3: - default: - return "default"; - } - } - async sendMessage( - content: string, - { - attachments = [], - replyingto = null, - }: { attachments: Blob[]; embeds: embedjson; replyingto: Message | null } - ) { - let replyjson: any; - if (replyingto) { - replyjson = { - guild_id: replyingto.guild.id, - channel_id: replyingto.channel.id, - message_id: replyingto.id, - }; - } - if (attachments.length === 0) { - const body = { - content, - nonce: Math.floor(Math.random() * 1000000000), - message_reference: undefined, - }; - if (replyjson) { - body.message_reference = replyjson; - } - return await fetch(this.info.api + "/channels/" + this.id + "/messages", { - method: "POST", - headers: this.headers, - body: JSON.stringify(body), - }); - } else { - const formData = new FormData(); - const body = { - content, - nonce: Math.floor(Math.random() * 1000000000), - message_reference: undefined, - }; - if (replyjson) { - body.message_reference = replyjson; - } - formData.append("payload_json", JSON.stringify(body)); - for (const i in attachments) { - formData.append("files[" + i + "]", attachments[i]); - } - return await fetch(this.info.api + "/channels/" + this.id + "/messages", { - method: "POST", - body: formData, - headers: { Authorization: this.headers.Authorization }, - }); - } - } - messageCreate(messagep: messageCreateJson): void { - if (!this.hasPermission("VIEW_CHANNEL")) { - return; - } - const messagez = new Message(messagep.d, this); - this.lastmessage = messagez; - if (this.lastmessageid) { - this.idToNext.set(this.lastmessageid, messagez.id); - this.idToPrev.set(messagez.id, this.lastmessageid); - } + ){ + continue; + } + this.permission_overwrites.set( + thing.id, + new Permissions(thing.allow, thing.deny) + ); + const permisions = this.permission_overwrites.get(thing.id); + if(permisions){ + const role = this.guild.roleids.get(thing.id); + if(role){ + this.permission_overwritesar.push([role, permisions]); + } + } + } + this.topic = json.topic; + this.nsfw = json.nsfw; + } + typingstart(){ + if(this.typing > Date.now()){ + return; + } + this.typing = Date.now() + 6000; + fetch(this.info.api + "/channels/" + this.id + "/typing", { + method: "POST", + headers: this.headers, + }); + } + get notification(){ + let notinumber: number | null = this.message_notifications; + if(Number(notinumber) === 3){ + notinumber = null; + } + notinumber ??= this.guild.message_notifications; + switch(Number(notinumber)){ + case 0: + return"all"; + case 1: + return"mentions"; + case 2: + return"none"; + case 3: + default: + return"default"; + } + } + async sendMessage( + content: string, + { + attachments = [], + replyingto = null, + }: { attachments: Blob[]; embeds: embedjson; replyingto: Message | null } + ){ + let replyjson: any; + if(replyingto){ + replyjson = { + guild_id: replyingto.guild.id, + channel_id: replyingto.channel.id, + message_id: replyingto.id, + }; + } + if(attachments.length === 0){ + const body = { + content, + nonce: Math.floor(Math.random() * 1000000000), + message_reference: undefined, + }; + if(replyjson){ + body.message_reference = replyjson; + } + return await fetch(this.info.api + "/channels/" + this.id + "/messages", { + method: "POST", + headers: this.headers, + body: JSON.stringify(body), + }); + }else{ + const formData = new FormData(); + const body = { + content, + nonce: Math.floor(Math.random() * 1000000000), + message_reference: undefined, + }; + if(replyjson){ + body.message_reference = replyjson; + } + formData.append("payload_json", JSON.stringify(body)); + for(const i in attachments){ + formData.append("files[" + i + "]", attachments[i]); + } + return await fetch(this.info.api + "/channels/" + this.id + "/messages", { + method: "POST", + body: formData, + headers: { Authorization: this.headers.Authorization }, + }); + } + } + messageCreate(messagep: messageCreateJson): void{ + if(!this.hasPermission("VIEW_CHANNEL")){ + return; + } + const messagez = new Message(messagep.d, this); + this.lastmessage = messagez; + if(this.lastmessageid){ + this.idToNext.set(this.lastmessageid, messagez.id); + this.idToPrev.set(messagez.id, this.lastmessageid); + } - this.lastmessageid = messagez.id; + this.lastmessageid = messagez.id; - if (messagez.author === this.localuser.user) { - this.lastreadmessageid = messagez.id; - if (this.myhtml) { - this.myhtml.classList.remove("cunread"); - } - } else { - if (this.myhtml) { - this.myhtml.classList.add("cunread"); - } - } - this.guild.unreads(); - if (this === this.localuser.channelfocus) { - if (!this.infinitefocus) { - this.tryfocusinfinate(); - } - this.infinite.addedBottom(); - } - if (messagez.author === this.localuser.user) { - return; - } - if ( - this.localuser.lookingguild?.prevchannel === this && + if(messagez.author === this.localuser.user){ + this.lastreadmessageid = messagez.id; + if(this.myhtml){ + this.myhtml.classList.remove("cunread"); + } + }else{ + if(this.myhtml){ + this.myhtml.classList.add("cunread"); + } + } + this.guild.unreads(); + if(this === this.localuser.channelfocus){ + if(!this.infinitefocus){ + this.tryfocusinfinate(); + } + this.infinite.addedBottom(); + } + if(messagez.author === this.localuser.user){ + return; + } + if( + this.localuser.lookingguild?.prevchannel === this && document.hasFocus() - ) { - return; - } - if (this.notification === "all") { - this.notify(messagez); - } else if ( - this.notification === "mentions" && + ){ + return; + } + if(this.notification === "all"){ + this.notify(messagez); + }else if( + this.notification === "mentions" && messagez.mentionsuser(this.localuser.user) - ) { - this.notify(messagez); - } - } - notititle(message: Message): string { - return ( - message.author.username + + ){ + this.notify(messagez); + } + } + notititle(message: Message): string{ + return( + message.author.username + " > " + this.guild.properties.name + " > " + this.name - ); - } - notify(message: Message, deep = 0) { - Voice.noises(Voice.getNotificationSound()); - if (!("Notification" in window)) { - } else if (Notification.permission === "granted") { - let noticontent: string | undefined | null = message.content.textContent; - if (message.embeds[0]) { - noticontent ||= message.embeds[0]?.json.title; - noticontent ||= message.content.textContent; - } - noticontent ||= "Blank Message"; - let imgurl: null | string = null; - const images = message.getimages(); - if (images.length) { - const image = images[0]; - if (image.proxy_url) { - imgurl ||= image.proxy_url; - } - imgurl ||= image.url; - } - const notification = new Notification(this.notititle(message), { - body: noticontent, - icon: message.author.getpfpsrc(), - image: imgurl, - }); - notification.addEventListener("click", (_) => { - window.focus(); - this.getHTML(); - }); - } else if (Notification.permission !== "denied") { - Notification.requestPermission().then(() => { - if (deep === 3) { - return; - } - this.notify(message, deep + 1); - }); - } - } - async addRoleToPerms(role: Role) { - await fetch( - this.info.api + "/channels/" + this.id + "/permissions/" + role.id, - { - method: "PUT", - headers: this.headers, - body: JSON.stringify({ - allow: "0", - deny: "0", - id: role.id, - type: 0, - }), - } - ); - const perm = new Permissions("0", "0"); - this.permission_overwrites.set(role.id, perm); - this.permission_overwritesar.push([role, perm]); - } - async updateRolePermissions(id: string, perms: Permissions) { - const permission = this.permission_overwrites.get(id); - if (permission) { - permission.allow = perms.allow; - permission.deny = perms.deny; - await fetch( - this.info.api + "/channels/" + this.id + "/permissions/" + id, - { - method: "PUT", - headers: this.headers, - body: JSON.stringify({ - allow: permission.allow.toString(), - deny: permission.deny.toString(), - id, - type: 0, - }), - } - ); - } - } - } - Channel.setupcontextmenu(); - export { Channel }; + ); + } + notify(message: Message, deep = 0){ + Voice.noises(Voice.getNotificationSound()); + if(!("Notification" in window)){ + }else if(Notification.permission === "granted"){ + let noticontent: string | undefined | null = message.content.textContent; + if(message.embeds[0]){ + noticontent ||= message.embeds[0]?.json.title; + noticontent ||= message.content.textContent; + } + noticontent ||= "Blank Message"; + let imgurl: null | string = null; + const images = message.getimages(); + if(images.length){ + const image = images[0]; + if(image.proxy_url){ + imgurl ||= image.proxy_url; + } + imgurl ||= image.url; + } + const notification = new Notification(this.notititle(message), { + body: noticontent, + icon: message.author.getpfpsrc(), + image: imgurl, + }); + notification.addEventListener("click", _=>{ + window.focus(); + this.getHTML(); + }); + }else if(Notification.permission !== "denied"){ + Notification.requestPermission().then(()=>{ + if(deep === 3){ + return; + } + this.notify(message, deep + 1); + }); + } + } + async addRoleToPerms(role: Role){ + await fetch( + this.info.api + "/channels/" + this.id + "/permissions/" + role.id, + { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: "0", + deny: "0", + id: role.id, + type: 0, + }), + } + ); + const perm = new Permissions("0", "0"); + this.permission_overwrites.set(role.id, perm); + this.permission_overwritesar.push([role, perm]); + } + async updateRolePermissions(id: string, perms: Permissions){ + const permission = this.permission_overwrites.get(id); + if(permission){ + permission.allow = perms.allow; + permission.deny = perms.deny; + await fetch( + this.info.api + "/channels/" + this.id + "/permissions/" + id, + { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: permission.allow.toString(), + deny: permission.deny.toString(), + id, + type: 0, + }), + } + ); + } + } +} +Channel.setupcontextmenu(); +export{ Channel }; diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index 72c20fe..205c025 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -1,4 +1,4 @@ -class Contextmenu { +class Contextmenu{ static currentmenu: HTMLElement | ""; name: string; buttons: [ @@ -10,98 +10,98 @@ class Contextmenu { string ][]; div!: HTMLDivElement; - static setup() { - Contextmenu.currentmenu = ""; - document.addEventListener("click", (event) => { - if (Contextmenu.currentmenu === "") { - return; + static setup(){ + Contextmenu.currentmenu = ""; + document.addEventListener("click", event=>{ + if(Contextmenu.currentmenu === ""){ + return; + } + if(!Contextmenu.currentmenu.contains(event.target as Node)){ + Contextmenu.currentmenu.remove(); + Contextmenu.currentmenu = ""; + } + }); } - if (!Contextmenu.currentmenu.contains(event.target as Node)) { - Contextmenu.currentmenu.remove(); - Contextmenu.currentmenu = ""; - } - }); - } - constructor(name: string) { - this.name = name; - this.buttons = []; + constructor(name: string){ + this.name = name; + this.buttons = []; } addbutton( - text: string, - onclick: (this: x, arg: y, e: MouseEvent) => void, - img: null | string = null, - shown: (this: x, arg: y) => boolean = (_) => true, - enabled: (this: x, arg: y) => boolean = (_) => true - ) { - this.buttons.push([text, onclick, img, shown, enabled, "button"]); - return {}; + text: string, + onclick: (this: x, arg: y, e: MouseEvent) => void, + img: null | string = null, + shown: (this: x, arg: y) => boolean = _=>true, + enabled: (this: x, arg: y) => boolean = _=>true + ){ + this.buttons.push([text, onclick, img, shown, enabled, "button"]); + return{}; } addsubmenu( - text: string, - onclick: (this: x, arg: y, e: MouseEvent) => void, - img = null, - shown: (this: x, arg: y) => boolean = (_) => true, - enabled: (this: x, arg: y) => boolean = (_) => true - ) { - this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); - return {}; + text: string, + onclick: (this: x, arg: y, e: MouseEvent) => void, + img = null, + shown: (this: x, arg: y) => boolean = _=>true, + enabled: (this: x, arg: y) => boolean = _=>true + ){ + this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); + return{}; } - private makemenu(x: number, y: number, addinfo: x, other: y) { - const div = document.createElement("div"); - div.classList.add("contextmenu", "flexttb"); + private makemenu(x: number, y: number, addinfo: x, other: y){ + const div = document.createElement("div"); + div.classList.add("contextmenu", "flexttb"); - let visibleButtons = 0; - for (const thing of this.buttons) { - if (!thing[3].bind(addinfo).call(addinfo, other)) continue; - visibleButtons++; + let visibleButtons = 0; + for(const thing of this.buttons){ + if(!thing[3].bind(addinfo).call(addinfo, other))continue; + visibleButtons++; - const intext = document.createElement("button"); - intext.disabled = !thing[4].bind(addinfo).call(addinfo, other); - intext.classList.add("contextbutton"); - intext.textContent = thing[0]; - console.log(thing); - if (thing[5] === "button" || thing[5] === "submenu") { - intext.onclick = thing[1].bind(addinfo, other); - } + const intext = document.createElement("button"); + intext.disabled = !thing[4].bind(addinfo).call(addinfo, other); + intext.classList.add("contextbutton"); + intext.textContent = thing[0]; + console.log(thing); + if(thing[5] === "button" || thing[5] === "submenu"){ + intext.onclick = thing[1].bind(addinfo, other); + } - div.appendChild(intext); - } - if (visibleButtons == 0) return; + div.appendChild(intext); + } + if(visibleButtons == 0)return; - if (Contextmenu.currentmenu != "") { - Contextmenu.currentmenu.remove(); + if(Contextmenu.currentmenu != ""){ + Contextmenu.currentmenu.remove(); + } + div.style.top = y + "px"; + div.style.left = x + "px"; + document.body.appendChild(div); + Contextmenu.keepOnScreen(div); + console.log(div); + Contextmenu.currentmenu = div; + return this.div; } - div.style.top = y + "px"; - div.style.left = x + "px"; - document.body.appendChild(div); - Contextmenu.keepOnScreen(div); - console.log(div); - Contextmenu.currentmenu = div; - return this.div; + bindContextmenu(obj: HTMLElement, addinfo: x, other: y){ + const func = (event: MouseEvent)=>{ + event.preventDefault(); + event.stopImmediatePropagation(); + this.makemenu(event.clientX, event.clientY, addinfo, other); + }; + obj.addEventListener("contextmenu", func); + return func; } - bindContextmenu(obj: HTMLElement, addinfo: x, other: y) { - const func = (event: MouseEvent) => { - event.preventDefault(); - event.stopImmediatePropagation(); - this.makemenu(event.clientX, event.clientY, addinfo, other); - }; - obj.addEventListener("contextmenu", func); - return func; + static keepOnScreen(obj: HTMLElement){ + const html = document.documentElement.getBoundingClientRect(); + const docheight = html.height; + const docwidth = html.width; + const box = obj.getBoundingClientRect(); + console.log(box, docheight, docwidth); + if(box.right > docwidth){ + console.log("test"); + obj.style.left = docwidth - box.width + "px"; + } + if(box.bottom > docheight){ + obj.style.top = docheight - box.height + "px"; + } } - static keepOnScreen(obj: HTMLElement) { - const html = document.documentElement.getBoundingClientRect(); - const docheight = html.height; - const docwidth = html.width; - const box = obj.getBoundingClientRect(); - console.log(box, docheight, docwidth); - if (box.right > docwidth) { - console.log("test"); - obj.style.left = docwidth - box.width + "px"; - } - if (box.bottom > docheight) { - obj.style.top = docheight - box.height + "px"; - } - } - } - Contextmenu.setup(); - export { Contextmenu }; +} +Contextmenu.setup(); +export{ Contextmenu }; diff --git a/src/webpage/dialog.ts b/src/webpage/dialog.ts index 9dd69c1..2e08c11 100644 --- a/src/webpage/dialog.ts +++ b/src/webpage/dialog.ts @@ -19,255 +19,255 @@ string[], number ] | ["tabs", [string, dialogjson][]]; -class Dialog { -layout: dialogjson; -onclose: Function; -onopen: Function; -html: HTMLDivElement; -background!: HTMLDivElement; -constructor( -layout: dialogjson, -onclose = (_: any) => {}, -onopen = (_: any) => {} -) { -this.layout = layout; -this.onclose = onclose; -this.onopen = onopen; -const div = document.createElement("div"); -div.appendChild(this.tohtml(layout)); -this.html = div; -this.html.classList.add("centeritem"); -if (!(layout[0] === "img")) { -this.html.classList.add("nonimagecenter"); -} -} -tohtml(array: dialogjson): HTMLElement { -switch (array[0]) { -case "img": -const img = document.createElement("img"); -img.src = array[1]; -if (array[2] != undefined) { -if (array[2].length === 2) { -img.width = array[2][0]; -img.height = array[2][1]; -} else if (array[2][0] === "fit") { -img.classList.add("imgfit"); -} -} -return img; -case "hdiv": -const hdiv = document.createElement("div"); -hdiv.classList.add("flexltr"); +class Dialog{ + layout: dialogjson; + onclose: Function; + onopen: Function; + html: HTMLDivElement; + background!: HTMLDivElement; + constructor( + layout: dialogjson, + onclose = (_: any)=>{}, + onopen = (_: any)=>{} + ){ + this.layout = layout; + this.onclose = onclose; + this.onopen = onopen; + const div = document.createElement("div"); + div.appendChild(this.tohtml(layout)); + this.html = div; + this.html.classList.add("centeritem"); + if(!(layout[0] === "img")){ + this.html.classList.add("nonimagecenter"); + } + } + tohtml(array: dialogjson): HTMLElement{ + switch(array[0]){ + case"img": + const img = document.createElement("img"); + img.src = array[1]; + if(array[2] != undefined){ + if(array[2].length === 2){ + img.width = array[2][0]; + img.height = array[2][1]; + }else if(array[2][0] === "fit"){ + img.classList.add("imgfit"); + } + } + return img; + case"hdiv": + const hdiv = document.createElement("div"); + hdiv.classList.add("flexltr"); -for (const thing of array) { -if (thing === "hdiv") { -continue; -} -hdiv.appendChild(this.tohtml(thing)); -} -return hdiv; -case "vdiv": -const vdiv = document.createElement("div"); -vdiv.classList.add("flexttb"); -for (const thing of array) { -if (thing === "vdiv") { -continue; -} -vdiv.appendChild(this.tohtml(thing)); -} -return vdiv; -case "checkbox": { -const div = document.createElement("div"); -const checkbox = document.createElement("input"); -div.appendChild(checkbox); -const label = document.createElement("span"); -checkbox.checked = array[2]; -label.textContent = array[1]; -div.appendChild(label); -checkbox.addEventListener("change", array[3]); -checkbox.type = "checkbox"; -return div; -} -case "button": { -const div = document.createElement("div"); -const input = document.createElement("button"); + for(const thing of array){ + if(thing === "hdiv"){ + continue; + } + hdiv.appendChild(this.tohtml(thing)); + } + return hdiv; + case"vdiv": + const vdiv = document.createElement("div"); + vdiv.classList.add("flexttb"); + for(const thing of array){ + if(thing === "vdiv"){ + continue; + } + vdiv.appendChild(this.tohtml(thing)); + } + return vdiv; + case"checkbox": { + const div = document.createElement("div"); + const checkbox = document.createElement("input"); + div.appendChild(checkbox); + const label = document.createElement("span"); + checkbox.checked = array[2]; + label.textContent = array[1]; + div.appendChild(label); + checkbox.addEventListener("change", array[3]); + checkbox.type = "checkbox"; + return div; + } + case"button": { + const div = document.createElement("div"); + const input = document.createElement("button"); -const label = document.createElement("span"); -input.textContent = array[2]; -label.textContent = array[1]; -div.appendChild(label); -div.appendChild(input); -input.addEventListener("click", array[3]); -return div; -} -case "mdbox": { -const div = document.createElement("div"); -const input = document.createElement("textarea"); -input.value = array[2]; -const label = document.createElement("span"); -label.textContent = array[1]; -input.addEventListener("input", array[3]); -div.appendChild(label); -div.appendChild(document.createElement("br")); -div.appendChild(input); -return div; -} -case "textbox": { -const div = document.createElement("div"); -const input = document.createElement("input"); -input.value = array[2]; -input.type = "text"; -const label = document.createElement("span"); -label.textContent = array[1]; -console.log(array[3]); -input.addEventListener("input", array[3]); -div.appendChild(label); -div.appendChild(input); -return div; -} -case "fileupload": { -const div = document.createElement("div"); -const input = document.createElement("input"); -input.type = "file"; -const label = document.createElement("span"); -label.textContent = array[1]; -div.appendChild(label); -div.appendChild(input); -input.addEventListener("change", array[2]); -console.log(array); -return div; -} -case "text": { -const span = document.createElement("span"); -span.textContent = array[1]; -return span; -} -case "title": { -const span = document.createElement("span"); -span.classList.add("title"); -span.textContent = array[1]; -return span; -} -case "radio": { -const div = document.createElement("div"); -const fieldset = document.createElement("fieldset"); -fieldset.addEventListener("change", () => { -let i = -1; -for (const thing of Array.from(fieldset.children)) { -i++; -if (i === 0) { -continue; -} -const checkbox = thing.children[0].children[0] as HTMLInputElement; -if (checkbox.checked) { -array[3](checkbox.value); -} -} -}); -const legend = document.createElement("legend"); -legend.textContent = array[1]; -fieldset.appendChild(legend); -let i = 0; -for (const thing of array[2]) { -const div = document.createElement("div"); -const input = document.createElement("input"); -input.classList.add("radio"); -input.type = "radio"; -input.name = array[1]; -input.value = thing; -if (i === array[4]) { -input.checked = true; -} -const label = document.createElement("label"); + const label = document.createElement("span"); + input.textContent = array[2]; + label.textContent = array[1]; + div.appendChild(label); + div.appendChild(input); + input.addEventListener("click", array[3]); + return div; + } + case"mdbox": { + const div = document.createElement("div"); + const input = document.createElement("textarea"); + input.value = array[2]; + const label = document.createElement("span"); + label.textContent = array[1]; + input.addEventListener("input", array[3]); + div.appendChild(label); + div.appendChild(document.createElement("br")); + div.appendChild(input); + return div; + } + case"textbox": { + const div = document.createElement("div"); + const input = document.createElement("input"); + input.value = array[2]; + input.type = "text"; + const label = document.createElement("span"); + label.textContent = array[1]; + console.log(array[3]); + input.addEventListener("input", array[3]); + div.appendChild(label); + div.appendChild(input); + return div; + } + case"fileupload": { + const div = document.createElement("div"); + const input = document.createElement("input"); + input.type = "file"; + const label = document.createElement("span"); + label.textContent = array[1]; + div.appendChild(label); + div.appendChild(input); + input.addEventListener("change", array[2]); + console.log(array); + return div; + } + case"text": { + const span = document.createElement("span"); + span.textContent = array[1]; + return span; + } + case"title": { + const span = document.createElement("span"); + span.classList.add("title"); + span.textContent = array[1]; + return span; + } + case"radio": { + const div = document.createElement("div"); + const fieldset = document.createElement("fieldset"); + fieldset.addEventListener("change", ()=>{ + let i = -1; + for(const thing of Array.from(fieldset.children)){ + i++; + if(i === 0){ + continue; + } + const checkbox = thing.children[0].children[0] as HTMLInputElement; + if(checkbox.checked){ + array[3](checkbox.value); + } + } + }); + const legend = document.createElement("legend"); + legend.textContent = array[1]; + fieldset.appendChild(legend); + let i = 0; + for(const thing of array[2]){ + const div = document.createElement("div"); + const input = document.createElement("input"); + input.classList.add("radio"); + input.type = "radio"; + input.name = array[1]; + input.value = thing; + if(i === array[4]){ + input.checked = true; + } + const label = document.createElement("label"); -label.appendChild(input); -const span = document.createElement("span"); -span.textContent = thing; -label.appendChild(span); -div.appendChild(label); -fieldset.appendChild(div); -i++; -} -div.appendChild(fieldset); -return div; -} -case "html": -return array[1]; + label.appendChild(input); + const span = document.createElement("span"); + span.textContent = thing; + label.appendChild(span); + div.appendChild(label); + fieldset.appendChild(div); + i++; + } + div.appendChild(fieldset); + return div; + } + case"html": + return array[1]; -case "select": { -const div = document.createElement("div"); -const label = document.createElement("label"); -const select = document.createElement("select"); + case"select": { + const div = document.createElement("div"); + const label = document.createElement("label"); + const select = document.createElement("select"); -label.textContent = array[1]; -div.append(label); -div.appendChild(select); -for (const thing of array[2]) { -const option = document.createElement("option"); -option.textContent = thing; -select.appendChild(option); -} -select.selectedIndex = array[4]; -select.addEventListener("change", array[3]); -return div; -} -case "tabs": { -const table = document.createElement("div"); -table.classList.add("flexttb"); -const tabs = document.createElement("div"); -tabs.classList.add("flexltr"); -tabs.classList.add("tabbed-head"); -table.appendChild(tabs); -const content = document.createElement("div"); -content.classList.add("tabbed-content"); -table.appendChild(content); + label.textContent = array[1]; + div.append(label); + div.appendChild(select); + for(const thing of array[2]){ + const option = document.createElement("option"); + option.textContent = thing; + select.appendChild(option); + } + select.selectedIndex = array[4]; + select.addEventListener("change", array[3]); + return div; + } + case"tabs": { + const table = document.createElement("div"); + table.classList.add("flexttb"); + const tabs = document.createElement("div"); + tabs.classList.add("flexltr"); + tabs.classList.add("tabbed-head"); + table.appendChild(tabs); + const content = document.createElement("div"); + content.classList.add("tabbed-content"); + table.appendChild(content); -let shown: HTMLElement | undefined; -for (const thing of array[1]) { -const button = document.createElement("button"); -button.textContent = thing[0]; -tabs.appendChild(button); + let shown: HTMLElement | undefined; + for(const thing of array[1]){ + const button = document.createElement("button"); + button.textContent = thing[0]; + tabs.appendChild(button); -const html = this.tohtml(thing[1]); -content.append(html); -if (!shown) { -shown = html; -} else { -html.style.display = "none"; + const html = this.tohtml(thing[1]); + content.append(html); + if(!shown){ + shown = html; + }else{ + html.style.display = "none"; + } + button.addEventListener("click", _=>{ + if(shown){ + shown.style.display = "none"; + } + html.style.display = ""; + shown = html; + }); + } + return table; + } + default: + console.error( + "can't find element:" + array[0], + " full element:", + array + ); + return document.createElement("span"); + } + } + show(){ + this.onopen(); + console.log("fullscreen"); + this.background = document.createElement("div"); + this.background.classList.add("background"); + document.body.appendChild(this.background); + document.body.appendChild(this.html); + this.background.onclick = _=>{ + this.hide(); + }; + } + hide(){ + document.body.removeChild(this.background); + document.body.removeChild(this.html); + } } -button.addEventListener("click", (_) => { -if (shown) { -shown.style.display = "none"; -} -html.style.display = ""; -shown = html; -}); -} -return table; -} -default: -console.error( -"can't find element:" + array[0], -" full element:", -array -); -return document.createElement("span"); -} -} -show() { -this.onopen(); -console.log("fullscreen"); -this.background = document.createElement("div"); -this.background.classList.add("background"); -document.body.appendChild(this.background); -document.body.appendChild(this.html); -this.background.onclick = (_) => { -this.hide(); -}; -} -hide() { -document.body.removeChild(this.background); -document.body.removeChild(this.html); -} -} -export { Dialog }; +export{ Dialog }; diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 3ac7313..189e935 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -1,78 +1,78 @@ -import { Guild } from "./guild.js"; -import { Channel } from "./channel.js"; -import { Message } from "./message.js"; -import { Localuser } from "./localuser.js"; -import { User } from "./user.js"; -import { -channeljson, -dirrectjson, -memberjson, -messagejson, -} from "./jsontypes.js"; -import { Permissions } from "./permissions.js"; -import { SnowFlake } from "./snowflake.js"; -import { Contextmenu } from "./contextmenu.js"; +import{ Guild }from"./guild.js"; +import{ Channel }from"./channel.js"; +import{ Message }from"./message.js"; +import{ Localuser }from"./localuser.js"; +import{ User }from"./user.js"; +import{ + channeljson, + dirrectjson, + memberjson, + messagejson, +}from"./jsontypes.js"; +import{ Permissions }from"./permissions.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ Contextmenu }from"./contextmenu.js"; -class Direct extends Guild { -declare channelids: { [key: string]: Group }; -getUnixTime(): number { -throw new Error("Do not call this for Direct, it does not make sense"); -} -constructor(json: dirrectjson[], owner: Localuser) { -super(-1, owner, null); -this.message_notifications = 0; -this.owner = owner; -if (!this.localuser) { -console.error("Owner was not included, please fix"); -} -this.headers = this.localuser.headers; -this.channels = []; -this.channelids = {}; -// @ts-ignore -this.properties = {}; -this.roles = []; -this.roleids = new Map(); -this.prevchannel = undefined; -this.properties.name = "Direct Messages"; -for (const thing of json) { -const temp = new Group(thing, this); -this.channels.push(temp); -this.channelids[temp.id] = temp; -} -this.headchannels = this.channels; -} -createChannelpac(json: any) { -const thischannel = new Group(json, this); -this.channelids[thischannel.id] = thischannel; -this.channels.push(thischannel); -this.sortchannels(); -this.printServers(); -return thischannel; -} -delChannel(json: channeljson) { -const channel = this.channelids[json.id]; -super.delChannel(json); -if (channel) { -channel.del(); -} -} -giveMember(_member: memberjson) { -console.error("not a real guild, can't give member object"); -} -getRole(/* ID: string */) { -return null; -} -hasRole(/* r: string */) { -return false; -} -isAdmin() { -return false; -} -unreaddms() { -for (const thing of this.channels) { -(thing as Group).unreads(); -} -} +class Direct extends Guild{ + declare channelids: { [key: string]: Group }; + getUnixTime(): number{ + throw new Error("Do not call this for Direct, it does not make sense"); + } + constructor(json: dirrectjson[], owner: Localuser){ + super(-1, owner, null); + this.message_notifications = 0; + this.owner = owner; + if(!this.localuser){ + console.error("Owner was not included, please fix"); + } + this.headers = this.localuser.headers; + this.channels = []; + this.channelids = {}; + // @ts-ignore + this.properties = {}; + this.roles = []; + this.roleids = new Map(); + this.prevchannel = undefined; + this.properties.name = "Direct Messages"; + for(const thing of json){ + const temp = new Group(thing, this); + this.channels.push(temp); + this.channelids[temp.id] = temp; + } + this.headchannels = this.channels; + } + createChannelpac(json: any){ + const thischannel = new Group(json, this); + this.channelids[thischannel.id] = thischannel; + this.channels.push(thischannel); + this.sortchannels(); + this.printServers(); + return thischannel; + } + delChannel(json: channeljson){ + const channel = this.channelids[json.id]; + super.delChannel(json); + if(channel){ + channel.del(); + } + } + giveMember(_member: memberjson){ + console.error("not a real guild, can't give member object"); + } + getRole(/* ID: string */){ + return null; + } + hasRole(/* r: string */){ + return false; + } + isAdmin(){ + return false; + } + unreaddms(){ + for(const thing of this.channels){ + (thing as Group).unreads(); + } + } } const dmPermissions = new Permissions("0"); @@ -99,37 +99,37 @@ dmPermissions.setPermission("STREAM", 1); dmPermissions.setPermission("USE_VAD", 1); // @ts-ignore -class Group extends Channel { -user: User; -static contextmenu = new Contextmenu("channel menu"); - static setupcontextmenu() { - this.contextmenu.addbutton("Copy DM id", function (this: Group) { - navigator.clipboard.writeText(this.id); - }); +class Group extends Channel{ + user: User; + static contextmenu = new Contextmenu("channel menu"); + static setupcontextmenu(){ + this.contextmenu.addbutton("Copy DM id", function(this: Group){ + navigator.clipboard.writeText(this.id); + }); - this.contextmenu.addbutton("Mark as read", function (this: Group) { - this.readbottom(); - }); + this.contextmenu.addbutton("Mark as read", function(this: Group){ + this.readbottom(); + }); - this.contextmenu.addbutton("Close DM", function (this: Group) { - this.deleteChannel(); - }); + this.contextmenu.addbutton("Close DM", function(this: Group){ + this.deleteChannel(); + }); - this.contextmenu.addbutton("Copy user ID", function () { - navigator.clipboard.writeText(this.user.id); - }); + this.contextmenu.addbutton("Copy user ID", function(){ + navigator.clipboard.writeText(this.user.id); + }); } - constructor(json: dirrectjson, owner: Direct) { - super(-1, owner, json.id); - this.owner = owner; - this.headers = this.guild.headers; - this.name = json.recipients[0]?.username; - if (json.recipients[0]) { - this.user = new User(json.recipients[0], this.localuser); - } else { - this.user = this.localuser.user; - } - this.name ??= this.localuser.user.username; + constructor(json: dirrectjson, owner: Direct){ + super(-1, owner, json.id); + this.owner = owner; + this.headers = this.guild.headers; + this.name = json.recipients[0]?.username; + if(json.recipients[0]){ + this.user = new User(json.recipients[0], this.localuser); + }else{ + this.user = this.localuser.user; + } + this.name ??= this.localuser.user.username; this.parent_id!; this.parent!; this.children = []; @@ -140,139 +140,139 @@ static contextmenu = new Contextmenu("channel menu"); this.setUpInfiniteScroller(); this.updatePosition(); } - updatePosition() { - if (this.lastmessageid) { - this.position = SnowFlake.stringToUnixTime(this.lastmessageid); - } else { - this.position = 0; + updatePosition(){ + if(this.lastmessageid){ + this.position = SnowFlake.stringToUnixTime(this.lastmessageid); + }else{ + this.position = 0; + } + this.position = -Math.max(this.position, this.getUnixTime()); } - this.position = -Math.max(this.position, this.getUnixTime()); - } - createguildHTML() { - const div = document.createElement("div"); - Group.contextmenu.bindContextmenu(div, this, undefined); - this.html = new WeakRef(div); - div.classList.add("channeleffects"); - const myhtml = document.createElement("span"); - myhtml.textContent = this.name; - div.appendChild(this.user.buildpfp()); - div.appendChild(myhtml); - (div as any)["myinfo"] = this; - div.onclick = (_) => { - this.getHTML(); - }; + createguildHTML(){ + const div = document.createElement("div"); + Group.contextmenu.bindContextmenu(div, this); + this.html = new WeakRef(div); + div.classList.add("channeleffects"); + const myhtml = document.createElement("span"); + myhtml.textContent = this.name; + div.appendChild(this.user.buildpfp()); + div.appendChild(myhtml); + (div as any).myinfo = this; + div.onclick = _=>{ + this.getHTML(); + }; - return div; + return div; } - async getHTML() { - const id = ++Channel.genid; - if (this.localuser.channelfocus) { - this.localuser.channelfocus.infinite.delete(); - } - if (this.guild !== this.localuser.lookingguild) { - this.guild.loadGuild(); - } - this.guild.prevchannel = this; - this.localuser.channelfocus = this; - const prom = this.infinite.delete(); - history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); - this.localuser.pageTitle("@" + this.name); - (document.getElementById("channelTopic") as HTMLElement).setAttribute( - "hidden", - "" - ); + async getHTML(){ + const id = ++Channel.genid; + if(this.localuser.channelfocus){ + this.localuser.channelfocus.infinite.delete(); + } + if(this.guild !== this.localuser.lookingguild){ + this.guild.loadGuild(); + } + this.guild.prevchannel = this; + this.localuser.channelfocus = this; + const prom = this.infinite.delete(); + history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); + this.localuser.pageTitle("@" + this.name); + (document.getElementById("channelTopic") as HTMLElement).setAttribute( + "hidden", + "" + ); - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - Channel.regenLoadingMessages(); - loading.classList.add("loading"); - this.rendertyping(); - await this.putmessages(); - await prom; - if (id !== Channel.genid) { - return; - } - this.buildmessages(); - (document.getElementById("typebox") as HTMLDivElement).contentEditable = + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + Channel.regenLoadingMessages(); + loading.classList.add("loading"); + this.rendertyping(); + await this.putmessages(); + await prom; + if(id !== Channel.genid){ + return; + } + this.buildmessages(); + (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + true; } - messageCreate(messagep: { d: messagejson }) { - const messagez = new Message(messagep.d, this); - if (this.lastmessageid) { - this.idToNext.set(this.lastmessageid, messagez.id); - this.idToPrev.set(messagez.id, this.lastmessageid); - } - this.lastmessageid = messagez.id; - if (messagez.author === this.localuser.user) { - this.lastreadmessageid = messagez.id; - if (this.myhtml) { - this.myhtml.classList.remove("cunread"); - } - } else { - if (this.myhtml) { - this.myhtml.classList.add("cunread"); - } - } - this.unreads(); - this.updatePosition(); - this.infinite.addedBottom(); - this.guild.sortchannels(); - if (this.myhtml) { - const parrent = this.myhtml.parentElement as HTMLElement; - parrent.prepend(this.myhtml); - } - if (this === this.localuser.channelfocus) { - if (!this.infinitefocus) { - this.tryfocusinfinate(); - } - this.infinite.addedBottom(); - } - this.unreads(); - if (messagez.author === this.localuser.user) { - return; - } - if ( - this.localuser.lookingguild?.prevchannel === this && + messageCreate(messagep: { d: messagejson }){ + const messagez = new Message(messagep.d, this); + if(this.lastmessageid){ + this.idToNext.set(this.lastmessageid, messagez.id); + this.idToPrev.set(messagez.id, this.lastmessageid); + } + this.lastmessageid = messagez.id; + if(messagez.author === this.localuser.user){ + this.lastreadmessageid = messagez.id; + if(this.myhtml){ + this.myhtml.classList.remove("cunread"); + } + }else{ + if(this.myhtml){ + this.myhtml.classList.add("cunread"); + } + } + this.unreads(); + this.updatePosition(); + this.infinite.addedBottom(); + this.guild.sortchannels(); + if(this.myhtml){ + const parrent = this.myhtml.parentElement as HTMLElement; + parrent.prepend(this.myhtml); + } + if(this === this.localuser.channelfocus){ + if(!this.infinitefocus){ + this.tryfocusinfinate(); + } + this.infinite.addedBottom(); + } + this.unreads(); + if(messagez.author === this.localuser.user){ + return; + } + if( + this.localuser.lookingguild?.prevchannel === this && document.hasFocus() - ) { - return; - } - if (this.notification === "all") { - this.notify(messagez); - } else if ( - this.notification === "mentions" && + ){ + return; + } + if(this.notification === "all"){ + this.notify(messagez); + }else if( + this.notification === "mentions" && messagez.mentionsuser(this.localuser.user) - ) { - this.notify(messagez); + ){ + this.notify(messagez); + } } + notititle(message: Message){ + return message.author.username; } - notititle(message: Message) { - return message.author.username; - } - readbottom() { - super.readbottom(); - this.unreads(); + readbottom(){ + super.readbottom(); + this.unreads(); } all: WeakRef = new WeakRef(document.createElement("div")); - noti: WeakRef = new WeakRef(document.createElement("div")); - del() { - const all = this.all.deref(); - if (all) { + noti: WeakRef = new WeakRef(document.createElement("div")); + del(){ + const all = this.all.deref(); + if(all){ all.remove(); - } - if (this.myhtml) { + } + if(this.myhtml){ this.myhtml.remove(); - } - } - unreads() { - const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime - const current = this.all.deref(); - if (this.hasunreads) { + } + } + unreads(){ + const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime + const current = this.all.deref(); + if(this.hasunreads){ { - const noti = this.noti.deref(); - if (noti) { - noti.textContent = this.mentions + ""; - return; - } + const noti = this.noti.deref(); + if(noti){ + noti.textContent = this.mentions + ""; + return; + } } const div = document.createElement("div"); div.classList.add("servernoti"); @@ -286,21 +286,21 @@ static contextmenu = new Contextmenu("channel menu"); buildpfp.classList.add("mentioned"); div.append(buildpfp); sentdms.append(div); - div.onclick = (_) => { - this.guild.loadGuild(); - this.getHTML(); + div.onclick = _=>{ + this.guild.loadGuild(); + this.getHTML(); }; - } else if (current) { + }else if(current){ current.remove(); - } else { - } - } - isAdmin(): boolean { - return false; - } - hasPermission(name: string): boolean { - return dmPermissions.hasPermission(name); - } - } - export { Direct, Group }; - Group.setupcontextmenu(); + }else{ + } + } + isAdmin(): boolean{ + return false; + } + hasPermission(name: string): boolean{ + return dmPermissions.hasPermission(name); + } +} +export{ Direct, Group }; +Group.setupcontextmenu(); diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index fcf8feb..d915cd1 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -1,411 +1,409 @@ -import { Dialog } from "./dialog.js"; -import { Message } from "./message.js"; -import { MarkDown } from "./markdown.js"; -import { embedjson, invitejson } from "./jsontypes.js"; -import { getapiurls, getInstances } from "./login.js"; -import { Guild } from "./guild.js"; +import{ Dialog }from"./dialog.js"; +import{ Message }from"./message.js"; +import{ MarkDown }from"./markdown.js"; +import{ embedjson, invitejson }from"./jsontypes.js"; +import{ getapiurls, getInstances }from"./login.js"; +import{ Guild }from"./guild.js"; -class Embed { -type: string; -owner: Message; -json: embedjson; -constructor(json: embedjson, owner: Message) { -this.type = this.getType(json); -this.owner = owner; -this.json = json; -} -getType(json: embedjson) { -const instances = getInstances(); -if ( -instances && +class Embed{ + type: string; + owner: Message; + json: embedjson; + constructor(json: embedjson, owner: Message){ + this.type = this.getType(json); + this.owner = owner; + this.json = json; + } + getType(json: embedjson){ + const instances = getInstances(); + if( + instances && json.type === "link" && json.url && URL.canParse(json.url) -) { -const Url = new URL(json.url); -for (const instance of instances) { -if (instance.url && URL.canParse(instance.url)) { -const IUrl = new URL(instance.url); -const params = new URLSearchParams(Url.search); -let host: string; -if (params.has("instance")) { -const url = params.get("instance") as string; -if (URL.canParse(url)) { -host = new URL(url).host; -} else { -host = Url.host; -} -} else { -host = Url.host; -} -if (IUrl.host === host) { -const code = + ){ + const Url = new URL(json.url); + for(const instance of instances){ + if(instance.url && URL.canParse(instance.url)){ + const IUrl = new URL(instance.url); + const params = new URLSearchParams(Url.search); + let host: string; + if(params.has("instance")){ + const url = params.get("instance") as string; + if(URL.canParse(url)){ + host = new URL(url).host; + }else{ + host = Url.host; + } + }else{ + host = Url.host; + } + if(IUrl.host === host){ + const code = Url.pathname.split("/")[Url.pathname.split("/").length - 1]; -json.invite = { -url: instance.url, -code, -}; -return "invite"; -} -} -} -} -return json.type || "rich"; -} -generateHTML() { -switch (this.type) { -case "rich": -return this.generateRich(); -case "image": -return this.generateImage(); -case "invite": -return this.generateInvite(); -case "link": -return this.generateLink(); -case "video": -case "article": -return this.generateArticle(); -default: -console.warn( -`unsupported embed type ${this.type}, please add support dev :3`, -this.json -); -return document.createElement("div"); //prevent errors by giving blank div -} -} -get message() { -return this.owner; -} -get channel() { -return this.message.channel; -} -get guild() { -return this.channel.guild; -} -get localuser() { -return this.guild.localuser; -} -generateRich() { -const div = document.createElement("div"); -if (this.json.color) { -div.style.backgroundColor = "#" + this.json.color.toString(16); -} -div.classList.add("embed-color"); + json.invite = { + url: instance.url, + code, + }; + return"invite"; + } + } + } + } + return json.type || "rich"; + } + generateHTML(){ + switch(this.type){ + case"rich": + return this.generateRich(); + case"image": + return this.generateImage(); + case"invite": + return this.generateInvite(); + case"link": + return this.generateLink(); + case"video": + case"article": + return this.generateArticle(); + default: + console.warn( + `unsupported embed type ${this.type}, please add support dev :3`, + this.json + ); + return document.createElement("div"); //prevent errors by giving blank div + } + } + get message(){ + return this.owner; + } + get channel(){ + return this.message.channel; + } + get guild(){ + return this.channel.guild; + } + get localuser(){ + return this.guild.localuser; + } + generateRich(){ + const div = document.createElement("div"); + if(this.json.color){ + div.style.backgroundColor = "#" + this.json.color.toString(16); + } + div.classList.add("embed-color"); -const embed = document.createElement("div"); -embed.classList.add("embed"); -div.append(embed); + const embed = document.createElement("div"); + embed.classList.add("embed"); + div.append(embed); -if (this.json.author) { -const authorline = document.createElement("div"); -if (this.json.author.icon_url) { -const img = document.createElement("img"); -img.classList.add("embedimg"); -img.src = this.json.author.icon_url; -authorline.append(img); -} -const a = document.createElement("a"); -a.textContent = this.json.author.name as string; -if (this.json.author.url) { -MarkDown.safeLink(a, this.json.author.url); -} -a.classList.add("username"); -authorline.append(a); -embed.append(authorline); -} -if (this.json.title) { -const title = document.createElement("a"); -title.append(new MarkDown(this.json.title, this.channel).makeHTML()); -if (this.json.url) { -MarkDown.safeLink(title, this.json.url); -} -title.classList.add("embedtitle"); -embed.append(title); -} -if (this.json.description) { -const p = document.createElement("p"); -p.append(new MarkDown(this.json.description, this.channel).makeHTML()); -embed.append(p); -} + if(this.json.author){ + const authorline = document.createElement("div"); + if(this.json.author.icon_url){ + const img = document.createElement("img"); + img.classList.add("embedimg"); + img.src = this.json.author.icon_url; + authorline.append(img); + } + const a = document.createElement("a"); + a.textContent = this.json.author.name as string; + if(this.json.author.url){ + MarkDown.safeLink(a, this.json.author.url); + } + a.classList.add("username"); + authorline.append(a); + embed.append(authorline); + } + if(this.json.title){ + const title = document.createElement("a"); + title.append(new MarkDown(this.json.title, this.channel).makeHTML()); + if(this.json.url){ + MarkDown.safeLink(title, this.json.url); + } + title.classList.add("embedtitle"); + embed.append(title); + } + if(this.json.description){ + const p = document.createElement("p"); + p.append(new MarkDown(this.json.description, this.channel).makeHTML()); + embed.append(p); + } -embed.append(document.createElement("br")); -if (this.json.fields) { -for (const thing of this.json.fields) { -const div = document.createElement("div"); -const b = document.createElement("b"); -b.textContent = thing.name; -div.append(b); -const p = document.createElement("p"); -p.append(new MarkDown(thing.value, this.channel).makeHTML()); -p.classList.add("embedp"); -div.append(p); + embed.append(document.createElement("br")); + if(this.json.fields){ + for(const thing of this.json.fields){ + const div = document.createElement("div"); + const b = document.createElement("b"); + b.textContent = thing.name; + div.append(b); + const p = document.createElement("p"); + p.append(new MarkDown(thing.value, this.channel).makeHTML()); + p.classList.add("embedp"); + div.append(p); -if (thing.inline) { -div.classList.add("inline"); -} -embed.append(div); -} -} -if (this.json.footer || this.json.timestamp) { -const footer = document.createElement("div"); -if (this.json?.footer?.icon_url) { -const img = document.createElement("img"); -img.src = this.json.footer.icon_url; -img.classList.add("embedicon"); -footer.append(img); -} -if (this.json?.footer?.text) { -const span = document.createElement("span"); -span.textContent = this.json.footer.text; -span.classList.add("spaceright"); -footer.append(span); -} -if (this.json?.footer && this.json?.timestamp) { -const span = document.createElement("span"); -span.textContent = "•"; -span.classList.add("spaceright"); -footer.append(span); -} -if (this.json?.timestamp) { -const span = document.createElement("span"); -span.textContent = new Date(this.json.timestamp).toLocaleString(); -footer.append(span); -} -embed.append(footer); -} -return div; -} -generateImage() { -const img = document.createElement("img"); -img.classList.add("messageimg"); -img.onclick = function () { -const full = new Dialog(["img", img.src, ["fit"]]); -full.show(); -}; -img.src = this.json.thumbnail.proxy_url; -if (this.json.thumbnail.width) { -let scale = 1; -const max = 96 * 3; -scale = Math.max(scale, this.json.thumbnail.width / max); -scale = Math.max(scale, this.json.thumbnail.height / max); -this.json.thumbnail.width /= scale; -this.json.thumbnail.height /= scale; -} -img.style.width = this.json.thumbnail.width + "px"; -img.style.height = this.json.thumbnail.height + "px"; -console.log(this.json, "Image fix"); -return img; -} -generateLink() { -const table = document.createElement("table"); -table.classList.add("embed", "linkembed"); -const trtop = document.createElement("tr"); -table.append(trtop); -if (this.json.url && this.json.title) { -const td = document.createElement("td"); -const a = document.createElement("a"); -MarkDown.safeLink(a, this.json.url); -a.textContent = this.json.title; -td.append(a); -trtop.append(td); -} -{ -const td = document.createElement("td"); -const img = document.createElement("img"); -if (this.json.thumbnail) { -img.classList.add("embedimg"); -img.onclick = function () { -const full = new Dialog(["img", img.src, ["fit"]]); -full.show(); -}; -img.src = this.json.thumbnail.proxy_url; -td.append(img); -} -trtop.append(td); -} -const bottomtr = document.createElement("tr"); -const td = document.createElement("td"); -if (this.json.description) { -const span = document.createElement("span"); -span.textContent = this.json.description; -td.append(span); -} -bottomtr.append(td); -table.append(bottomtr); -return table; -} -invcache: [invitejson, { cdn: string; api: string }] | undefined; -generateInvite() { -if (this.invcache && (!this.json.invite || !this.localuser)) { -return this.generateLink(); -} -const div = document.createElement("div"); -div.classList.add("embed", "inviteEmbed", "flexttb"); -const json1 = this.json.invite; -(async () => { -let json: invitejson; -let info: { cdn: string; api: string }; -if (!this.invcache) { -if (!json1) { -div.append(this.generateLink()); -return; -} -const tempinfo = await getapiurls(json1.url); + if(thing.inline){ + div.classList.add("inline"); + } + embed.append(div); + } + } + if(this.json.footer || this.json.timestamp){ + const footer = document.createElement("div"); + if(this.json?.footer?.icon_url){ + const img = document.createElement("img"); + img.src = this.json.footer.icon_url; + img.classList.add("embedicon"); + footer.append(img); + } + if(this.json?.footer?.text){ + const span = document.createElement("span"); + span.textContent = this.json.footer.text; + span.classList.add("spaceright"); + footer.append(span); + } + if(this.json?.footer && this.json?.timestamp){ + const span = document.createElement("span"); + span.textContent = "•"; + span.classList.add("spaceright"); + footer.append(span); + } + if(this.json?.timestamp){ + const span = document.createElement("span"); + span.textContent = new Date(this.json.timestamp).toLocaleString(); + footer.append(span); + } + embed.append(footer); + } + return div; + } + generateImage(){ + const img = document.createElement("img"); + img.classList.add("messageimg"); + img.onclick = function(){ + const full = new Dialog(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = this.json.thumbnail.proxy_url; + if(this.json.thumbnail.width){ + let scale = 1; + const max = 96 * 3; + scale = Math.max(scale, this.json.thumbnail.width / max); + scale = Math.max(scale, this.json.thumbnail.height / max); + this.json.thumbnail.width /= scale; + this.json.thumbnail.height /= scale; + } + img.style.width = this.json.thumbnail.width + "px"; + img.style.height = this.json.thumbnail.height + "px"; + console.log(this.json, "Image fix"); + return img; + } + generateLink(){ + const table = document.createElement("table"); + table.classList.add("embed", "linkembed"); + const trtop = document.createElement("tr"); + table.append(trtop); + if(this.json.url && this.json.title){ + const td = document.createElement("td"); + const a = document.createElement("a"); + MarkDown.safeLink(a, this.json.url); + a.textContent = this.json.title; + td.append(a); + trtop.append(td); + } + { + const td = document.createElement("td"); + const img = document.createElement("img"); + if(this.json.thumbnail){ + img.classList.add("embedimg"); + img.onclick = function(){ + const full = new Dialog(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = this.json.thumbnail.proxy_url; + td.append(img); + } + trtop.append(td); + } + const bottomtr = document.createElement("tr"); + const td = document.createElement("td"); + if(this.json.description){ + const span = document.createElement("span"); + span.textContent = this.json.description; + td.append(span); + } + bottomtr.append(td); + table.append(bottomtr); + return table; + } + invcache: [invitejson, { cdn: string; api: string }] | undefined; + generateInvite(){ + if(this.invcache && (!this.json.invite || !this.localuser)){ + return this.generateLink(); + } + const div = document.createElement("div"); + div.classList.add("embed", "inviteEmbed", "flexttb"); + const json1 = this.json.invite; + (async ()=>{ + let json: invitejson; + let info: { cdn: string; api: string }; + if(!this.invcache){ + if(!json1){ + div.append(this.generateLink()); + return; + } + const tempinfo = await getapiurls(json1.url); -if (!tempinfo) { -div.append(this.generateLink()); -return; -} -info = tempinfo; -const res = await fetch(info.api + "/invites/" + json1.code); -if (!res.ok) { -div.append(this.generateLink()); -} -json = (await res.json()) as invitejson; -this.invcache = [json, info]; -} else { -[json, info] = this.invcache; -} -if (!json) { -div.append(this.generateLink()); -return; -} -if (json.guild.banner) { -const banner = document.createElement("img"); -banner.src = + if(!tempinfo){ + div.append(this.generateLink()); + return; + } + info = tempinfo; + const res = await fetch(info.api + "/invites/" + json1.code); + if(!res.ok){ + div.append(this.generateLink()); + } + json = (await res.json()) as invitejson; + this.invcache = [json, info]; + }else{ + [json, info] = this.invcache; + } + if(!json){ + div.append(this.generateLink()); + return; + } + if(json.guild.banner){ + const banner = document.createElement("img"); + banner.src = this.localuser.info.cdn + "/icons/" + json.guild.id + "/" + json.guild.banner + ".png?size=256"; -banner.classList.add("banner"); -div.append(banner); -} -const guild: invitejson["guild"] & { info?: { cdn: string } } = + banner.classList.add("banner"); + div.append(banner); + } + const guild: invitejson["guild"] & { info?: { cdn: string } } = json.guild; -guild.info = info; -const icon = Guild.generateGuildIcon( + guild.info = info; + const icon = Guild.generateGuildIcon( guild as invitejson["guild"] & { info: { cdn: string } } -); -const iconrow = document.createElement("div"); -iconrow.classList.add("flexltr", "flexstart"); -iconrow.append(icon); -{ -const guildinfo = document.createElement("div"); -guildinfo.classList.add("flexttb", "invguildinfo"); -const name = document.createElement("b"); -name.textContent = guild.name; -guildinfo.append(name); + ); + const iconrow = document.createElement("div"); + iconrow.classList.add("flexltr", "flexstart"); + iconrow.append(icon); + { + const guildinfo = document.createElement("div"); + guildinfo.classList.add("flexttb", "invguildinfo"); + const name = document.createElement("b"); + name.textContent = guild.name; + guildinfo.append(name); -const members = document.createElement("span"); -members.innerText = + const members = document.createElement("span"); + members.innerText = "#" + json.channel.name + " • Members: " + guild.member_count; -guildinfo.append(members); -members.classList.add("subtext"); -iconrow.append(guildinfo); -} + guildinfo.append(members); + members.classList.add("subtext"); + iconrow.append(guildinfo); + } -div.append(iconrow); -const h2 = document.createElement("h2"); -h2.textContent = `You've been invited by ${json.inviter.username}`; -div.append(h2); -const button = document.createElement("button"); -button.textContent = "Accept"; -if (this.localuser.info.api.startsWith(info.api)) { -if (this.localuser.guildids.has(guild.id)) { -button.textContent = "Already joined"; -button.disabled = true; -} -} -button.classList.add("acceptinvbutton"); -div.append(button); -button.onclick = (_) => { -if (this.localuser.info.api.startsWith(info.api)) { -fetch(this.localuser.info.api + "/invites/" + json.code, { -method: "POST", -headers: this.localuser.headers, -}) -.then((r) => r.json()) -.then((_) => { -if (_.message) { -alert(_.message); -} -}); -} else { -if (this.json.invite) { -const params = new URLSearchParams(""); -params.set("instance", this.json.invite.url); -const encoded = params.toString(); -const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`; -window.open(url, "_blank"); -} -} -}; -})(); -return div; -} -generateArticle() { -const colordiv = document.createElement("div"); -colordiv.style.backgroundColor = "#000000"; -colordiv.classList.add("embed-color"); + div.append(iconrow); + const h2 = document.createElement("h2"); + h2.textContent = `You've been invited by ${json.inviter.username}`; + div.append(h2); + const button = document.createElement("button"); + button.textContent = "Accept"; + if(this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)){ + button.textContent = "Already joined"; + button.disabled = true; + } + button.classList.add("acceptinvbutton"); + div.append(button); + button.onclick = _=>{ + if(this.localuser.info.api.startsWith(info.api)){ + fetch(this.localuser.info.api + "/invites/" + json.code, { + method: "POST", + headers: this.localuser.headers, + }) + .then(r=>r.json()) + .then(_=>{ + if(_.message){ + alert(_.message); + } + }); + }else{ + if(this.json.invite){ + const params = new URLSearchParams(""); + params.set("instance", this.json.invite.url); + const encoded = params.toString(); + const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`; + window.open(url, "_blank"); + } + } + }; + })(); + return div; + } + generateArticle(){ + const colordiv = document.createElement("div"); + colordiv.style.backgroundColor = "#000000"; + colordiv.classList.add("embed-color"); -const div = document.createElement("div"); -div.classList.add("embed"); -if (this.json.provider) { -const provider = document.createElement("p"); -provider.classList.add("provider"); -provider.textContent = this.json.provider.name; -div.append(provider); + const div = document.createElement("div"); + div.classList.add("embed"); + if(this.json.provider){ + const provider = document.createElement("p"); + provider.classList.add("provider"); + provider.textContent = this.json.provider.name; + div.append(provider); + } + const a = document.createElement("a"); + if(this.json.url && this.json.url){ + MarkDown.safeLink(a, this.json.url); + a.textContent = this.json.url; + div.append(a); + } + if(this.json.description){ + const description = document.createElement("p"); + description.textContent = this.json.description; + div.append(description); + } + if(this.json.thumbnail){ + const img = document.createElement("img"); + if(this.json.thumbnail.width && this.json.thumbnail.width){ + let scale = 1; + const inch = 96; + scale = Math.max(scale, this.json.thumbnail.width / inch / 4); + scale = Math.max(scale, this.json.thumbnail.height / inch / 3); + this.json.thumbnail.width /= scale; + this.json.thumbnail.height /= scale; + img.style.width = this.json.thumbnail.width + "px"; + img.style.height = this.json.thumbnail.height + "px"; + } + img.classList.add("bigembedimg"); + if(this.json.video){ + img.onclick = async ()=>{ + if(this.json.video){ + img.remove(); + const iframe = document.createElement("iframe"); + iframe.src = this.json.video.url + "?autoplay=1"; + if(this.json.thumbnail.width && this.json.thumbnail.width){ + iframe.style.width = this.json.thumbnail.width + "px"; + iframe.style.height = this.json.thumbnail.height + "px"; + } + div.append(iframe); + } + }; + }else{ + img.onclick = async ()=>{ + const full = new Dialog(["img", img.src, ["fit"]]); + full.show(); + }; + } + img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url; + div.append(img); + } + colordiv.append(div); + return colordiv; + } } -const a = document.createElement("a"); -if (this.json.url && this.json.url) { -MarkDown.safeLink(a, this.json.url); -a.textContent = this.json.url; -div.append(a); -} -if (this.json.description) { -const description = document.createElement("p"); -description.textContent = this.json.description; -div.append(description); -} -if (this.json.thumbnail) { -const img = document.createElement("img"); -if (this.json.thumbnail.width && this.json.thumbnail.width) { -let scale = 1; -const inch = 96; -scale = Math.max(scale, this.json.thumbnail.width / inch / 4); -scale = Math.max(scale, this.json.thumbnail.height / inch / 3); -this.json.thumbnail.width /= scale; -this.json.thumbnail.height /= scale; -img.style.width = this.json.thumbnail.width + "px"; -img.style.height = this.json.thumbnail.height + "px"; -} -img.classList.add("bigembedimg"); -if (this.json.video) { -img.onclick = async () => { -if (this.json.video) { -img.remove(); -const iframe = document.createElement("iframe"); -iframe.src = this.json.video.url + "?autoplay=1"; -if (this.json.thumbnail.width && this.json.thumbnail.width) { -iframe.style.width = this.json.thumbnail.width + "px"; -iframe.style.height = this.json.thumbnail.height + "px"; -} -div.append(iframe); -} -}; -} else { -img.onclick = async () => { -const full = new Dialog(["img", img.src, ["fit"]]); -full.show(); -}; -} -img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url; -div.append(img); -} -colordiv.append(div); -return colordiv; -} -} -export { Embed }; +export{ Embed }; diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 2e61a93..883c7f8 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -1,50 +1,49 @@ -import { Contextmenu } from "./contextmenu.js"; -import { Guild } from "./guild.js"; -import { Localuser } from "./localuser.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ Guild }from"./guild.js"; +import{ Localuser }from"./localuser.js"; -class Emoji { - static emojis: { +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 = + name: string; + id: string; + animated: boolean; + owner: Guild | Localuser; + get guild(){ + if(this.owner instanceof Guild){ + return this.owner; + } + } + 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 + @@ -52,208 +51,208 @@ class Emoji { (this.animated ? "gif" : "png") + "?size=32"; - 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); + 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 }[] }[] = + 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(); + let cats = read16(); - for (; cats !== 0; cats--) { - const name = readString16(); - const emojis: { + 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"; + 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"); + 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"); + 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 = + 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); - } + 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); + 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 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); + 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(); - } - }); - } - }; + emojiElem.addEventListener("click", ()=>{ + res(emojiClass); + if(Contextmenu.currentmenu !== ""){ + Contextmenu.currentmenu.remove(); + } + }); + } + }; - select.addEventListener("click", clickEvent); - if (isFirst) { - clickEvent(); - isFirst = false; - } - }); + 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); + 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; - } + 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 }; +export{ Emoji }; diff --git a/src/webpage/file.ts b/src/webpage/file.ts index 570d620..20862ef 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -1,152 +1,152 @@ -import { Message } from "./message.js"; -import { Dialog } from "./dialog.js"; -import { filejson } from "./jsontypes.js"; +import{ Message }from"./message.js"; +import{ Dialog }from"./dialog.js"; +import{ filejson }from"./jsontypes.js"; -class File { -owner: Message | null; -id: string; -filename: string; -content_type: string; -width: number | undefined; -height: number | undefined; -proxy_url: string | undefined; -url: string; -size: number; -constructor(fileJSON: filejson, owner: Message | null) { -this.owner = owner; -this.id = fileJSON.id; -this.filename = fileJSON.filename; -this.content_type = fileJSON.content_type; -this.width = fileJSON.width; -this.height = fileJSON.height; -this.url = fileJSON.url; -this.proxy_url = fileJSON.proxy_url; -this.content_type = fileJSON.content_type; -this.size = fileJSON.size; -} -getHTML(temp: boolean = false): HTMLElement { -const src = this.proxy_url || this.url; -if (this.width && this.height) { -let scale = 1; -const max = 96 * 3; -scale = Math.max(scale, this.width / max); -scale = Math.max(scale, this.height / max); -this.width /= scale; -this.height /= scale; -} -if (this.content_type.startsWith("image/")) { -const div = document.createElement("div"); -const img = document.createElement("img"); -img.classList.add("messageimg"); -div.classList.add("messageimgdiv"); -img.onclick = function () { -const full = new Dialog(["img", img.src, ["fit"]]); -full.show(); -}; -img.src = src; -div.append(img); -if (this.width) { -div.style.width = this.width + "px"; -div.style.height = this.height + "px"; -} -console.log(img); -console.log(this.width, this.height); -return div; -} else if (this.content_type.startsWith("video/")) { -const video = document.createElement("video"); -const source = document.createElement("source"); -source.src = src; -video.append(source); -source.type = this.content_type; -video.controls = !temp; -if (this.width && this.height) { -video.width = this.width; -video.height = this.height; -} -return video; -} else if (this.content_type.startsWith("audio/")) { -const audio = document.createElement("audio"); -const source = document.createElement("source"); -source.src = src; -audio.append(source); -source.type = this.content_type; -audio.controls = !temp; -return audio; -} else { -return this.createunknown(); -} -} -upHTML(files: Blob[], file: globalThis.File): HTMLElement { -const div = document.createElement("div"); -const contained = this.getHTML(true); -div.classList.add("containedFile"); -div.append(contained); -const controls = document.createElement("div"); -const garbage = document.createElement("button"); -garbage.textContent = "🗑"; -garbage.onclick = (_) => { -div.remove(); -files.splice(files.indexOf(file), 1); -}; -controls.classList.add("controls"); -div.append(controls); -controls.append(garbage); -return div; -} -static initFromBlob(file: globalThis.File) { -return new File( -{ -filename: file.name, -size: file.size, -id: "null", -content_type: file.type, -width: undefined, -height: undefined, -url: URL.createObjectURL(file), -proxy_url: undefined, -}, -null -); -} -createunknown(): HTMLElement { -console.log("🗎"); -const src = this.proxy_url || this.url; -const div = document.createElement("table"); -div.classList.add("unknownfile"); -const nametr = document.createElement("tr"); -div.append(nametr); -const fileicon = document.createElement("td"); -nametr.append(fileicon); -fileicon.append("🗎"); -fileicon.classList.add("fileicon"); -fileicon.rowSpan = 2; -const nametd = document.createElement("td"); -if (src) { -const a = document.createElement("a"); -a.href = src; -a.textContent = this.filename; -nametd.append(a); -} else { -nametd.textContent = this.filename; -} +class File{ + owner: Message | null; + id: string; + filename: string; + content_type: string; + width: number | undefined; + height: number | undefined; + proxy_url: string | undefined; + url: string; + size: number; + constructor(fileJSON: filejson, owner: Message | null){ + this.owner = owner; + this.id = fileJSON.id; + this.filename = fileJSON.filename; + this.content_type = fileJSON.content_type; + this.width = fileJSON.width; + this.height = fileJSON.height; + this.url = fileJSON.url; + this.proxy_url = fileJSON.proxy_url; + this.content_type = fileJSON.content_type; + this.size = fileJSON.size; + } + getHTML(temp: boolean = false): HTMLElement{ + const src = this.proxy_url || this.url; + if(this.width && this.height){ + let scale = 1; + const max = 96 * 3; + scale = Math.max(scale, this.width / max); + scale = Math.max(scale, this.height / max); + this.width /= scale; + this.height /= scale; + } + if(this.content_type.startsWith("image/")){ + const div = document.createElement("div"); + const img = document.createElement("img"); + img.classList.add("messageimg"); + div.classList.add("messageimgdiv"); + img.onclick = function(){ + const full = new Dialog(["img", img.src, ["fit"]]); + full.show(); + }; + img.src = src; + div.append(img); + if(this.width){ + div.style.width = this.width + "px"; + div.style.height = this.height + "px"; + } + console.log(img); + console.log(this.width, this.height); + return div; + }else if(this.content_type.startsWith("video/")){ + const video = document.createElement("video"); + const source = document.createElement("source"); + source.src = src; + video.append(source); + source.type = this.content_type; + video.controls = !temp; + if(this.width && this.height){ + video.width = this.width; + video.height = this.height; + } + return video; + }else if(this.content_type.startsWith("audio/")){ + const audio = document.createElement("audio"); + const source = document.createElement("source"); + source.src = src; + audio.append(source); + source.type = this.content_type; + audio.controls = !temp; + return audio; + }else{ + return this.createunknown(); + } + } + upHTML(files: Blob[], file: globalThis.File): HTMLElement{ + const div = document.createElement("div"); + const contained = this.getHTML(true); + div.classList.add("containedFile"); + div.append(contained); + const controls = document.createElement("div"); + const garbage = document.createElement("button"); + garbage.textContent = "🗑"; + garbage.onclick = _=>{ + div.remove(); + files.splice(files.indexOf(file), 1); + }; + controls.classList.add("controls"); + div.append(controls); + controls.append(garbage); + return div; + } + static initFromBlob(file: globalThis.File){ + return new File( + { + filename: file.name, + size: file.size, + id: "null", + content_type: file.type, + width: undefined, + height: undefined, + url: URL.createObjectURL(file), + proxy_url: undefined, + }, + null + ); + } + createunknown(): HTMLElement{ + console.log("🗎"); + const src = this.proxy_url || this.url; + const div = document.createElement("table"); + div.classList.add("unknownfile"); + const nametr = document.createElement("tr"); + div.append(nametr); + const fileicon = document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan = 2; + const nametd = document.createElement("td"); + if(src){ + const a = document.createElement("a"); + a.href = src; + a.textContent = this.filename; + nametd.append(a); + }else{ + nametd.textContent = this.filename; + } -nametd.classList.add("filename"); -nametr.append(nametd); -const sizetr = document.createElement("tr"); -const size = document.createElement("td"); -sizetr.append(size); -size.textContent = "Size:" + File.filesizehuman(this.size); -size.classList.add("filesize"); -div.appendChild(sizetr); -return div; -} -static filesizehuman(fsize: number) { -const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); -return ( -Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr = document.createElement("tr"); + const size = document.createElement("td"); + sizetr.append(size); + size.textContent = "Size:" + File.filesizehuman(this.size); + size.classList.add("filesize"); + div.appendChild(sizetr); + return div; + } + static filesizehuman(fsize: number){ + const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return( + Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] -); + ); + } } -} -export { File }; +export{ File }; diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index a1351da..33c1504 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -1,29 +1,29 @@ -import { Channel } from "./channel.js"; -import { Localuser } from "./localuser.js"; -import { Contextmenu } from "./contextmenu.js"; -import { Role, RoleList } from "./role.js"; -import { Dialog } from "./dialog.js"; -import { Member } from "./member.js"; -import { Settings } from "./settings.js"; -import { Permissions } from "./permissions.js"; -import { SnowFlake } from "./snowflake.js"; -import { -channeljson, -guildjson, -emojijson, -memberjson, -invitejson, -} from "./jsontypes.js"; -import { User } from "./user.js"; +import{ Channel }from"./channel.js"; +import{ Localuser }from"./localuser.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ Role, RoleList }from"./role.js"; +import{ Dialog }from"./dialog.js"; +import{ Member }from"./member.js"; +import{ Settings }from"./settings.js"; +import{ Permissions }from"./permissions.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ + channeljson, + guildjson, + emojijson, + memberjson, + invitejson, +}from"./jsontypes.js"; +import{ User }from"./user.js"; -class Guild extends SnowFlake { -owner!: Localuser; -headers!: Localuser["headers"]; -channels!: Channel[]; -properties!: guildjson["properties"]; -member_count!: number; -roles!: Role[]; -roleids!: Map; +class Guild extends SnowFlake{ + owner!: Localuser; + headers!: Localuser["headers"]; + channels!: Channel[]; + properties!: guildjson["properties"]; + member_count!: number; + roles!: Role[]; + roleids!: Map; prevchannel: Channel | undefined; banner!: string; message_notifications!: number; @@ -35,50 +35,50 @@ roleids!: Map; emojis!: emojijson[]; large!: boolean; static contextmenu = new Contextmenu("guild menu"); - static setupcontextmenu() { - Guild.contextmenu.addbutton("Copy Guild id", function (this: Guild) { - navigator.clipboard.writeText(this.id); + static setupcontextmenu(){ + Guild.contextmenu.addbutton("Copy Guild id", function(this: Guild){ + navigator.clipboard.writeText(this.id); }); - Guild.contextmenu.addbutton("Mark as read", function (this: Guild) { - this.markAsRead(); + Guild.contextmenu.addbutton("Mark as read", function(this: Guild){ + this.markAsRead(); }); - Guild.contextmenu.addbutton("Notifications", function (this: Guild) { - this.setnotifcation(); + Guild.contextmenu.addbutton("Notifications", function(this: Guild){ + this.setnotifcation(); }); Guild.contextmenu.addbutton( - "Leave guild", - function (this: Guild) { - this.confirmleave(); - }, - null, - function (_) { - return this.properties.owner_id !== this.member.user.id; - } + "Leave guild", + function(this: Guild){ + this.confirmleave(); + }, + null, + function(_){ + return this.properties.owner_id !== this.member.user.id; + } ); Guild.contextmenu.addbutton( - "Delete guild", - function (this: Guild) { - this.confirmDelete(); - }, - null, - function (_) { - return this.properties.owner_id === this.member.user.id; - } + "Delete guild", + function(this: Guild){ + this.confirmDelete(); + }, + null, + function(_){ + return this.properties.owner_id === this.member.user.id; + } ); Guild.contextmenu.addbutton( - "Create invite", - function (this: Guild) {}, - null, - (_) => true, - (_) => false + "Create invite", + (this: Guild)=>{}, + null, + _=>true, + _=>false ); - Guild.contextmenu.addbutton("Settings", function (this: Guild) { - this.generateSettings(); + Guild.contextmenu.addbutton("Settings", function(this: Guild){ + this.generateSettings(); }); /* -----things left for later----- guild.contextmenu.addbutton("Leave Guild",function(){ @@ -90,50 +90,50 @@ roleids!: Map; editchannelf(this); },null,_=>{return thisuser.isAdmin()}) */ - } - generateSettings() { + } + generateSettings(){ const settings = new Settings("Settings for " + this.properties.name); { - const overview = settings.addButton("Overview"); - const form = overview.addForm("", (_) => {}, { - headers: this.headers, - traditionalSubmit: true, - fetchURL: this.info.api + "/guilds/" + this.id, - method: "PATCH", - }); - form.addTextInput("Name:", "name", { initText: this.properties.name }); - form.addMDInput("Description:", "description", { - initText: this.properties.description, - }); - form.addFileInput("Banner:", "banner", { clear: true }); - form.addFileInput("Icon:", "icon", { clear: true }); - let region = this.properties.region; - if (!region) { - region = ""; - } - form.addTextInput("Region:", "region", { initText: region }); + const overview = settings.addButton("Overview"); + const form = overview.addForm("", _=>{}, { + headers: this.headers, + traditionalSubmit: true, + fetchURL: this.info.api + "/guilds/" + this.id, + method: "PATCH", + }); + form.addTextInput("Name:", "name", { initText: this.properties.name }); + form.addMDInput("Description:", "description", { + initText: this.properties.description, + }); + form.addFileInput("Banner:", "banner", { clear: true }); + form.addFileInput("Icon:", "icon", { clear: true }); + let region = this.properties.region; + if(!region){ + region = ""; + } + form.addTextInput("Region:", "region", { initText: region }); } const s1 = settings.addButton("roles"); const permlist: [Role, Permissions][] = []; - for (const thing of this.roles) { - permlist.push([thing, thing.permissions]); + for(const thing of this.roles){ + permlist.push([thing, thing.permissions]); } s1.options.push( - new RoleList(permlist, this, this.updateRolePermissions.bind(this)) + new RoleList(permlist, this, this.updateRolePermissions.bind(this)) ); settings.show(); - } - constructor( + } + constructor( json: guildjson | -1, owner: Localuser, member: memberjson | User | null - ) { - if (json === -1 || member === null) { - super("@me"); - return; + ){ + if(json === -1 || member === null){ + super("@me"); + return; } - if (json.stickers.length) { - console.log(json.stickers, ":3"); + if(json.stickers.length){ + console.log(json.stickers, ":3"); } super(json.id); this.large = json.large; @@ -147,48 +147,48 @@ roleids!: Map; this.roleids = new Map(); this.message_notifications = 0; - for (const roley of json.roles) { - const roleh = new Role(roley, this); - this.roles.push(roleh); - this.roleids.set(roleh.id, roleh); + for(const roley of json.roles){ + const roleh = new Role(roley, this); + this.roles.push(roleh); + this.roleids.set(roleh.id, roleh); } - if (member instanceof User) { - Member.resolveMember(member, this).then((_) => { - if (_) { - this.member = _; - } else { - console.error("Member was unable to resolve"); - } - }); - } else { - Member.new(member, this).then((_) => { - if (_) { - this.member = _; - } - }); + if(member instanceof User){ + Member.resolveMember(member, this).then(_=>{ + if(_){ + this.member = _; + }else{ + console.error("Member was unable to resolve"); + } + }); + }else{ + Member.new(member, this).then(_=>{ + if(_){ + this.member = _; + } + }); } this.perminfo ??= { channels: {} }; - for (const thing of json.channels) { - const temp = new Channel(thing, this); - this.channels.push(temp); - this.localuser.channelids.set(temp.id, temp); + for(const thing of json.channels){ + const temp = new Channel(thing, this); + this.channels.push(temp); + this.localuser.channelids.set(temp.id, temp); } this.headchannels = []; - for (const thing of this.channels) { - const parent = thing.resolveparent(this); - if (!parent) { - this.headchannels.push(thing); - } + for(const thing of this.channels){ + const parent = thing.resolveparent(this); + if(!parent){ + this.headchannels.push(thing); + } } this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel); - } - get perminfo() { + } + get perminfo(){ return this.localuser.perminfo.guilds[this.id]; - } - set perminfo(e) { + } + set perminfo(e){ this.localuser.perminfo.guilds[this.id] = e; - } - notisetting(settings: { + } + notisetting(settings: { channel_overrides?: unknown[]; message_notifications: any; flags?: number; @@ -202,299 +202,299 @@ roleids!: Map; suppress_roles?: boolean; version?: number; guild_id?: string; - }) { + }){ this.message_notifications = settings.message_notifications; - } - setnotifcation() { + } + setnotifcation(){ let noti = this.message_notifications; const notiselect = new Dialog([ - "vdiv", - [ - "radio", - "select notifications type", - ["all", "only mentions", "none"], - function (e: string /* "all" | "only mentions" | "none" */) { - noti = ["all", "only mentions", "none"].indexOf(e); - }, - noti, - ], - [ - "button", - "", - "submit", - (_: any) => { - // - fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - message_notifications: noti, - }), - }); - this.message_notifications = noti; - }, - ], + "vdiv", + [ + "radio", + "select notifications type", + ["all", "only mentions", "none"], + function(e: string /* "all" | "only mentions" | "none" */){ + noti = ["all", "only mentions", "none"].indexOf(e); + }, + noti, + ], + [ + "button", + "", + "submit", + (_: any)=>{ + // + fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + message_notifications: noti, + }), + }); + this.message_notifications = noti; + }, + ], ]); notiselect.show(); - } - confirmleave() { + } + confirmleave(){ const full = new Dialog([ - "vdiv", - ["title", "Are you sure you want to leave?"], - [ - "hdiv", - [ - "button", - "", - "Yes, I'm sure", - (_: any) => { - this.leave().then((_) => { - full.hide(); - }); - }, - ], - [ - "button", - "", - "Nevermind", - (_: any) => { - full.hide(); - }, - ], - ], + "vdiv", + ["title", "Are you sure you want to leave?"], + [ + "hdiv", + [ + "button", + "", + "Yes, I'm sure", + (_: any)=>{ + this.leave().then(_=>{ + full.hide(); + }); + }, + ], + [ + "button", + "", + "Nevermind", + (_: any)=>{ + full.hide(); + }, + ], + ], ]); full.show(); - } - async leave() { + } + async leave(){ return fetch(this.info.api + "/users/@me/guilds/" + this.id, { - method: "DELETE", - headers: this.headers, + method: "DELETE", + headers: this.headers, }); - } - printServers() { + } + printServers(){ let build = ""; - for (const thing of this.headchannels) { - build += thing.name + ":" + thing.position + "\n"; - for (const thingy of thing.children) { - build += " " + thingy.name + ":" + thingy.position + "\n"; - } + for(const thing of this.headchannels){ + build += thing.name + ":" + thing.position + "\n"; + for(const thingy of thing.children){ + build += " " + thingy.name + ":" + thingy.position + "\n"; + } } console.log(build); - } - calculateReorder() { + } + calculateReorder(){ let position = -1; const build: { id: string; position: number | undefined; parent_id: string | undefined; }[] = []; - for (const thing of this.headchannels) { - const thisthing: { + for(const thing of this.headchannels){ + const thisthing: { id: string; position: number | undefined; parent_id: string | undefined; } = { id: thing.id, position: undefined, parent_id: undefined }; - if (thing.position <= position) { - thing.position = thisthing.position = position + 1; - } - position = thing.position; - console.log(position); - if (thing.move_id && thing.move_id !== thing.parent_id) { - thing.parent_id = thing.move_id; - thisthing.parent_id = thing.parent?.id; - thing.move_id = undefined; - } - if (thisthing.position || thisthing.parent_id) { - build.push(thisthing); - } - if (thing.children.length > 0) { - const things = thing.calculateReorder(); - for (const thing of things) { - build.push(thing); - } - } + if(thing.position <= position){ + thing.position = thisthing.position = position + 1; + } + position = thing.position; + console.log(position); + if(thing.move_id && thing.move_id !== thing.parent_id){ + thing.parent_id = thing.move_id; + thisthing.parent_id = thing.parent?.id; + thing.move_id = undefined; + } + if(thisthing.position || thisthing.parent_id){ + build.push(thisthing); + } + if(thing.children.length > 0){ + const things = thing.calculateReorder(); + for(const thing of things){ + build.push(thing); + } + } } console.log(build); this.printServers(); - if (build.length === 0) { - return; + if(build.length === 0){ + return; } const serverbug = false; - if (serverbug) { - for (const thing of build) { - console.log(build, thing); - fetch(this.info.api + "/guilds/" + this.id + "/channels", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify([thing]), - }); + if(serverbug){ + for(const thing of build){ + console.log(build, thing); + fetch(this.info.api + "/guilds/" + this.id + "/channels", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify([thing]), + }); + } + }else{ + fetch(this.info.api + "/guilds/" + this.id + "/channels", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(build), + }); } - } else { - fetch(this.info.api + "/guilds/" + this.id + "/channels", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(build), - }); - } - } - get localuser() { + } + get localuser(){ return this.owner; - } - get info() { + } + get info(){ return this.owner.info; - } - sortchannels() { - this.headchannels.sort((a, b) => { - return a.position - b.position; + } + sortchannels(){ + this.headchannels.sort((a, b)=>{ + return a.position - b.position; }); - } - static generateGuildIcon( + } + static generateGuildIcon( guild: Guild | (invitejson["guild"] & { info: { cdn: string } }) - ) { + ){ const divy = document.createElement("div"); divy.classList.add("servernoti"); const noti = document.createElement("div"); noti.classList.add("unread"); divy.append(noti); - if (guild instanceof Guild) { - guild.localuser.guildhtml.set(guild.id, divy); + if(guild instanceof Guild){ + guild.localuser.guildhtml.set(guild.id, divy); } let icon: string | null; - if (guild instanceof Guild) { - icon = guild.properties.icon; - } else { - icon = guild.icon; - } - if (icon !== null) { - const img = document.createElement("img"); - img.classList.add("pfp", "servericon"); - img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; - divy.appendChild(img); - if (guild instanceof Guild) { - img.onclick = () => { - console.log(guild.loadGuild); - guild.loadGuild(); - guild.loadChannel(); - }; - Guild.contextmenu.bindContextmenu(img, guild, undefined); - } - } else { - const div = document.createElement("div"); - let name: string; - if (guild instanceof Guild) { - name = guild.properties.name; - } else { - name = guild.name; - } - const build = name - .replace(/'s /g, " ") - .replace(/\w+/g, (word) => word[0]) - .replace(/\s/g, ""); - div.textContent = build; - div.classList.add("blankserver", "servericon"); - divy.appendChild(div); - if (guild instanceof Guild) { - div.onclick = () => { - guild.loadGuild(); - guild.loadChannel(); - }; - Guild.contextmenu.bindContextmenu(div, guild, undefined); + if(guild instanceof Guild){ + icon = guild.properties.icon; + }else{ + icon = guild.icon; } + if(icon !== null){ + const img = document.createElement("img"); + img.classList.add("pfp", "servericon"); + img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; + divy.appendChild(img); + if(guild instanceof Guild){ + img.onclick = ()=>{ + console.log(guild.loadGuild); + guild.loadGuild(); + guild.loadChannel(); + }; + Guild.contextmenu.bindContextmenu(img, guild); + } + }else{ + const div = document.createElement("div"); + let name: string; + if(guild instanceof Guild){ + name = guild.properties.name; + }else{ + name = guild.name; + } + const build = name + .replace(/'s /g, " ") + .replace(/\w+/g, word=>word[0]) + .replace(/\s/g, ""); + div.textContent = build; + div.classList.add("blankserver", "servericon"); + divy.appendChild(div); + if(guild instanceof Guild){ + div.onclick = ()=>{ + guild.loadGuild(); + guild.loadChannel(); + }; + Guild.contextmenu.bindContextmenu(div, guild); + } } return divy; - } - generateGuildIcon() { + } + generateGuildIcon(){ return Guild.generateGuildIcon(this); - } - confirmDelete() { + } + confirmDelete(){ let confirmname = ""; const full = new Dialog([ - "vdiv", - [ - "title", - "Are you sure you want to delete " + this.properties.name + "?", - ], - [ - "textbox", - "Name of server:", - "", - function (this: HTMLInputElement) { - confirmname = this.value; - }, - ], - [ - "hdiv", - [ - "button", - "", - "Yes, I'm sure", - (_: any) => { - console.log(confirmname); - if (confirmname !== this.properties.name) { - return; - } - this.delete().then((_) => { - full.hide(); - }); - }, - ], - [ - "button", - "", - "Nevermind", - (_: any) => { - full.hide(); - }, - ], - ], + "vdiv", + [ + "title", + "Are you sure you want to delete " + this.properties.name + "?", + ], + [ + "textbox", + "Name of server:", + "", + function(this: HTMLInputElement){ + confirmname = this.value; + }, + ], + [ + "hdiv", + [ + "button", + "", + "Yes, I'm sure", + (_: any)=>{ + console.log(confirmname); + if(confirmname !== this.properties.name){ + return; + } + this.delete().then(_=>{ + full.hide(); + }); + }, + ], + [ + "button", + "", + "Nevermind", + (_: any)=>{ + full.hide(); + }, + ], + ], ]); full.show(); - } - async delete() { + } + async delete(){ return fetch(this.info.api + "/guilds/" + this.id + "/delete", { - method: "POST", - headers: this.headers, + method: "POST", + headers: this.headers, }); - } - unreads(html?: HTMLElement | undefined) { - if (html) { - this.html = html; - } else { - html = this.html; + } + unreads(html?: HTMLElement | undefined){ + if(html){ + this.html = html; + }else{ + html = this.html; } let read = true; - for (const thing of this.channels) { - if (thing.hasunreads) { - console.log(thing); - read = false; - break; + for(const thing of this.channels){ + if(thing.hasunreads){ + console.log(thing); + read = false; + break; + } } + if(!html){ + return; } - if (!html) { - return; + if(read){ + html.children[0].classList.remove("notiunread"); + }else{ + html.children[0].classList.add("notiunread"); } - if (read) { - html.children[0].classList.remove("notiunread"); - } else { - html.children[0].classList.add("notiunread"); - } - } - getHTML() { + } + getHTML(){ //this.printServers(); this.sortchannels(); this.printServers(); const build = document.createElement("div"); - for (const thing of this.headchannels) { - build.appendChild(thing.createguildHTML(this.isAdmin())); + for(const thing of this.headchannels){ + build.appendChild(thing.createguildHTML(this.isAdmin())); } return build; - } - isAdmin() { + } + isAdmin(){ return this.member.isAdmin(); - } - async markAsRead() { + } + async markAsRead(){ const build: { read_states: { channel_id: string; @@ -502,156 +502,156 @@ roleids!: Map; read_state_type: number; }[]; } = { read_states: [] }; - for (const thing of this.channels) { - if (thing.hasunreads) { - build.read_states.push({ - channel_id: thing.id, - message_id: thing.lastmessageid, - read_state_type: 0, - }); - thing.lastreadmessageid = thing.lastmessageid; - if (!thing.myhtml) continue; - thing.myhtml.classList.remove("cunread"); - } + for(const thing of this.channels){ + if(thing.hasunreads){ + build.read_states.push({ + channel_id: thing.id, + message_id: thing.lastmessageid, + read_state_type: 0, + }); + thing.lastreadmessageid = thing.lastmessageid; + if(!thing.myhtml)continue; + thing.myhtml.classList.remove("cunread"); + } } this.unreads(); fetch(this.info.api + "/read-states/ack-bulk", { - method: "POST", - headers: this.headers, - body: JSON.stringify(build), + method: "POST", + headers: this.headers, + body: JSON.stringify(build), }); - } - hasRole(r: Role | string) { + } + hasRole(r: Role | string){ console.log("this should run"); - if (r instanceof Role) { - r = r.id; + if(r instanceof Role){ + r = r.id; } return this.member.hasRole(r); + } + loadChannel(ID?: string | undefined){ + if(ID){ + const channel = this.localuser.channelids.get(ID); + if(channel){ + channel.getHTML(); + return; + } } - loadChannel(ID?: string | undefined) { - if (ID) { - const channel = this.localuser.channelids.get(ID); - if (channel) { - channel.getHTML(); - return; + if(this.prevchannel){ + console.log(this.prevchannel); + this.prevchannel.getHTML(); + return; } + for(const thing of this.channels){ + if(thing.children.length === 0){ + thing.getHTML(); + return; + } } - if (this.prevchannel) { - console.log(this.prevchannel); - this.prevchannel.getHTML(); - return; - } - for (const thing of this.channels) { - if (thing.children.length === 0) { - thing.getHTML(); - return; - } - } - } - loadGuild() { + } + loadGuild(){ this.localuser.loadGuild(this.id); - } - updateChannel(json: channeljson) { + } + updateChannel(json: channeljson){ const channel = this.localuser.channelids.get(json.id); - if (channel) { - channel.updateChannel(json); - this.headchannels = []; - for (const thing of this.channels) { - thing.children = []; + if(channel){ + channel.updateChannel(json); + this.headchannels = []; + for(const thing of this.channels){ + thing.children = []; + } + this.headchannels = []; + for(const thing of this.channels){ + const parent = thing.resolveparent(this); + if(!parent){ + this.headchannels.push(thing); + } + } + this.printServers(); } - this.headchannels = []; - for (const thing of this.channels) { - const parent = thing.resolveparent(this); - if (!parent) { - this.headchannels.push(thing); - } - } - this.printServers(); - } - } - createChannelpac(json: channeljson) { + } + createChannelpac(json: channeljson){ const thischannel = new Channel(json, this); this.localuser.channelids.set(json.id, thischannel); this.channels.push(thischannel); thischannel.resolveparent(this); - if (!thischannel.parent) { - this.headchannels.push(thischannel); + if(!thischannel.parent){ + this.headchannels.push(thischannel); } this.calculateReorder(); this.printServers(); return thischannel; - } - createchannels(func = this.createChannel) { + } + createchannels(func = this.createChannel){ let name = ""; let category = 0; const channelselect = new Dialog([ - "vdiv", - [ - "radio", - "select channel type", - ["voice", "text", "announcement"], - function (radio: string) { - console.log(radio); - category = + "vdiv", + [ + "radio", + "select channel type", + ["voice", "text", "announcement"], + function(radio: string){ + console.log(radio); + category = { text: 0, voice: 2, announcement: 5, category: 4 }[radio] || 0; - }, - 1, - ], - [ - "textbox", - "Name of channel", - "", - function (this: HTMLInputElement) { - name = this.value; - }, - ], - [ - "button", - "", - "submit", - function () { - console.log(name, category); - func(name, category); - channelselect.hide(); - }, - ], + }, + 1, + ], + [ + "textbox", + "Name of channel", + "", + function(this: HTMLInputElement){ + name = this.value; + }, + ], + [ + "button", + "", + "submit", + function(){ + console.log(name, category); + func(name, category); + channelselect.hide(); + }, + ], ]); channelselect.show(); - } - createcategory() { + } + createcategory(){ let name = ""; const category = 4; const channelselect = new Dialog([ - "vdiv", - [ - "textbox", - "Name of category", - "", - function (this: HTMLInputElement) { - name = this.value; - }, - ], - [ - "button", - "", - "submit", - () => { - console.log(name, category); - this.createChannel(name, category); - channelselect.hide(); - }, - ], + "vdiv", + [ + "textbox", + "Name of category", + "", + function(this: HTMLInputElement){ + name = this.value; + }, + ], + [ + "button", + "", + "submit", + ()=>{ + console.log(name, category); + this.createChannel(name, category); + channelselect.hide(); + }, + ], ]); channelselect.show(); - } - delChannel(json: channeljson) { + } + delChannel(json: channeljson){ const channel = this.localuser.channelids.get(json.id); this.localuser.channelids.delete(json.id); - if (!channel) return; + if(!channel)return; this.channels.splice(this.channels.indexOf(channel), 1); const indexy = this.headchannels.indexOf(channel); - if (indexy !== -1) { - this.headchannels.splice(indexy, 1); + if(indexy !== -1){ + this.headchannels.splice(indexy, 1); } /* @@ -670,55 +670,55 @@ roleids!: Map; this.channels=build; */ this.printServers(); - } - createChannel(name: string, type: number) { + } + createChannel(name: string, type: number){ fetch(this.info.api + "/guilds/" + this.id + "/channels", { - method: "POST", - headers: this.headers, - body: JSON.stringify({ name, type }), + method: "POST", + headers: this.headers, + body: JSON.stringify({ name, type }), }); - } - async createRole(name: string) { + } + async createRole(name: string){ const fetched = await fetch( - this.info.api + "/guilds/" + this.id + "roles", - { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - name, - color: 0, - permissions: "0", - }), - } + this.info.api + "/guilds/" + this.id + "roles", + { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + color: 0, + permissions: "0", + }), + } ); const json = await fetched.json(); const role = new Role(json, this); this.roleids.set(role.id, role); this.roles.push(role); return role; - } - async updateRolePermissions(id: string, perms: Permissions) { + } + async updateRolePermissions(id: string, perms: Permissions){ const role = this.roleids.get(id); - if (!role) { - return; + if(!role){ + return; } role.permissions.allow = perms.allow; role.permissions.deny = perms.deny; await fetch(this.info.api + "/guilds/" + this.id + "/roles/" + role.id, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - color: role.color, - hoist: role.hoist, - icon: role.icon, - mentionable: role.mentionable, - name: role.name, - permissions: role.permissions.allow.toString(), - unicode_emoji: role.unicode_emoji, - }), + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + color: role.color, + hoist: role.hoist, + icon: role.icon, + mentionable: role.mentionable, + name: role.name, + permissions: role.permissions.allow.toString(), + unicode_emoji: role.unicode_emoji, + }), }); - } - } - Guild.setupcontextmenu(); - export { Guild }; + } +} +Guild.setupcontextmenu(); +export{ Guild }; diff --git a/src/webpage/home.html b/src/webpage/home.html index 795833a..8b49090 100644 --- a/src/webpage/home.html +++ b/src/webpage/home.html @@ -1,61 +1,61 @@ - - - - Jank Client - - - - - - - + + + + Jank Client + + + + + + + - - -
- -
-

Welcome to Jank Client

-
-
-

Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including: -

-
    -
  • Direct Messaging
  • -
  • Reactions support
  • -
  • Invites
  • -
  • Account switching
  • -
  • User settings
  • -
-
-
-

Spacebar compatible Instances:

-
-
-
-
-

Contribute to Jank Client

-

We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out - some typos.


- + + -
- - +
+ +
+

Welcome to Jank Client

+
+
+

Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including: +

+
    +
  • Direct Messaging
  • +
  • Reactions support
  • +
  • Invites
  • +
  • Account switching
  • +
  • User settings
  • +
+
+
+

Spacebar compatible Instances:

+
+
+
+
+

Contribute to Jank Client

+

We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out + some typos.


+ +

Github

+
+
+
+ + \ No newline at end of file diff --git a/src/webpage/home.ts b/src/webpage/home.ts index a60402b..669c0d1 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -1,12 +1,12 @@ -import { mobile } from "./login.js"; +import{ mobile }from"./login.js"; console.log(mobile); const serverbox = document.getElementById("instancebox") as HTMLDivElement; fetch("/instances.json") -.then((_) => _.json()) -.then( -( -json: { + .then(_=>_.json()) + .then( + ( + json: { name: string; description?: string; descriptionLong?: string; @@ -23,67 +23,67 @@ gateway: string; login?: string; }; }[] -) => { -console.warn(json); -for (const instance of json) { -if (instance.display === false) { -continue; -} -const div = document.createElement("div"); -div.classList.add("flexltr", "instance"); -if (instance.image) { -const img = document.createElement("img"); -img.src = instance.image; -div.append(img); -} -const statbox = document.createElement("div"); -statbox.classList.add("flexttb"); + )=>{ + console.warn(json); + for(const instance of json){ + if(instance.display === false){ + continue; + } + const div = document.createElement("div"); + div.classList.add("flexltr", "instance"); + if(instance.image){ + const img = document.createElement("img"); + img.src = instance.image; + div.append(img); + } + const statbox = document.createElement("div"); + statbox.classList.add("flexttb"); -{ -const textbox = document.createElement("div"); -textbox.classList.add("flexttb", "instatancetextbox"); -const title = document.createElement("h2"); -title.innerText = instance.name; -if (instance.online !== undefined) { -const status = document.createElement("span"); -status.innerText = instance.online ? "Online" : "Offline"; -status.classList.add("instanceStatus"); -title.append(status); -} -textbox.append(title); -if (instance.description || instance.descriptionLong) { -const p = document.createElement("p"); -if (instance.descriptionLong) { -p.innerText = instance.descriptionLong; -} else if (instance.description) { -p.innerText = instance.description; -} -textbox.append(p); -} -statbox.append(textbox); -} -if (instance.uptime) { -const stats = document.createElement("div"); -stats.classList.add("flexltr"); -const span = document.createElement("span"); -span.innerText = `Uptime: All time: ${Math.round( -instance.uptime.alltime * 100 -)}% This week: ${Math.round( -instance.uptime.weektime * 100 -)}% Today: ${Math.round(instance.uptime.daytime * 100)}%`; -stats.append(span); -statbox.append(stats); -} -div.append(statbox); -div.onclick = (_) => { -if (instance.online) { -window.location.href = + { + const textbox = document.createElement("div"); + textbox.classList.add("flexttb", "instatancetextbox"); + const title = document.createElement("h2"); + title.innerText = instance.name; + if(instance.online !== undefined){ + const status = document.createElement("span"); + status.innerText = instance.online ? "Online" : "Offline"; + status.classList.add("instanceStatus"); + title.append(status); + } + textbox.append(title); + if(instance.description || instance.descriptionLong){ + const p = document.createElement("p"); + if(instance.descriptionLong){ + p.innerText = instance.descriptionLong; + }else if(instance.description){ + p.innerText = instance.description; + } + textbox.append(p); + } + statbox.append(textbox); + } + if(instance.uptime){ + const stats = document.createElement("div"); + stats.classList.add("flexltr"); + const span = document.createElement("span"); + span.innerText = `Uptime: All time: ${Math.round( + instance.uptime.alltime * 100 + )}% This week: ${Math.round( + instance.uptime.weektime * 100 + )}% Today: ${Math.round(instance.uptime.daytime * 100)}%`; + stats.append(span); + statbox.append(stats); + } + div.append(statbox); + div.onclick = _=>{ + if(instance.online){ + window.location.href = "/register.html?instance=" + encodeURI(instance.name); -} else { -alert("Instance is offline, can't connect"); -} -}; -serverbox.append(div); -} -} -); + }else{ + alert("Instance is offline, can't connect"); + } + }; + serverbox.append(div); + } + } + ); diff --git a/src/webpage/index.html b/src/webpage/index.html index 7bd13c6..67308f8 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -1,82 +1,82 @@ - - - - Jank Client - - - - - - + + + + Jank Client + + + + + + - - + + - - + + -
-
- -

Jank Client is loading

-

This shouldn't take long

-

Switch Accounts

-
-
-
-
-
-
-
-
-

Server Name

+
+
+ +

Jank Client is loading

+

This shouldn't take long

+

Switch Accounts

-
-
-
- +
+
+
+
+
+
+
+

Server Name

+
+
+
+
+ -
-

USERNAME

-

STATUS

+
+

USERNAME

+

STATUS

+
+
+ +
+
- -
- -
-
-
-
- - Channel name - -
-
-
+
+
+ + Channel name +
-
-
-
-
-
-
+
+
+
- -
- + \ No newline at end of file diff --git a/src/webpage/index.ts b/src/webpage/index.ts index d902d3b..c42917e 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -1,146 +1,146 @@ -import { Localuser } from "./localuser.js"; -import { Contextmenu } from "./contextmenu.js"; -import { mobile, getBulkUsers, setTheme, Specialuser } from "./login.js"; -import { MarkDown } from "./markdown.js"; -import { Message } from "./message.js"; -import { File } from "./file.js"; +import{ Localuser }from"./localuser.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ mobile, getBulkUsers, setTheme, Specialuser }from"./login.js"; +import{ MarkDown }from"./markdown.js"; +import{ Message }from"./message.js"; +import{ File }from"./file.js"; -(async () => { -async function waitForLoad(): Promise { - return new Promise((resolve) => { - document.addEventListener("DOMContentLoaded", (_) => resolve()); - }); +(async ()=>{ + async function waitForLoad(): Promise{ + return new Promise(resolve=>{ + document.addEventListener("DOMContentLoaded", _=>resolve()); + }); } await waitForLoad(); const users = getBulkUsers(); - if (!users.currentuser) { - window.location.href = "/login.html"; - return; + if(!users.currentuser){ + window.location.href = "/login.html"; + return; } - function showAccountSwitcher(): void { - const table = document.createElement("div"); - table.classList.add("accountSwitcher"); + function showAccountSwitcher(): void{ + const table = document.createElement("div"); + table.classList.add("accountSwitcher"); - for (const user of Object.values(users.users)) { - const specialUser = user as Specialuser; - const userInfo = document.createElement("div"); - userInfo.classList.add("flexltr", "switchtable"); + for(const user of Object.values(users.users)){ + const specialUser = user as Specialuser; + const userInfo = document.createElement("div"); + userInfo.classList.add("flexltr", "switchtable"); - const pfp = document.createElement("img"); - pfp.src = specialUser.pfpsrc; - pfp.classList.add("pfp"); - userInfo.append(pfp); + const pfp = document.createElement("img"); + pfp.src = specialUser.pfpsrc; + pfp.classList.add("pfp"); + userInfo.append(pfp); - const userDiv = document.createElement("div"); - userDiv.classList.add("userinfo"); - userDiv.textContent = specialUser.username; - userDiv.append(document.createElement("br")); + const userDiv = document.createElement("div"); + userDiv.classList.add("userinfo"); + userDiv.textContent = specialUser.username; + userDiv.append(document.createElement("br")); - const span = document.createElement("span"); - span.textContent = specialUser.serverurls.wellknown - .replace("https://", "") - .replace("http://", ""); - span.classList.add("serverURL"); - userDiv.append(span); + const span = document.createElement("span"); + span.textContent = specialUser.serverurls.wellknown + .replace("https://", "") + .replace("http://", ""); + span.classList.add("serverURL"); + userDiv.append(span); - userInfo.append(userDiv); - table.append(userInfo); + userInfo.append(userDiv); + table.append(userInfo); - userInfo.addEventListener("click", () => { - thisUser.unload(); - thisUser.swapped = true; - const loading = document.getElementById("loading") as HTMLDivElement; - loading.classList.remove("doneloading"); - loading.classList.add("loading"); + userInfo.addEventListener("click", ()=>{ + thisUser.unload(); + thisUser.swapped = true; + const loading = document.getElementById("loading") as HTMLDivElement; + loading.classList.remove("doneloading"); + loading.classList.add("loading"); - thisUser = new Localuser(specialUser); - users.currentuser = specialUser.uid; - localStorage.setItem("userinfos", JSON.stringify(users)); + thisUser = new Localuser(specialUser); + users.currentuser = specialUser.uid; + localStorage.setItem("userinfos", JSON.stringify(users)); - thisUser.initwebsocket().then(() => { - thisUser.loaduser(); - thisUser.init(); - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); + thisUser.initwebsocket().then(()=>{ + thisUser.loaduser(); + thisUser.init(); + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); - userInfo.remove(); - }); - } + userInfo.remove(); + }); + } - const switchAccountDiv = document.createElement("div"); - switchAccountDiv.classList.add("switchtable"); - switchAccountDiv.textContent = "Switch accounts ⇌"; - switchAccountDiv.addEventListener("click", () => { - window.location.href = "/login.html"; - }); - table.append(switchAccountDiv); + const switchAccountDiv = document.createElement("div"); + switchAccountDiv.classList.add("switchtable"); + switchAccountDiv.textContent = "Switch accounts ⇌"; + switchAccountDiv.addEventListener("click", ()=>{ + window.location.href = "/login.html"; + }); + table.append(switchAccountDiv); - if (Contextmenu.currentmenu) { - Contextmenu.currentmenu.remove(); - } - Contextmenu.currentmenu = table; - document.body.append(table); + if(Contextmenu.currentmenu){ + Contextmenu.currentmenu.remove(); + } + Contextmenu.currentmenu = table; + document.body.append(table); } const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; - userInfoElement.addEventListener("click", (event) => { - event.stopImmediatePropagation(); - showAccountSwitcher(); + userInfoElement.addEventListener("click", event=>{ + event.stopImmediatePropagation(); + showAccountSwitcher(); }); const switchAccountsElement = document.getElementById( - "switchaccounts" + "switchaccounts" ) as HTMLDivElement; - switchAccountsElement.addEventListener("click", (event) => { - event.stopImmediatePropagation(); - showAccountSwitcher(); + switchAccountsElement.addEventListener("click", event=>{ + event.stopImmediatePropagation(); + showAccountSwitcher(); }); let thisUser: Localuser; - try { - console.log(users.users, users.currentuser); - thisUser = new Localuser(users.users[users.currentuser]); - thisUser.initwebsocket().then(() => { - thisUser.loaduser(); - thisUser.init(); - const loading = document.getElementById("loading") as HTMLDivElement; - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); - } catch (e) { - console.error(e); - (document.getElementById("load-desc") as HTMLSpanElement).textContent = + try{ + console.log(users.users, users.currentuser); + thisUser = new Localuser(users.users[users.currentuser]); + thisUser.initwebsocket().then(()=>{ + thisUser.loaduser(); + thisUser.init(); + const loading = document.getElementById("loading") as HTMLDivElement; + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + }catch(e){ + console.error(e); + (document.getElementById("load-desc") as HTMLSpanElement).textContent = "Account unable to start"; - thisUser = new Localuser(-1); + thisUser = new Localuser(-1); } const menu = new Contextmenu("create rightclick"); menu.addbutton( - "Create channel", - () => { - if (thisUser.lookingguild) { - thisUser.lookingguild.createchannels(); - } - }, - null, - () => thisUser.isAdmin() + "Create channel", + ()=>{ + if(thisUser.lookingguild){ + thisUser.lookingguild.createchannels(); + } + }, + null, + ()=>thisUser.isAdmin() ); menu.addbutton( - "Create category", - () => { - if (thisUser.lookingguild) { - thisUser.lookingguild.createcategory(); - } - }, - null, - () => thisUser.isAdmin() + "Create category", + ()=>{ + if(thisUser.lookingguild){ + thisUser.lookingguild.createcategory(); + } + }, + null, + ()=>thisUser.isAdmin() ); menu.bindContextmenu( @@ -150,51 +150,51 @@ async function waitForLoad(): Promise { ); const pasteImageElement = document.getElementById( - "pasteimage" + "pasteimage" ) as HTMLDivElement; let replyingTo: Message | null = null; - async function handleEnter(event: KeyboardEvent): Promise { + async function handleEnter(event: KeyboardEvent): Promise{ const channel = thisUser.channelfocus; - if (!channel) return; + if(!channel)return; channel.typingstart(); - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); + if(event.key === "Enter" && !event.shiftKey){ + event.preventDefault(); - if (channel.editing) { - channel.editing.edit(markdown.rawString); - channel.editing = null; - } else { - replyingTo = thisUser.channelfocus - ? thisUser.channelfocus.replyingto - : null; - if (replyingTo?.div) { - replyingTo.div.classList.remove("replying"); - } - if (thisUser.channelfocus) { - thisUser.channelfocus.replyingto = null; - } - channel.sendMessage(markdown.rawString, { - attachments: images, - // @ts-ignore This is valid according to the API - embeds: [], // Add an empty array for the embeds property - replyingto: replyingTo, - }); - if (thisUser.channelfocus) { - thisUser.channelfocus.makereplybox(); - } - } + if(channel.editing){ + channel.editing.edit(markdown.rawString); + channel.editing = null; + }else{ + replyingTo = thisUser.channelfocus + ? thisUser.channelfocus.replyingto + : null; + if(replyingTo?.div){ + replyingTo.div.classList.remove("replying"); + } + if(thisUser.channelfocus){ + thisUser.channelfocus.replyingto = null; + } + channel.sendMessage(markdown.rawString, { + attachments: images, + // @ts-ignore This is valid according to the API + embeds: [], // Add an empty array for the embeds property + replyingto: replyingTo, + }); + if(thisUser.channelfocus){ + thisUser.channelfocus.makereplybox(); + } + } - while (images.length) { - images.pop(); - pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); - } + while(images.length){ + images.pop(); + pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); + } - typebox.innerHTML = ""; - } + typebox.innerHTML = ""; } + } interface CustomHTMLDivElement extends HTMLDivElement { markdown: MarkDown; @@ -204,56 +204,56 @@ async function waitForLoad(): Promise { const markdown = new MarkDown("", thisUser); typebox.markdown = markdown; typebox.addEventListener("keyup", handleEnter); - typebox.addEventListener("keydown", (event) => { - if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); + typebox.addEventListener("keydown", event=>{ + if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); }); markdown.giveBox(typebox); const images: Blob[] = []; const imagesHtml: HTMLElement[] = []; - document.addEventListener("paste", async (e: ClipboardEvent) => { - if (!e.clipboardData) return; + document.addEventListener("paste", async (e: ClipboardEvent)=>{ + if(!e.clipboardData)return; - for (const file of Array.from(e.clipboardData.files)) { - const fileInstance = File.initFromBlob(file); - e.preventDefault(); - const html = fileInstance.upHTML(images, file); - pasteImageElement.appendChild(html); - images.push(file); - imagesHtml.push(html); - } + for(const file of Array.from(e.clipboardData.files)){ + const fileInstance = File.initFromBlob(file); + e.preventDefault(); + const html = fileInstance.upHTML(images, file); + pasteImageElement.appendChild(html); + images.push(file); + imagesHtml.push(html); + } }); setTheme(); - function userSettings(): void { - thisUser.showusersettings(); + function userSettings(): void{ + thisUser.showusersettings(); } (document.getElementById("settings") as HTMLImageElement).onclick = userSettings; - if (mobile) { - const channelWrapper = document.getElementById( - "channelw" - ) as HTMLDivElement; - channelWrapper.onclick = () => { - ( + if(mobile){ + const channelWrapper = document.getElementById( + "channelw" + ) as HTMLDivElement; + channelWrapper.onclick = ()=>{ + ( document.getElementById("channels")!.parentNode as HTMLElement - ).classList.add("collapse"); + ).classList.add("collapse"); document.getElementById("servertd")!.classList.add("collapse"); document.getElementById("servers")!.classList.add("collapse"); - }; + }; - const mobileBack = document.getElementById("mobileback") as HTMLDivElement; - mobileBack.textContent = "#"; - mobileBack.onclick = () => { - ( + const mobileBack = document.getElementById("mobileback") as HTMLDivElement; + mobileBack.textContent = "#"; + mobileBack.onclick = ()=>{ + ( document.getElementById("channels")!.parentNode as HTMLElement - ).classList.remove("collapse"); + ).classList.remove("collapse"); document.getElementById("servertd")!.classList.remove("collapse"); document.getElementById("servers")!.classList.remove("collapse"); - }; + }; } - })(); +})(); diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 2cd2f18..6a5962e 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -1,323 +1,323 @@ -class InfiniteScroller { -readonly getIDFromOffset: ( +class InfiniteScroller{ + readonly getIDFromOffset: ( ID: string, offset: number ) => Promise; readonly getHTMLFromID: (ID: string) => Promise; - readonly destroyFromID: (ID: string) => Promise; - readonly reachesBottom: () => void; - private readonly minDist = 2000; - private readonly fillDist = 3000; - private readonly maxDist = 6000; - HTMLElements: [HTMLElement, string][] = []; - div: HTMLDivElement | null = null; - timeout: NodeJS.Timeout | null = null; - beenloaded = false; - scrollBottom = 0; - scrollTop = 0; - needsupdate = true; - averageheight = 60; - watchtime = false; - changePromise: Promise | undefined; - scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; + readonly destroyFromID: (ID: string) => Promise; + readonly reachesBottom: () => void; + private readonly minDist = 2000; + private readonly fillDist = 3000; + private readonly maxDist = 6000; + HTMLElements: [HTMLElement, string][] = []; + div: HTMLDivElement | null = null; + timeout: NodeJS.Timeout | null = null; + beenloaded = false; + scrollBottom = 0; + scrollTop = 0; + needsupdate = true; + averageheight = 60; + watchtime = false; + changePromise: Promise | undefined; + scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; - constructor( - getIDFromOffset: InfiniteScroller["getIDFromOffset"], - getHTMLFromID: InfiniteScroller["getHTMLFromID"], - destroyFromID: InfiniteScroller["destroyFromID"], - reachesBottom: InfiniteScroller["reachesBottom"] = () => {} - ) { - this.getIDFromOffset = getIDFromOffset; - this.getHTMLFromID = getHTMLFromID; - this.destroyFromID = destroyFromID; - this.reachesBottom = reachesBottom; + constructor( + getIDFromOffset: InfiniteScroller["getIDFromOffset"], + getHTMLFromID: InfiniteScroller["getHTMLFromID"], + destroyFromID: InfiniteScroller["destroyFromID"], + reachesBottom: InfiniteScroller["reachesBottom"] = ()=>{} + ){ + this.getIDFromOffset = getIDFromOffset; + this.getHTMLFromID = getHTMLFromID; + this.destroyFromID = destroyFromID; + this.reachesBottom = reachesBottom; + } + + async getDiv(initialId: string): Promise{ + if(this.div){ + throw new Error("Div already exists, exiting."); + } + + const scroll = document.createElement("div"); + scroll.classList.add("flexttb", "scroller"); + this.div = scroll; + + this.div.addEventListener("scroll", ()=>{ + this.checkscroll(); + if(this.scrollBottom < 5){ + this.scrollBottom = 5; + } + if(this.timeout === null){ + this.timeout = setTimeout(this.updatestuff.bind(this), 300); + } + this.watchForChange(); + }); + + let oldheight = 0; + new ResizeObserver(()=>{ + this.checkscroll(); + const func = this.snapBottom(); + this.updatestuff(); + const change = oldheight - scroll.offsetHeight; + if(change > 0 && this.div){ + this.div.scrollTop += change; + } + oldheight = scroll.offsetHeight; + this.watchForChange(); + func(); + }).observe(scroll); + + new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); + + await this.firstElement(initialId); + this.updatestuff(); + await this.watchForChange().then(()=>{ + this.updatestuff(); + this.beenloaded = true; + }); + + return scroll; + } + + checkscroll(): void{ + if(this.beenloaded && this.div && !document.body.contains(this.div)){ + console.warn("not in document"); + this.div = null; + } + } + + async updatestuff(): Promise{ + this.timeout = null; + if(!this.div)return; + + this.scrollBottom = + this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight; + this.averageheight = this.div.scrollHeight / this.HTMLElements.length; + if(this.averageheight < 10){ + this.averageheight = 60; + } + this.scrollTop = this.div.scrollTop; + + if(!this.scrollBottom && !(await this.watchForChange())){ + this.reachesBottom(); + } + if(!this.scrollTop){ + await this.watchForChange(); + } + this.needsupdate = false; + } + + async firstElement(id: string): Promise{ + if(!this.div)return; + const html = await this.getHTMLFromID(id); + this.div.appendChild(html); + this.HTMLElements.push([html, id]); + } + + async addedBottom(): Promise{ + await this.updatestuff(); + const func = this.snapBottom(); + await this.watchForChange(); + func(); + } + + snapBottom(): () => void{ + const scrollBottom = this.scrollBottom; + return()=>{ + if(this.div && scrollBottom < 4){ + this.div.scrollTop = this.div.scrollHeight; + } + }; + } + + private async watchForTop( + already = false, + fragment = new DocumentFragment() + ): Promise{ + if(!this.div)return false; + try{ + let again = false; + if(this.scrollTop < (already ? this.fillDist : this.minDist)){ + let nextid: string | undefined; + const firstelm = this.HTMLElements.at(0); + if(firstelm){ + const previd = firstelm[1]; + nextid = await this.getIDFromOffset(previd, 1); } - async getDiv(initialId: string): Promise { - if (this.div) { - throw new Error("Div already exists, exiting."); + if(nextid){ + const html = await this.getHTMLFromID(nextid); + if(!html){ + this.destroyFromID(nextid); + return false; } + again = true; + fragment.prepend(html); + this.HTMLElements.unshift([html, nextid]); + this.scrollTop += this.averageheight; + } + } + if(this.scrollTop > this.maxDist){ + const html = this.HTMLElements.shift(); + if(html){ + again = true; + await this.destroyFromID(html[1]); + this.scrollTop -= this.averageheight; + } + } + if(again){ + await this.watchForTop(true, fragment); + } + return again; + }finally{ + if(!already){ + if(this.div.scrollTop === 0){ + this.scrollTop = 1; + this.div.scrollTop = 10; + } + this.div.prepend(fragment, fragment); + } + } + } - const scroll = document.createElement("div"); - scroll.classList.add("flexttb", "scroller"); - this.div = scroll; - - this.div.addEventListener("scroll", () => { - this.checkscroll(); - if (this.scrollBottom < 5) { - this.scrollBottom = 5; - } - if (this.timeout === null) { - this.timeout = setTimeout(this.updatestuff.bind(this), 300); - } - this.watchForChange(); - }); - - let oldheight = 0; - new ResizeObserver(() => { - this.checkscroll(); - const func = this.snapBottom(); - this.updatestuff(); - const change = oldheight - scroll.offsetHeight; - if (change > 0 && this.div) { - this.div.scrollTop += change; - } - oldheight = scroll.offsetHeight; - this.watchForChange(); + async watchForBottom( + already = false, + fragment = new DocumentFragment() + ): Promise{ + let func: Function | undefined; + if(!already) func = this.snapBottom(); + if(!this.div)return false; + try{ + let again = false; + const scrollBottom = this.scrollBottom; + if(scrollBottom < (already ? this.fillDist : this.minDist)){ + let nextid: string | undefined; + const lastelm = this.HTMLElements.at(-1); + if(lastelm){ + const previd = lastelm[1]; + nextid = await this.getIDFromOffset(previd, -1); + } + if(nextid){ + again = true; + const html = await this.getHTMLFromID(nextid); + fragment.appendChild(html); + this.HTMLElements.push([html, nextid]); + this.scrollBottom += this.averageheight; + } + } + if(scrollBottom > this.maxDist){ + const html = this.HTMLElements.pop(); + if(html){ + await this.destroyFromID(html[1]); + this.scrollBottom -= this.averageheight; + again = true; + } + } + if(again){ + await this.watchForBottom(true, fragment); + } + return again; + }finally{ + if(!already){ + this.div.append(fragment); + if(func){ func(); - }).observe(scroll); + } + } + } + } - new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); + async watchForChange(): Promise{ + if(this.changePromise){ + this.watchtime = true; + return await this.changePromise; + }else{ + this.watchtime = false; + } - await this.firstElement(initialId); - this.updatestuff(); - await this.watchForChange().then(() => { - this.updatestuff(); - this.beenloaded = true; - }); - - return scroll; + this.changePromise = new Promise(async res=>{ + try{ + if(!this.div){ + res(false); + return false; + } + const out = (await Promise.allSettled([ + this.watchForTop(), + this.watchForBottom(), + ])) as { value: boolean }[]; + const changed = out[0].value || out[1].value; + if(this.timeout === null && changed){ + 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; + if(this.watchtime){ + this.watchForChange(); } + }, 300); + } + }); - checkscroll(): void { - if (this.beenloaded && this.div && !document.body.contains(this.div)) { - console.warn("not in document"); - this.div = null; - } - } + return await this.changePromise; + } - async updatestuff(): Promise { - this.timeout = null; - if (!this.div) return; + async focus(id: string, flash = true): Promise{ + let element: HTMLElement | undefined; + for(const thing of this.HTMLElements){ + if(thing[1] === id){ + element = thing[0]; + } + } + if(element){ + if(flash){ + element.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + await new Promise(resolve=>setTimeout(resolve, 1000)); + element.classList.remove("jumped"); + await new Promise(resolve=>setTimeout(resolve, 100)); + element.classList.add("jumped"); + }else{ + element.scrollIntoView(); + } + }else{ + for(const thing of this.HTMLElements){ + await this.destroyFromID(thing[1]); + } + this.HTMLElements = []; + await this.firstElement(id); + this.updatestuff(); + await this.watchForChange(); + await new Promise(resolve=>setTimeout(resolve, 100)); + await this.focus(id, true); + } + } - this.scrollBottom = - this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight; - this.averageheight = this.div.scrollHeight / this.HTMLElements.length; - if (this.averageheight < 10) { - this.averageheight = 60; - } - this.scrollTop = this.div.scrollTop; + async delete(): Promise{ + if(this.div){ + this.div.remove(); + this.div = null; + } + try{ + for(const thing of this.HTMLElements){ + await this.destroyFromID(thing[1]); + } + }catch(e){ + console.error(e); + } + this.HTMLElements = []; + if(this.timeout){ + clearTimeout(this.timeout); + } + } +} - if (!this.scrollBottom && !(await this.watchForChange())) { - this.reachesBottom(); - } - if (!this.scrollTop) { - await this.watchForChange(); - } - this.needsupdate = false; - } - - async firstElement(id: string): Promise { - if (!this.div) return; - const html = await this.getHTMLFromID(id); - this.div.appendChild(html); - this.HTMLElements.push([html, id]); - } - - async addedBottom(): Promise { - await this.updatestuff(); - const func = this.snapBottom(); - await this.watchForChange(); - func(); - } - - snapBottom(): () => void { - const scrollBottom = this.scrollBottom; - return () => { - if (this.div && scrollBottom < 4) { - this.div.scrollTop = this.div.scrollHeight; - } - }; - } - - private async watchForTop( - already = false, - fragment = new DocumentFragment() - ): Promise { - if (!this.div) return false; - try { - let again = false; - if (this.scrollTop < (already ? this.fillDist : this.minDist)) { - let nextid: string | undefined; - const firstelm = this.HTMLElements.at(0); - if (firstelm) { - const previd = firstelm[1]; - nextid = await this.getIDFromOffset(previd, 1); - } - - if (nextid) { - const html = await this.getHTMLFromID(nextid); - if (!html) { - this.destroyFromID(nextid); - return false; - } - again = true; - fragment.prepend(html); - this.HTMLElements.unshift([html, nextid]); - this.scrollTop += this.averageheight; - } - } - if (this.scrollTop > this.maxDist) { - const html = this.HTMLElements.shift(); - if (html) { - again = true; - await this.destroyFromID(html[1]); - this.scrollTop -= this.averageheight; - } - } - if (again) { - await this.watchForTop(true, fragment); - } - return again; - } finally { - if (!already) { - if (this.div.scrollTop === 0) { - this.scrollTop = 1; - this.div.scrollTop = 10; - } - this.div.prepend(fragment, fragment); - } - } - } - - async watchForBottom( - already = false, - fragment = new DocumentFragment() - ): Promise { - let func: Function | undefined; - if (!already) func = this.snapBottom(); - if (!this.div) return false; - try { - let again = false; - const scrollBottom = this.scrollBottom; - if (scrollBottom < (already ? this.fillDist : this.minDist)) { - let nextid: string | undefined; - const lastelm = this.HTMLElements.at(-1); - if (lastelm) { - const previd = lastelm[1]; - nextid = await this.getIDFromOffset(previd, -1); - } - if (nextid) { - again = true; - const html = await this.getHTMLFromID(nextid); - fragment.appendChild(html); - this.HTMLElements.push([html, nextid]); - this.scrollBottom += this.averageheight; - } - } - if (scrollBottom > this.maxDist) { - const html = this.HTMLElements.pop(); - if (html) { - await this.destroyFromID(html[1]); - this.scrollBottom -= this.averageheight; - again = true; - } - } - if (again) { - await this.watchForBottom(true, fragment); - } - return again; - } finally { - if (!already) { - this.div.append(fragment); - if (func) { - func(); - } - } - } - } - - async watchForChange(): Promise { - if (this.changePromise) { - this.watchtime = true; - return await this.changePromise; - } else { - this.watchtime = false; - } - - this.changePromise = new Promise(async (res) => { - try { - if (!this.div) { - res(false); - return false; - } - const out = (await Promise.allSettled([ - this.watchForTop(), - this.watchForBottom(), - ])) as { value: boolean }[]; - const changed = out[0].value || out[1].value; - if (this.timeout === null && changed) { - 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; - if (this.watchtime) { - this.watchForChange(); - } - }, 300); - } - }); - - return await this.changePromise; - } - - async focus(id: string, flash = true): Promise { - let element: HTMLElement | undefined; - for (const thing of this.HTMLElements) { - if (thing[1] === id) { - element = thing[0]; - } - } - if (element) { - if (flash) { - element.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - await new Promise((resolve) => setTimeout(resolve, 1000)); - element.classList.remove("jumped"); - await new Promise((resolve) => setTimeout(resolve, 100)); - element.classList.add("jumped"); - } else { - element.scrollIntoView(); - } - } else { - for (const thing of this.HTMLElements) { - await this.destroyFromID(thing[1]); - } - this.HTMLElements = []; - await this.firstElement(id); - this.updatestuff(); - await this.watchForChange(); - await new Promise((resolve) => setTimeout(resolve, 100)); - await this.focus(id, true); - } - } - - async delete(): Promise { - if (this.div) { - this.div.remove(); - this.div = null; - } - try { - for (const thing of this.HTMLElements) { - await this.destroyFromID(thing[1]); - } - } catch (e) { - console.error(e); - } - this.HTMLElements = []; - if (this.timeout) { - clearTimeout(this.timeout); - } - } - } - - export { InfiniteScroller }; +export{ InfiniteScroller }; diff --git a/src/webpage/invite.ts b/src/webpage/invite.ts index 4dfc19e..f2e6805 100644 --- a/src/webpage/invite.ts +++ b/src/webpage/invite.ts @@ -1,147 +1,147 @@ -import { getBulkUsers, Specialuser, getapiurls } from "./login.js"; +import{ getBulkUsers, Specialuser, getapiurls }from"./login.js"; -(async () => { -const users = getBulkUsers(); -const well = new URLSearchParams(window.location.search).get("instance"); -const joinable: Specialuser[] = []; +(async ()=>{ + const users = getBulkUsers(); + const well = new URLSearchParams(window.location.search).get("instance"); + const joinable: Specialuser[] = []; -for (const key in users.users) { -if (Object.prototype.hasOwnProperty.call(users.users, key)) { -const user: Specialuser = users.users[key]; -if (well && user.serverurls.wellknown.includes(well)) { -joinable.push(user); -} -console.log(user); -} -} + for(const key in users.users){ + if(Object.prototype.hasOwnProperty.call(users.users, key)){ + const user: Specialuser = users.users[key]; + if(well && user.serverurls.wellknown.includes(well)){ + joinable.push(user); + } + console.log(user); + } + } -let urls: { api: string; cdn: string } | undefined; + let urls: { api: string; cdn: string } | undefined; -if (!joinable.length && well) { -const out = await getapiurls(well); -if (out) { -urls = out; -for (const key in users.users) { -if (Object.prototype.hasOwnProperty.call(users.users, key)) { -const user: Specialuser = users.users[key]; -if (user.serverurls.api.includes(out.api)) { -joinable.push(user); -} -console.log(user); -} -} -} else { -throw new Error( -"Someone needs to handle the case where the servers don't exist" -); -} -} else { -urls = joinable[0].serverurls; -} + if(!joinable.length && well){ + const out = await getapiurls(well); + if(out){ + urls = out; + for(const key in users.users){ + if(Object.prototype.hasOwnProperty.call(users.users, key)){ + const user: Specialuser = users.users[key]; + if(user.serverurls.api.includes(out.api)){ + joinable.push(user); + } + console.log(user); + } + } + }else{ + throw new Error( + "Someone needs to handle the case where the servers don't exist" + ); + } + }else{ + urls = joinable[0].serverurls; + } -if (!joinable.length) { + if(!joinable.length){ document.getElementById("AcceptInvite")!.textContent = "Create an account to accept the invite"; -} + } -const code = window.location.pathname.split("/")[2]; -let guildinfo: any; + const code = window.location.pathname.split("/")[2]; + let guildinfo: any; -fetch(`${urls!.api}/invites/${code}`, { -method: "GET", -}) -.then((response) => response.json()) -.then((json) => { -const guildjson = json.guild; -guildinfo = guildjson; + fetch(`${urls!.api}/invites/${code}`, { + method: "GET", + }) + .then(response=>response.json()) + .then(json=>{ + const guildjson = json.guild; + guildinfo = guildjson; document.getElementById("invitename")!.textContent = guildjson.name; document.getElementById( -"invitedescription" + "invitedescription" )!.textContent = `${json.inviter.username} invited you to join ${guildjson.name}`; -if (guildjson.icon) { -const img = document.createElement("img"); -img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; -img.classList.add("inviteGuild"); +if(guildjson.icon){ + const img = document.createElement("img"); + img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; + img.classList.add("inviteGuild"); document.getElementById("inviteimg")!.append(img); -} else { -const txt = guildjson.name -.replace(/'s /g, " ") -.replace(/\w+/g, (word: any[]) => word[0]) -.replace(/\s/g, ""); -const div = document.createElement("div"); -div.textContent = txt; -div.classList.add("inviteGuild"); +}else{ + const txt = guildjson.name + .replace(/'s /g, " ") + .replace(/\w+/g, (word: any[])=>word[0]) + .replace(/\s/g, ""); + const div = document.createElement("div"); + div.textContent = txt; + div.classList.add("inviteGuild"); document.getElementById("inviteimg")!.append(div); } -}); + }); -function showAccounts(): void { -const table = document.createElement("dialog"); -for (const user of joinable) { -console.log(user.pfpsrc); + function showAccounts(): void{ + const table = document.createElement("dialog"); + for(const user of joinable){ + console.log(user.pfpsrc); -const userinfo = document.createElement("div"); -userinfo.classList.add("flexltr", "switchtable"); + const userinfo = document.createElement("div"); + userinfo.classList.add("flexltr", "switchtable"); -const pfp = document.createElement("img"); -pfp.src = user.pfpsrc; -pfp.classList.add("pfp"); -userinfo.append(pfp); + const pfp = document.createElement("img"); + pfp.src = user.pfpsrc; + pfp.classList.add("pfp"); + userinfo.append(pfp); -const userDiv = document.createElement("div"); -userDiv.classList.add("userinfo"); -userDiv.textContent = user.username; -userDiv.append(document.createElement("br")); + const userDiv = document.createElement("div"); + userDiv.classList.add("userinfo"); + userDiv.textContent = user.username; + userDiv.append(document.createElement("br")); -const span = document.createElement("span"); -span.textContent = user.serverurls.wellknown -.replace("https://", "") -.replace("http://", ""); -span.classList.add("serverURL"); -userDiv.append(span); + const span = document.createElement("span"); + span.textContent = user.serverurls.wellknown + .replace("https://", "") + .replace("http://", ""); + span.classList.add("serverURL"); + userDiv.append(span); -userinfo.append(userDiv); -table.append(userinfo); + userinfo.append(userDiv); + table.append(userinfo); -userinfo.addEventListener("click", () => { -console.log(user); -fetch(`${urls!.api}/invites/${code}`, { -method: "POST", -headers: { -Authorization: user.token, -}, -}).then(() => { -users.currentuser = user.uid; -localStorage.setItem("userinfos", JSON.stringify(users)); -window.location.href = "/channels/" + guildinfo.id; -}); -}); -} + userinfo.addEventListener("click", ()=>{ + console.log(user); + fetch(`${urls!.api}/invites/${code}`, { + method: "POST", + headers: { + Authorization: user.token, + }, + }).then(()=>{ + users.currentuser = user.uid; + localStorage.setItem("userinfos", JSON.stringify(users)); + window.location.href = "/channels/" + guildinfo.id; + }); + }); + } -const td = document.createElement("div"); -td.classList.add("switchtable"); -td.textContent = "Login or create an account ⇌"; -td.addEventListener("click", () => { -const l = new URLSearchParams("?"); -l.set("goback", window.location.href); -l.set("instance", well!); -window.location.href = "/login?" + l.toString(); -}); + const td = document.createElement("div"); + td.classList.add("switchtable"); + td.textContent = "Login or create an account ⇌"; + td.addEventListener("click", ()=>{ + const l = new URLSearchParams("?"); + l.set("goback", window.location.href); + l.set("instance", well!); + window.location.href = "/login?" + l.toString(); + }); -if (!joinable.length) { -const l = new URLSearchParams("?"); -l.set("goback", window.location.href); -l.set("instance", well!); -window.location.href = "/login?" + l.toString(); -} + if(!joinable.length){ + const l = new URLSearchParams("?"); + l.set("goback", window.location.href); + l.set("instance", well!); + window.location.href = "/login?" + l.toString(); + } -table.append(td); -table.classList.add("accountSwitcher"); -console.log(table); -document.body.append(table); -} + table.append(td); + table.classList.add("accountSwitcher"); + console.log(table); + document.body.append(table); + } document -.getElementById("AcceptInvite")! -.addEventListener("click", showAccounts); + .getElementById("AcceptInvite")! + .addEventListener("click", showAccounts); })(); diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 6438ff1..387fdd9 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -479,23 +479,23 @@ chunk_index: number; chunk_count: number; not_found: string[]; }; -export { -readyjson, -dirrectjson, -startTypingjson, -channeljson, -guildjson, -rolesjson, -userjson, -memberjson, -mainuserjson, -messagejson, -filejson, -embedjson, -emojijson, -presencejson, -wsjson, -messageCreateJson, -memberChunk, -invitejson, +export{ + readyjson, + dirrectjson, + startTypingjson, + channeljson, + guildjson, + rolesjson, + userjson, + memberjson, + mainuserjson, + messagejson, + filejson, + embedjson, + emojijson, + presencejson, + wsjson, + messageCreateJson, + memberChunk, + invitejson, }; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 40caa46..3a49410 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -1,1824 +1,1824 @@ -import { Guild } from "./guild.js"; -import { Channel } from "./channel.js"; -import { Direct } from "./direct.js"; -import { Voice } from "./audio.js"; -import { User } from "./user.js"; -import { Dialog } from "./dialog.js"; -import { getapiurls, getBulkInfo, setTheme, Specialuser } from "./login.js"; -import { - channeljson, - guildjson, - memberjson, - messageCreateJson, - presencejson, - readyjson, - startTypingjson, - wsjson, -} from "./jsontypes.js"; -import { Member } from "./member.js"; -import { FormError, Settings } from "./settings.js"; -import { MarkDown } from "./markdown.js"; +import{ Guild }from"./guild.js"; +import{ Channel }from"./channel.js"; +import{ Direct }from"./direct.js"; +import{ Voice }from"./audio.js"; +import{ User }from"./user.js"; +import{ Dialog }from"./dialog.js"; +import{ getapiurls, getBulkInfo, setTheme, Specialuser }from"./login.js"; +import{ + channeljson, + guildjson, + memberjson, + messageCreateJson, + presencejson, + readyjson, + startTypingjson, + wsjson, +}from"./jsontypes.js"; +import{ Member }from"./member.js"; +import{ FormError, Settings }from"./settings.js"; +import{ MarkDown }from"./markdown.js"; const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); -class Localuser { - badges: Map< +class Localuser{ + badges: Map< string, { id: string; description: string; icon: string; link: string } > = new Map(); - lastSequence: number | null = null; - token!: string; - userinfo!: Specialuser; - serverurls!: Specialuser["serverurls"]; - initialized!: boolean; - info!: Specialuser["serverurls"]; - headers!: { "Content-type": string; Authorization: string }; - userConnections!: Dialog; - devPortal!: Dialog; - ready!: readyjson; - guilds!: Guild[]; - guildids: Map = new Map(); - user!: User; - status!: string; - channelfocus: Channel | undefined; - lookingguild: Guild | undefined; - guildhtml: Map = new Map(); - ws: WebSocket | undefined; - connectionSucceed = 0; - errorBackoff = 0; - channelids: Map = new Map(); - readonly userMap: Map = new Map(); - instancePing = { - name: "Unknown", - }; - mfa_enabled!: boolean; - get perminfo() { - return this.userinfo.localuserStore; - } - set perminfo(e) { - this.userinfo.localuserStore = e; - } - constructor(userinfo: Specialuser | -1) { - if (userinfo === -1) { - return; - } - this.token = userinfo.token; - this.userinfo = userinfo; - this.perminfo.guilds ??= {}; - this.serverurls = this.userinfo.serverurls; - this.initialized = false; - this.info = this.serverurls; - this.headers = { - "Content-type": "application/json; charset=UTF-8", - Authorization: this.userinfo.token, - }; - } - gottenReady(ready: readyjson): void { - this.initialized = true; - this.ready = ready; - this.guilds = []; - this.guildids = new Map(); - this.user = new User(ready.d.user, this); - this.user.setstatus("online"); - this.mfa_enabled = ready.d.user.mfa_enabled as boolean; - this.userinfo.username = this.user.username; - this.userinfo.pfpsrc = this.user.getpfpsrc(); - this.status = this.ready.d.user_settings.status; - this.channelfocus = undefined; - this.lookingguild = undefined; - this.guildhtml = new Map(); - const members: { [key: string]: memberjson } = {}; - for (const thing of ready.d.merged_members) { - members[thing[0].guild_id] = thing[0]; - } + lastSequence: number | null = null; + token!: string; + userinfo!: Specialuser; + serverurls!: Specialuser["serverurls"]; + initialized!: boolean; + info!: Specialuser["serverurls"]; + headers!: { "Content-type": string; Authorization: string }; + userConnections!: Dialog; + devPortal!: Dialog; + ready!: readyjson; + guilds!: Guild[]; + guildids: Map = new Map(); + user!: User; + status!: string; + channelfocus: Channel | undefined; + lookingguild: Guild | undefined; + guildhtml: Map = new Map(); + ws: WebSocket | undefined; + connectionSucceed = 0; + errorBackoff = 0; + channelids: Map = new Map(); + readonly userMap: Map = new Map(); + instancePing = { + name: "Unknown", + }; + mfa_enabled!: boolean; + get perminfo(){ + return this.userinfo.localuserStore; + } + set perminfo(e){ + this.userinfo.localuserStore = e; + } + constructor(userinfo: Specialuser | -1){ + if(userinfo === -1){ + return; + } + this.token = userinfo.token; + this.userinfo = userinfo; + this.perminfo.guilds ??= {}; + this.serverurls = this.userinfo.serverurls; + this.initialized = false; + this.info = this.serverurls; + this.headers = { + "Content-type": "application/json; charset=UTF-8", + Authorization: this.userinfo.token, + }; + } + gottenReady(ready: readyjson): void{ + this.initialized = true; + this.ready = ready; + this.guilds = []; + this.guildids = new Map(); + this.user = new User(ready.d.user, this); + this.user.setstatus("online"); + this.mfa_enabled = ready.d.user.mfa_enabled as boolean; + this.userinfo.username = this.user.username; + this.userinfo.pfpsrc = this.user.getpfpsrc(); + this.status = this.ready.d.user_settings.status; + this.channelfocus = undefined; + this.lookingguild = undefined; + this.guildhtml = new Map(); + const members: { [key: string]: memberjson } = {}; + for(const thing of ready.d.merged_members){ + members[thing[0].guild_id] = thing[0]; + } - for (const thing of ready.d.guilds) { - const temp = new Guild(thing, this, members[thing.id]); - this.guilds.push(temp); - this.guildids.set(temp.id, temp); - } - { - const temp = new Direct(ready.d.private_channels, this); - this.guilds.push(temp); - this.guildids.set(temp.id, temp); - } - console.log(ready.d.user_guild_settings.entries); + for(const thing of ready.d.guilds){ + const temp = new Guild(thing, this, members[thing.id]); + this.guilds.push(temp); + this.guildids.set(temp.id, temp); + } + { + const temp = new Direct(ready.d.private_channels, this); + this.guilds.push(temp); + this.guildids.set(temp.id, temp); + } + console.log(ready.d.user_guild_settings.entries); - for (const thing of ready.d.user_guild_settings.entries) { - (this.guildids.get(thing.guild_id) as Guild).notisetting(thing); - } + for(const thing of ready.d.user_guild_settings.entries){ + (this.guildids.get(thing.guild_id) as Guild).notisetting(thing); + } - for (const thing of ready.d.read_state.entries) { - const channel = this.channelids.get(thing.channel_id); - if (!channel) { - continue; - } - channel.readStateInfo(thing); - } - for (const thing of ready.d.relationships) { - const user = new User(thing.user, this); - user.nickname = thing.nickname; - user.relationshipType = thing.type; - } + for(const thing of ready.d.read_state.entries){ + const channel = this.channelids.get(thing.channel_id); + if(!channel){ + continue; + } + channel.readStateInfo(thing); + } + for(const thing of ready.d.relationships){ + const user = new User(thing.user, this); + user.nickname = thing.nickname; + user.relationshipType = thing.type; + } - this.pingEndpoint(); - this.userinfo.updateLocal(); - } - outoffocus(): void { - const servers = document.getElementById("servers") as HTMLDivElement; - servers.innerHTML = ""; - const channels = document.getElementById("channels") as HTMLDivElement; - channels.innerHTML = ""; - if (this.channelfocus) { - this.channelfocus.infinite.delete(); - } - this.lookingguild = undefined; - this.channelfocus = undefined; - } - unload(): void { - this.initialized = false; - this.outoffocus(); - this.guilds = []; - this.guildids = new Map(); - if (this.ws) { - this.ws.close(4001); - } - } - swapped = false; - async initwebsocket(): Promise { - let returny: () => void; - const ws = new WebSocket( - this.serverurls.gateway.toString() + + this.pingEndpoint(); + this.userinfo.updateLocal(); + } + outoffocus(): void{ + const servers = document.getElementById("servers") as HTMLDivElement; + servers.innerHTML = ""; + const channels = document.getElementById("channels") as HTMLDivElement; + channels.innerHTML = ""; + if(this.channelfocus){ + this.channelfocus.infinite.delete(); + } + this.lookingguild = undefined; + this.channelfocus = undefined; + } + unload(): void{ + this.initialized = false; + this.outoffocus(); + this.guilds = []; + this.guildids = new Map(); + if(this.ws){ + this.ws.close(4001); + } + } + swapped = false; + async initwebsocket(): Promise{ + let returny: () => void; + const ws = new WebSocket( + this.serverurls.gateway.toString() + "?encoding=json&v=9" + (DecompressionStream ? "&compress=zlib-stream" : "") - ); - this.ws = ws; - let ds: DecompressionStream; - let w: WritableStreamDefaultWriter; - let r: ReadableStreamDefaultReader; - let arr: Uint8Array; - let build = ""; - if (DecompressionStream) { - ds = new DecompressionStream("deflate"); - w = ds.writable.getWriter(); - r = ds.readable.getReader(); - arr = new Uint8Array(); - } - const promise = new Promise((res) => { - returny = res; - ws.addEventListener("open", (_event) => { - console.log("WebSocket connected"); - ws.send( - JSON.stringify({ - op: 2, - d: { - token: this.token, - capabilities: 16381, - properties: { - browser: "Jank Client", - client_build_number: 0, //might update this eventually lol - release_channel: "Custom", - browser_user_agent: navigator.userAgent, - }, - compress: Boolean(DecompressionStream), - presence: { - status: "online", - since: null, //new Date().getTime() - activities: [], - afk: false, - }, - }, - }) - ); - }); - const textdecode = new TextDecoder(); - if (DecompressionStream) { - (async () => { - while (true) { - const read = await r.read(); - const data = textdecode.decode(read.value); - build += data; - try { - const temp = JSON.parse(build); - build = ""; - if (temp.op === 0 && temp.t === "READY") { - returny(); - } - await this.handleEvent(temp); - } catch {} - } - })(); - } - }); + ); + this.ws = ws; + let ds: DecompressionStream; + let w: WritableStreamDefaultWriter; + let r: ReadableStreamDefaultReader; + let arr: Uint8Array; + let build = ""; + if(DecompressionStream){ + ds = new DecompressionStream("deflate"); + w = ds.writable.getWriter(); + r = ds.readable.getReader(); + arr = new Uint8Array(); + } + const promise = new Promise(res=>{ + returny = res; + ws.addEventListener("open", _event=>{ + console.log("WebSocket connected"); + ws.send( + JSON.stringify({ + op: 2, + d: { + token: this.token, + capabilities: 16381, + properties: { + browser: "Jank Client", + client_build_number: 0, //might update this eventually lol + release_channel: "Custom", + browser_user_agent: navigator.userAgent, + }, + compress: Boolean(DecompressionStream), + presence: { + status: "online", + since: null, //new Date().getTime() + activities: [], + afk: false, + }, + }, + }) + ); + }); + const textdecode = new TextDecoder(); + if(DecompressionStream){ + (async ()=>{ + while(true){ + const read = await r.read(); + const data = textdecode.decode(read.value); + build += data; + try{ + const temp = JSON.parse(build); + build = ""; + if(temp.op === 0 && temp.t === "READY"){ + returny(); + } + await this.handleEvent(temp); + }catch{} + } + })(); + } + }); - let order = new Promise((res) => res()); + let order = new Promise(res=>res()); - ws.addEventListener("message", async (event) => { - const temp2 = order; - order = new Promise(async (res) => { - await temp2; - let temp: { op: number; t: string }; - try { - if (event.data instanceof Blob) { - const buff = await event.data.arrayBuffer(); - const array = new Uint8Array(buff); + ws.addEventListener("message", async event=>{ + const temp2 = order; + order = new Promise(async res=>{ + await temp2; + let temp: { op: number; t: string }; + try{ + if(event.data instanceof Blob){ + const buff = await event.data.arrayBuffer(); + const array = new Uint8Array(buff); - const temparr = new Uint8Array(array.length + arr.length); - temparr.set(arr, 0); - temparr.set(array, arr.length); - arr = temparr; + const temparr = new Uint8Array(array.length + arr.length); + temparr.set(arr, 0); + temparr.set(array, arr.length); + arr = temparr; - const len = array.length; - if ( - !( - array[len - 1] === 255 && + const len = array.length; + if( + !( + array[len - 1] === 255 && array[len - 2] === 255 && array[len - 3] === 0 && array[len - 4] === 0 - ) - ) { - return; - } - w.write(arr.buffer); - arr = new Uint8Array(); - return; //had to move the while loop due to me being dumb - } else { - temp = JSON.parse(event.data); - } - if (temp.op === 0 && temp.t === "READY") { - returny(); - } - await this.handleEvent(temp as readyjson); - } catch (e) { - console.error(e); - } finally { - res(); - } - }); - }); + ) + ){ + return; + } + w.write(arr.buffer); + arr = new Uint8Array(); + return; //had to move the while loop due to me being dumb + }else{ + temp = JSON.parse(event.data); + } + if(temp.op === 0 && temp.t === "READY"){ + returny(); + } + await this.handleEvent(temp as readyjson); + }catch(e){ + console.error(e); + }finally{ + res(); + } + }); + }); - ws.addEventListener("close", async (event) => { - this.ws = undefined; - console.log("WebSocket closed with code " + event.code); + ws.addEventListener("close", async event=>{ + this.ws = undefined; + console.log("WebSocket closed with code " + event.code); - this.unload(); - (document.getElementById("loading") as HTMLElement).classList.remove( - "doneloading" - ); - (document.getElementById("loading") as HTMLElement).classList.add( - "loading" - ); - this.fetchingmembers = new Map(); - this.noncemap = new Map(); - this.noncebuild = new Map(); - if ( - (event.code > 1000 && event.code < 1016) || + this.unload(); + (document.getElementById("loading") as HTMLElement).classList.remove( + "doneloading" + ); + (document.getElementById("loading") as HTMLElement).classList.add( + "loading" + ); + this.fetchingmembers = new Map(); + this.noncemap = new Map(); + this.noncebuild = new Map(); + if( + (event.code > 1000 && event.code < 1016) || wsCodesRetry.has(event.code) - ) { - if ( - this.connectionSucceed !== 0 && + ){ + if( + this.connectionSucceed !== 0 && Date.now() > this.connectionSucceed + 20000 - ) - this.errorBackoff = 0; - else this.errorBackoff++; - this.connectionSucceed = 0; + ) + this.errorBackoff = 0; + else this.errorBackoff++; + this.connectionSucceed = 0; - (document.getElementById("load-desc") as HTMLElement).innerHTML = + (document.getElementById("load-desc") as HTMLElement).innerHTML = "Unable to connect to the Spacebar server, retrying in " + Math.round(0.2 + this.errorBackoff * 2.8) + " seconds..."; - switch ( - this.errorBackoff //try to recover from bad domain - ) { - case 3: - const newurls = await getapiurls(this.info.wellknown); - if (newurls) { - this.info = newurls; - this.serverurls = newurls; - this.userinfo.json.serverurls = this.info; - this.userinfo.updateLocal(); - break; - } - break; + switch( + this.errorBackoff //try to recover from bad domain + ){ + case 3: + const newurls = await getapiurls(this.info.wellknown); + if(newurls){ + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + break; + } + break; - case 4: { - const newurls = await getapiurls( - new URL(this.info.wellknown).origin - ); - if (newurls) { - this.info = newurls; - this.serverurls = newurls; - this.userinfo.json.serverurls = this.info; - this.userinfo.updateLocal(); - break; - } - break; - } - case 5: { - const breakappart = new URL(this.info.wellknown).origin.split("."); - const url = + case 4: { + const newurls = await getapiurls( + new URL(this.info.wellknown).origin + ); + if(newurls){ + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + break; + } + break; + } + case 5: { + const breakappart = new URL(this.info.wellknown).origin.split("."); + const url = "https://" + breakappart.at(-2) + "." + breakappart.at(-1); - const newurls = await getapiurls(url); - if (newurls) { - this.info = newurls; - this.serverurls = newurls; - this.userinfo.json.serverurls = this.info; - this.userinfo.updateLocal(); - } - break; - } - } - setTimeout(() => { - if (this.swapped) return; - (document.getElementById("load-desc") as HTMLElement).textContent = + const newurls = await getapiurls(url); + if(newurls){ + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + } + break; + } + } + setTimeout(()=>{ + if(this.swapped)return; + (document.getElementById("load-desc") as HTMLElement).textContent = "Retrying..."; - this.initwebsocket().then(() => { - this.loaduser(); - this.init(); - const loading = document.getElementById("loading") as HTMLElement; - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); - }, 200 + this.errorBackoff * 2800); - } else - (document.getElementById("load-desc") as HTMLElement).textContent = + this.initwebsocket().then(()=>{ + this.loaduser(); + this.init(); + const loading = document.getElementById("loading") as HTMLElement; + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + }, 200 + this.errorBackoff * 2800); + }else + (document.getElementById("load-desc") as HTMLElement).textContent = "Unable to connect to the Spacebar server. Please try logging out and back in."; - }); + }); - await promise; - } - async handleEvent(temp: wsjson) { - console.debug(temp); - if (temp.s) this.lastSequence = temp.s; - if (temp.op == 0) { - switch (temp.t) { - case "MESSAGE_CREATE": - if (this.initialized) { - this.messageCreate(temp); - } - break; - case "MESSAGE_DELETE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.id); - if (!message) break; - message.deleteEvent(); - break; - } - case "READY": - this.gottenReady(temp as readyjson); - break; - case "MESSAGE_UPDATE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.id); - if (!message) break; - message.giveData(temp.d); - break; - } - case "TYPING_START": - if (this.initialized) { - this.typingStart(temp); - } - break; - case "USER_UPDATE": - if (this.initialized) { - const users = this.userMap.get(temp.d.id); - if (users) { - users.userupdate(temp.d); - } - } - break; - case "CHANNEL_UPDATE": - if (this.initialized) { - this.updateChannel(temp.d); - } - break; - case "CHANNEL_CREATE": - if (this.initialized) { - this.createChannel(temp.d); - } - break; - case "CHANNEL_DELETE": - if (this.initialized) { - this.delChannel(temp.d); - } - break; - case "GUILD_DELETE": { - const guildy = this.guildids.get(temp.d.id); - if (guildy) { - this.guildids.delete(temp.d.id); - this.guilds.splice(this.guilds.indexOf(guildy), 1); - guildy.html.remove(); - } - break; - } - case "GUILD_CREATE": { - const guildy = new Guild(temp.d, this, this.user); - this.guilds.push(guildy); - this.guildids.set(guildy.id, guildy); - (document.getElementById("servers") as HTMLDivElement).insertBefore( - guildy.generateGuildIcon(), - document.getElementById("bottomseparator") - ); - break; - } - case "MESSAGE_REACTION_ADD": - { - temp.d.guild_id ??= "@me"; - const guild = this.guildids.get(temp.d.guild_id); - if (!guild) break; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - let thing: Member | { id: string }; - if (temp.d.member) { - thing = (await Member.new(temp.d.member, guild)) as Member; - } else { - thing = { id: temp.d.user_id }; - } - message.reactionAdd(temp.d.emoji, thing); - } - break; - case "MESSAGE_REACTION_REMOVE": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - message.reactionRemove(temp.d.emoji, temp.d.user_id); - } - break; - case "MESSAGE_REACTION_REMOVE_ALL": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - message.reactionRemoveAll(); - } - break; - case "MESSAGE_REACTION_REMOVE_EMOJI": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - message.reactionRemoveEmoji(temp.d.emoji); - } - break; - case "GUILD_MEMBERS_CHUNK": - this.gotChunk(temp.d); - break; - } - } else if (temp.op === 10) { - if (!this.ws) return; - console.log("heartbeat down"); - this.heartbeat_interval = temp.d.heartbeat_interval; - this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); - } else if (temp.op === 11) { - setTimeout((_: any) => { - if (!this.ws) return; - if (this.connectionSucceed === 0) this.connectionSucceed = Date.now(); - this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); - }, this.heartbeat_interval); - } - } - heartbeat_interval: number = 0; - updateChannel(json: channeljson): void { - const guild = this.guildids.get(json.guild_id); - if (guild) { - guild.updateChannel(json); - if (json.guild_id === this.lookingguild?.id) { - this.loadGuild(json.guild_id); - } - } - } - createChannel(json: channeljson): undefined | Channel { - json.guild_id ??= "@me"; - const guild = this.guildids.get(json.guild_id); - if (!guild) return; - const channel = guild.createChannelpac(json); - if (json.guild_id === this.lookingguild?.id) { - this.loadGuild(json.guild_id); - } - if (channel.id === this.gotoid) { - guild.loadGuild(); - guild.loadChannel(channel.id); - this.gotoid = undefined; - } - return channel; // Add this line to return the 'channel' variable - } - gotoid: string | undefined; - async goToChannel(id: string) { - const channel = this.channelids.get(id); - if (channel) { - const guild = channel.guild; - guild.loadGuild(); - guild.loadChannel(id); - } else { - this.gotoid = id; - } - } - delChannel(json: channeljson): void { - let guild_id = json.guild_id; - guild_id ??= "@me"; - const guild = this.guildids.get(guild_id); - if (guild) { - guild.delChannel(json); - } + await promise; + } + async handleEvent(temp: wsjson){ + console.debug(temp); + if(temp.s)this.lastSequence = temp.s; + if(temp.op == 0){ + switch(temp.t){ + case"MESSAGE_CREATE": + if(this.initialized){ + this.messageCreate(temp); + } + break; + case"MESSAGE_DELETE": { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.id); + if(!message)break; + message.deleteEvent(); + break; + } + case"READY": + this.gottenReady(temp as readyjson); + break; + case"MESSAGE_UPDATE": { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.id); + if(!message)break; + message.giveData(temp.d); + break; + } + case"TYPING_START": + if(this.initialized){ + this.typingStart(temp); + } + break; + case"USER_UPDATE": + if(this.initialized){ + const users = this.userMap.get(temp.d.id); + if(users){ + users.userupdate(temp.d); + } + } + break; + case"CHANNEL_UPDATE": + if(this.initialized){ + this.updateChannel(temp.d); + } + break; + case"CHANNEL_CREATE": + if(this.initialized){ + this.createChannel(temp.d); + } + break; + case"CHANNEL_DELETE": + if(this.initialized){ + this.delChannel(temp.d); + } + break; + case"GUILD_DELETE": { + const guildy = this.guildids.get(temp.d.id); + if(guildy){ + this.guildids.delete(temp.d.id); + this.guilds.splice(this.guilds.indexOf(guildy), 1); + guildy.html.remove(); + } + break; + } + case"GUILD_CREATE": { + const guildy = new Guild(temp.d, this, this.user); + this.guilds.push(guildy); + this.guildids.set(guildy.id, guildy); + (document.getElementById("servers") as HTMLDivElement).insertBefore( + guildy.generateGuildIcon(), + document.getElementById("bottomseparator") + ); + break; + } + case"MESSAGE_REACTION_ADD": + { + temp.d.guild_id ??= "@me"; + const guild = this.guildids.get(temp.d.guild_id); + if(!guild)break; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + let thing: Member | { id: string }; + if(temp.d.member){ + thing = (await Member.new(temp.d.member, guild)) as Member; + }else{ + thing = { id: temp.d.user_id }; + } + message.reactionAdd(temp.d.emoji, thing); + } + break; + case"MESSAGE_REACTION_REMOVE": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + message.reactionRemove(temp.d.emoji, temp.d.user_id); + } + break; + case"MESSAGE_REACTION_REMOVE_ALL": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + message.reactionRemoveAll(); + } + break; + case"MESSAGE_REACTION_REMOVE_EMOJI": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if(!channel)break; + const message = channel.messages.get(temp.d.message_id); + if(!message)break; + message.reactionRemoveEmoji(temp.d.emoji); + } + break; + case"GUILD_MEMBERS_CHUNK": + this.gotChunk(temp.d); + break; + } + }else if(temp.op === 10){ + if(!this.ws)return; + console.log("heartbeat down"); + this.heartbeat_interval = temp.d.heartbeat_interval; + this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); + }else if(temp.op === 11){ + setTimeout((_: any)=>{ + if(!this.ws)return; + if(this.connectionSucceed === 0)this.connectionSucceed = Date.now(); + this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); + }, this.heartbeat_interval); + } + } + heartbeat_interval: number = 0; + updateChannel(json: channeljson): void{ + const guild = this.guildids.get(json.guild_id); + if(guild){ + guild.updateChannel(json); + if(json.guild_id === this.lookingguild?.id){ + this.loadGuild(json.guild_id); + } + } + } + createChannel(json: channeljson): undefined | Channel{ + json.guild_id ??= "@me"; + const guild = this.guildids.get(json.guild_id); + if(!guild)return; + const channel = guild.createChannelpac(json); + if(json.guild_id === this.lookingguild?.id){ + this.loadGuild(json.guild_id); + } + if(channel.id === this.gotoid){ + guild.loadGuild(); + guild.loadChannel(channel.id); + this.gotoid = undefined; + } + return channel; // Add this line to return the 'channel' variable + } + gotoid: string | undefined; + async goToChannel(id: string){ + const channel = this.channelids.get(id); + if(channel){ + const guild = channel.guild; + guild.loadGuild(); + guild.loadChannel(id); + }else{ + this.gotoid = id; + } + } + delChannel(json: channeljson): void{ + let guild_id = json.guild_id; + guild_id ??= "@me"; + const guild = this.guildids.get(guild_id); + if(guild){ + guild.delChannel(json); + } - if (json.guild_id === this.lookingguild?.id) { - this.loadGuild(json.guild_id); - } - } - init(): void { - const location = window.location.href.split("/"); - this.buildservers(); - if (location[3] === "channels") { - const guild = this.loadGuild(location[4]); - if (!guild) { - return; - } - guild.loadChannel(location[5]); - this.channelfocus = this.channelids.get(location[5]); - } - } - loaduser(): void { - (document.getElementById("username") as HTMLSpanElement).textContent = + if(json.guild_id === this.lookingguild?.id){ + this.loadGuild(json.guild_id); + } + } + init(): void{ + const location = window.location.href.split("/"); + this.buildservers(); + if(location[3] === "channels"){ + const guild = this.loadGuild(location[4]); + if(!guild){ + return; + } + guild.loadChannel(location[5]); + this.channelfocus = this.channelids.get(location[5]); + } + } + loaduser(): void{ + (document.getElementById("username") as HTMLSpanElement).textContent = this.user.username; - (document.getElementById("userpfp") as HTMLImageElement).src = + (document.getElementById("userpfp") as HTMLImageElement).src = this.user.getpfpsrc(); - (document.getElementById("status") as HTMLSpanElement).textContent = + (document.getElementById("status") as HTMLSpanElement).textContent = this.status; - } - isAdmin(): boolean { - if (this.lookingguild) { - return this.lookingguild.isAdmin(); - } else { - return false; - } - } - loadGuild(id: string): Guild | undefined { - let guild = this.guildids.get(id); - if (!guild) { - guild = this.guildids.get("@me"); - } - if (this.lookingguild === guild) { - return guild; - } - if (this.channelfocus) { - this.channelfocus.infinite.delete(); - this.channelfocus = undefined; - } - if (this.lookingguild) { - this.lookingguild.html.classList.remove("serveropen"); - } + } + isAdmin(): boolean{ + if(this.lookingguild){ + return this.lookingguild.isAdmin(); + }else{ + return false; + } + } + loadGuild(id: string): Guild | undefined{ + let guild = this.guildids.get(id); + if(!guild){ + guild = this.guildids.get("@me"); + } + if(this.lookingguild === guild){ + return guild; + } + if(this.channelfocus){ + this.channelfocus.infinite.delete(); + this.channelfocus = undefined; + } + if(this.lookingguild){ + this.lookingguild.html.classList.remove("serveropen"); + } - if (!guild) return; - if (guild.html) { - guild.html.classList.add("serveropen"); - } - this.lookingguild = guild; - (document.getElementById("serverName") as HTMLElement).textContent = + if(!guild)return; + if(guild.html){ + guild.html.classList.add("serveropen"); + } + this.lookingguild = guild; + (document.getElementById("serverName") as HTMLElement).textContent = guild.properties.name; - //console.log(this.guildids,id) - const channels = document.getElementById("channels") as HTMLDivElement; - channels.innerHTML = ""; - const html = guild.getHTML(); - channels.appendChild(html); - return guild; - } - buildservers(): void { - const serverlist = document.getElementById("servers") as HTMLDivElement; // - const outdiv = document.createElement("div"); - const home: any = document.createElement("span"); - const div = document.createElement("div"); - div.classList.add("home", "servericon"); + //console.log(this.guildids,id) + const channels = document.getElementById("channels") as HTMLDivElement; + channels.innerHTML = ""; + const html = guild.getHTML(); + channels.appendChild(html); + return guild; + } + buildservers(): void{ + const serverlist = document.getElementById("servers") as HTMLDivElement; // + const outdiv = document.createElement("div"); + const home: any = document.createElement("span"); + const div = document.createElement("div"); + div.classList.add("home", "servericon"); - home.classList.add("svgtheme", "svgicon", "svg-home"); - home["all"] = this.guildids.get("@me"); - (this.guildids.get("@me") as Guild).html = outdiv; - const unread = document.createElement("div"); - unread.classList.add("unread"); - outdiv.append(unread); - outdiv.append(div); - div.appendChild(home); + home.classList.add("svgtheme", "svgicon", "svg-home"); + home.all = this.guildids.get("@me"); + (this.guildids.get("@me") as Guild).html = outdiv; + const unread = document.createElement("div"); + unread.classList.add("unread"); + outdiv.append(unread); + outdiv.append(div); + div.appendChild(home); - outdiv.classList.add("servernoti"); - serverlist.append(outdiv); - home.onclick = function () { - this["all"].loadGuild(); - this["all"].loadChannel(); - }; - const sentdms = document.createElement("div"); - sentdms.classList.add("sentdms"); - serverlist.append(sentdms); - sentdms.id = "sentdms"; + outdiv.classList.add("servernoti"); + serverlist.append(outdiv); + home.onclick = function(){ + this.all.loadGuild(); + this.all.loadChannel(); + }; + const sentdms = document.createElement("div"); + sentdms.classList.add("sentdms"); + serverlist.append(sentdms); + sentdms.id = "sentdms"; - const br = document.createElement("hr"); - br.classList.add("lightbr"); - serverlist.appendChild(br); - for (const thing of this.guilds) { - if (thing instanceof Direct) { - (thing as Direct).unreaddms(); - continue; - } - const divy = thing.generateGuildIcon(); - serverlist.append(divy); - } - { - const br = document.createElement("hr"); - br.classList.add("lightbr"); - serverlist.appendChild(br); - br.id = "bottomseparator"; + const br = document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + for(const thing of this.guilds){ + if(thing instanceof Direct){ + (thing as Direct).unreaddms(); + continue; + } + const divy = thing.generateGuildIcon(); + serverlist.append(divy); + } + { + const br = document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + br.id = "bottomseparator"; - const div = document.createElement("div"); - div.textContent = "+"; - div.classList.add("home", "servericon"); - serverlist.appendChild(div); - div.onclick = (_) => { - this.createGuild(); - }; - const guilddsdiv = document.createElement("div"); - const guildDiscoveryContainer = document.createElement("span"); - guildDiscoveryContainer.classList.add( - "svgtheme", - "svgicon", - "svg-explore" - ); - guilddsdiv.classList.add("home", "servericon"); - guilddsdiv.appendChild(guildDiscoveryContainer); - serverlist.appendChild(guilddsdiv); - guildDiscoveryContainer.addEventListener("click", () => { - this.guildDiscovery(); - }); - } - this.unreads(); - } - createGuild() { - let inviteurl = ""; - const error = document.createElement("span"); - const fields: { name: string; icon: string | null } = { - name: "", - icon: null, - }; - const full = new Dialog([ - "tabs", - [ - [ - "Join using invite", - [ - "vdiv", - [ - "textbox", - "Invite Link/Code", - "", - function (this: HTMLInputElement) { - inviteurl = this.value; - }, - ], - ["html", error], - [ - "button", - "", - "Submit", - (_: any) => { - let parsed = ""; - if (inviteurl.includes("/")) { - parsed = + const div = document.createElement("div"); + div.textContent = "+"; + div.classList.add("home", "servericon"); + serverlist.appendChild(div); + div.onclick = _=>{ + this.createGuild(); + }; + const guilddsdiv = document.createElement("div"); + const guildDiscoveryContainer = document.createElement("span"); + guildDiscoveryContainer.classList.add( + "svgtheme", + "svgicon", + "svg-explore" + ); + guilddsdiv.classList.add("home", "servericon"); + guilddsdiv.appendChild(guildDiscoveryContainer); + serverlist.appendChild(guilddsdiv); + guildDiscoveryContainer.addEventListener("click", ()=>{ + this.guildDiscovery(); + }); + } + this.unreads(); + } + createGuild(){ + let inviteurl = ""; + const error = document.createElement("span"); + const fields: { name: string; icon: string | null } = { + name: "", + icon: null, + }; + const full = new Dialog([ + "tabs", + [ + [ + "Join using invite", + [ + "vdiv", + [ + "textbox", + "Invite Link/Code", + "", + function(this: HTMLInputElement){ + inviteurl = this.value; + }, + ], + ["html", error], + [ + "button", + "", + "Submit", + (_: any)=>{ + let parsed = ""; + if(inviteurl.includes("/")){ + parsed = inviteurl.split("/")[inviteurl.split("/").length - 1]; - } else { - parsed = inviteurl; - } - fetch(this.info.api + "/invites/" + parsed, { - method: "POST", - headers: this.headers, - }) - .then((r) => r.json()) - .then((_) => { - if (_.message) { - error.textContent = _.message; - } - }); - }, - ], - ], - ], - [ - "Create Guild", - [ - "vdiv", - ["title", "Create a guild"], - [ - "fileupload", - "Icon:", - function (event: Event) { - const target = event.target as HTMLInputElement; - if (!target.files) return; - const reader = new FileReader(); - reader.readAsDataURL(target.files[0]); - reader.onload = () => { - fields.icon = reader.result as string; - }; - }, - ], - [ - "textbox", - "Name:", - "", - function (this: HTMLInputElement, event: Event) { - const target = event.target as HTMLInputElement; - fields.name = target.value; - }, - ], - [ - "button", - "", - "submit", - () => { - this.makeGuild(fields).then((_) => { - if (_.message) { - alert(_.errors.name._errors[0].message); - } else { - full.hide(); - } - }); - }, - ], - ], - ], - ], - ]); - full.show(); - } - async makeGuild(fields: { name: string; icon: string | null }) { - return await ( - await fetch(this.info.api + "/guilds", { - method: "POST", - headers: this.headers, - body: JSON.stringify(fields), - }) - ).json(); - } - async guildDiscovery() { - const content = document.createElement("div"); - content.classList.add("guildy"); - content.textContent = "Loading..."; - const full = new Dialog(["html", content]); - full.show(); + }else{ + parsed = inviteurl; + } + fetch(this.info.api + "/invites/" + parsed, { + method: "POST", + headers: this.headers, + }) + .then(r=>r.json()) + .then(_=>{ + if(_.message){ + error.textContent = _.message; + } + }); + }, + ], + ], + ], + [ + "Create Guild", + [ + "vdiv", + ["title", "Create a guild"], + [ + "fileupload", + "Icon:", + function(event: Event){ + const target = event.target as HTMLInputElement; + if(!target.files)return; + const reader = new FileReader(); + reader.readAsDataURL(target.files[0]); + reader.onload = ()=>{ + fields.icon = reader.result as string; + }; + }, + ], + [ + "textbox", + "Name:", + "", + function(this: HTMLInputElement, event: Event){ + const target = event.target as HTMLInputElement; + fields.name = target.value; + }, + ], + [ + "button", + "", + "submit", + ()=>{ + this.makeGuild(fields).then(_=>{ + if(_.message){ + alert(_.errors.name._errors[0].message); + }else{ + full.hide(); + } + }); + }, + ], + ], + ], + ], + ]); + full.show(); + } + async makeGuild(fields: { name: string; icon: string | null }){ + return await ( + await fetch(this.info.api + "/guilds", { + method: "POST", + headers: this.headers, + body: JSON.stringify(fields), + }) + ).json(); + } + async guildDiscovery(){ + const content = document.createElement("div"); + content.classList.add("guildy"); + content.textContent = "Loading..."; + const full = new Dialog(["html", content]); + full.show(); - const res = await fetch(this.info.api + "/discoverable-guilds?limit=50", { - headers: this.headers, - }); - const json = await res.json(); + const res = await fetch(this.info.api + "/discoverable-guilds?limit=50", { + headers: this.headers, + }); + const json = await res.json(); - content.innerHTML = ""; - const title = document.createElement("h2"); - title.textContent = "Guild discovery (" + json.total + " entries)"; - content.appendChild(title); + content.innerHTML = ""; + const title = document.createElement("h2"); + title.textContent = "Guild discovery (" + json.total + " entries)"; + content.appendChild(title); - const guilds = document.createElement("div"); - guilds.id = "discovery-guild-content"; + const guilds = document.createElement("div"); + guilds.id = "discovery-guild-content"; - json.guilds.forEach((guild: guildjson["properties"]) => { - const content = document.createElement("div"); - content.classList.add("discovery-guild"); + json.guilds.forEach((guild: guildjson["properties"])=>{ + const content = document.createElement("div"); + content.classList.add("discovery-guild"); - if (guild.banner) { - const banner = document.createElement("img"); - banner.classList.add("banner"); - banner.crossOrigin = "anonymous"; - banner.src = + if(guild.banner){ + const banner = document.createElement("img"); + banner.classList.add("banner"); + banner.crossOrigin = "anonymous"; + banner.src = this.info.cdn + "/icons/" + guild.id + "/" + guild.banner + ".png?size=256"; - banner.alt = ""; - content.appendChild(banner); - } + banner.alt = ""; + content.appendChild(banner); + } - const nameContainer = document.createElement("div"); - nameContainer.classList.add("flex"); - const img = document.createElement("img"); - img.classList.add("icon"); - img.crossOrigin = "anonymous"; - img.src = + const nameContainer = document.createElement("div"); + nameContainer.classList.add("flex"); + const img = document.createElement("img"); + img.classList.add("icon"); + img.crossOrigin = "anonymous"; + img.src = this.info.cdn + (guild.icon - ? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48" - : "/embed/avatars/3.png"); - img.alt = ""; - nameContainer.appendChild(img); + ? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48" + : "/embed/avatars/3.png"); + img.alt = ""; + nameContainer.appendChild(img); - const name = document.createElement("h3"); - name.textContent = guild.name; - nameContainer.appendChild(name); - content.appendChild(nameContainer); - const desc = document.createElement("p"); - desc.textContent = guild.description; - content.appendChild(desc); + const name = document.createElement("h3"); + name.textContent = guild.name; + nameContainer.appendChild(name); + content.appendChild(nameContainer); + const desc = document.createElement("p"); + desc.textContent = guild.description; + content.appendChild(desc); - content.addEventListener("click", async () => { - const joinRes = await fetch( - this.info.api + "/guilds/" + guild.id + "/members/@me", - { - method: "PUT", - headers: this.headers, - } - ); - if (joinRes.ok) full.hide(); - }); - guilds.appendChild(content); - }); - content.appendChild(guilds); - } - messageCreate(messagep: messageCreateJson): void { - messagep.d.guild_id ??= "@me"; - const channel = this.channelids.get(messagep.d.channel_id); - if (channel) { - channel.messageCreate(messagep); - this.unreads(); - } - } - unreads(): void { - for (const thing of this.guilds) { - if (thing.id === "@me") { - continue; - } - const html = this.guildhtml.get(thing.id); - thing.unreads(html); - } - } - async typingStart(typing: startTypingjson): Promise { - const channel = this.channelids.get(typing.d.channel_id); - if (!channel) return; - channel.typingStart(typing); - } - updatepfp(file: Blob): void { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => { - fetch(this.info.api + "/users/@me", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - avatar: reader.result, - }), - }); - }; - } - updatebanner(file: Blob | null): void { - if (file) { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => { - fetch(this.info.api + "/users/@me", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - banner: reader.result, - }), - }); - }; - } else { - fetch(this.info.api + "/users/@me", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - banner: null, - }), - }); - } - } - updateProfile(json: { + content.addEventListener("click", async ()=>{ + const joinRes = await fetch( + this.info.api + "/guilds/" + guild.id + "/members/@me", + { + method: "PUT", + headers: this.headers, + } + ); + if(joinRes.ok) full.hide(); + }); + guilds.appendChild(content); + }); + content.appendChild(guilds); + } + messageCreate(messagep: messageCreateJson): void{ + messagep.d.guild_id ??= "@me"; + const channel = this.channelids.get(messagep.d.channel_id); + if(channel){ + channel.messageCreate(messagep); + this.unreads(); + } + } + unreads(): void{ + for(const thing of this.guilds){ + if(thing.id === "@me"){ + continue; + } + const html = this.guildhtml.get(thing.id); + thing.unreads(html); + } + } + async typingStart(typing: startTypingjson): Promise{ + const channel = this.channelids.get(typing.d.channel_id); + if(!channel)return; + channel.typingStart(typing); + } + updatepfp(file: Blob): void{ + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + avatar: reader.result, + }), + }); + }; + } + updatebanner(file: Blob | null): void{ + if(file){ + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: reader.result, + }), + }); + }; + }else{ + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: null, + }), + }); + } + } + updateProfile(json: { bio?: string; pronouns?: string; accent_color?: number; - }) { - fetch(this.info.api + "/users/@me/profile", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(json), - }); - } - async showusersettings() { - const settings = new Settings("Settings"); - { - const userOptions = settings.addButton("User Settings", { ltr: true }); - const hypotheticalProfile = document.createElement("div"); - let file: undefined | File | null; - let newpronouns: string | undefined; - let newbio: string | undefined; - const hypouser = this.user.clone(); - let color: string; - async function regen() { - hypotheticalProfile.textContent = ""; - const hypoprofile = await hypouser.buildprofile(-1, -1); + }){ + fetch(this.info.api + "/users/@me/profile", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(json), + }); + } + async showusersettings(){ + const settings = new Settings("Settings"); + { + const userOptions = settings.addButton("User Settings", { ltr: true }); + const hypotheticalProfile = document.createElement("div"); + let file: undefined | File | null; + let newpronouns: string | undefined; + let newbio: string | undefined; + const hypouser = this.user.clone(); + let color: string; + async function regen(){ + hypotheticalProfile.textContent = ""; + const hypoprofile = await hypouser.buildprofile(-1, -1); - hypotheticalProfile.appendChild(hypoprofile); - } - regen(); - const settingsLeft = userOptions.addOptions(""); - const settingsRight = userOptions.addOptions(""); - settingsRight.addHTMLArea(hypotheticalProfile); + hypotheticalProfile.appendChild(hypoprofile); + } + regen(); + const settingsLeft = userOptions.addOptions(""); + const settingsRight = userOptions.addOptions(""); + settingsRight.addHTMLArea(hypotheticalProfile); - const finput = settingsLeft.addFileInput( - "Upload pfp:", - (_) => { - if (file) { - this.updatepfp(file); - } - }, - { clear: true } - ); - finput.watchForChange((_) => { - if (!_) { - file = null; - hypouser.avatar = null; - hypouser.hypotheticalpfp = true; - regen(); - return; - } - if (_.length) { - file = _[0]; - const blob = URL.createObjectURL(file); - hypouser.avatar = blob; - hypouser.hypotheticalpfp = true; - regen(); - } - }); - let bfile: undefined | File | null; - const binput = settingsLeft.addFileInput( - "Upload banner:", - (_) => { - if (bfile !== undefined) { - this.updatebanner(bfile); - } - }, - { clear: true } - ); - binput.watchForChange((_) => { - if (!_) { - bfile = null; - hypouser.banner = undefined; - hypouser.hypotheticalbanner = true; - regen(); - return; - } - if (_.length) { - bfile = _[0]; - const blob = URL.createObjectURL(bfile); - hypouser.banner = blob; - hypouser.hypotheticalbanner = true; - regen(); - } - }); - let changed = false; - const pronounbox = settingsLeft.addTextInput( - "Pronouns", - (_) => { - if (newpronouns || newbio || changed) { - this.updateProfile({ - pronouns: newpronouns, - bio: newbio, - accent_color: Number.parseInt("0x" + color.substr(1), 16), - }); - } - }, - { initText: this.user.pronouns } - ); - pronounbox.watchForChange((_) => { - hypouser.pronouns = _; - newpronouns = _; - regen(); - }); - const bioBox = settingsLeft.addMDInput("Bio:", (_) => {}, { - initText: this.user.bio.rawString, - }); - bioBox.watchForChange((_) => { - newbio = _; - hypouser.bio = new MarkDown(_, this); - regen(); - }); + const finput = settingsLeft.addFileInput( + "Upload pfp:", + _=>{ + if(file){ + this.updatepfp(file); + } + }, + { clear: true } + ); + finput.watchForChange(_=>{ + if(!_){ + file = null; + hypouser.avatar = null; + hypouser.hypotheticalpfp = true; + regen(); + return; + } + if(_.length){ + file = _[0]; + const blob = URL.createObjectURL(file); + hypouser.avatar = blob; + hypouser.hypotheticalpfp = true; + regen(); + } + }); + let bfile: undefined | File | null; + const binput = settingsLeft.addFileInput( + "Upload banner:", + _=>{ + if(bfile !== undefined){ + this.updatebanner(bfile); + } + }, + { clear: true } + ); + binput.watchForChange(_=>{ + if(!_){ + bfile = null; + hypouser.banner = undefined; + hypouser.hypotheticalbanner = true; + regen(); + return; + } + if(_.length){ + bfile = _[0]; + const blob = URL.createObjectURL(bfile); + hypouser.banner = blob; + hypouser.hypotheticalbanner = true; + regen(); + } + }); + let changed = false; + const pronounbox = settingsLeft.addTextInput( + "Pronouns", + _=>{ + if(newpronouns || newbio || changed){ + this.updateProfile({ + pronouns: newpronouns, + bio: newbio, + accent_color: Number.parseInt("0x" + color.substr(1), 16), + }); + } + }, + { initText: this.user.pronouns } + ); + pronounbox.watchForChange(_=>{ + hypouser.pronouns = _; + newpronouns = _; + regen(); + }); + const bioBox = settingsLeft.addMDInput("Bio:", _=>{}, { + initText: this.user.bio.rawString, + }); + bioBox.watchForChange(_=>{ + newbio = _; + hypouser.bio = new MarkDown(_, this); + regen(); + }); - if (this.user.accent_color) { - color = "#" + this.user.accent_color.toString(16); - } else { - color = "transparent"; - } - const colorPicker = settingsLeft.addColorInput( - "Profile color", - (_) => {}, - { initColor: color } - ); - colorPicker.watchForChange((_) => { - console.log(); - color = _; - hypouser.accent_color = Number.parseInt("0x" + _.substr(1), 16); - changed = true; - regen(); - }); - } - { - const tas = settings.addButton("Themes & sounds"); - { - const themes = ["Dark", "WHITE", "Light"]; - tas.addSelect( - "Theme:", - (_) => { - localStorage.setItem("theme", themes[_]); - setTheme(); - }, - themes, - { - defaultIndex: themes.indexOf( + if(this.user.accent_color){ + color = "#" + this.user.accent_color.toString(16); + }else{ + color = "transparent"; + } + const colorPicker = settingsLeft.addColorInput( + "Profile color", + _=>{}, + { initColor: color } + ); + colorPicker.watchForChange(_=>{ + console.log(); + color = _; + hypouser.accent_color = Number.parseInt("0x" + _.substr(1), 16); + changed = true; + regen(); + }); + } + { + const tas = settings.addButton("Themes & sounds"); + { + const themes = ["Dark", "WHITE", "Light"]; + tas.addSelect( + "Theme:", + _=>{ + localStorage.setItem("theme", themes[_]); + setTheme(); + }, + themes, + { + defaultIndex: themes.indexOf( localStorage.getItem("theme") as string - ), - } - ); - } - { - const sounds = Voice.sounds; - tas - .addSelect( - "Notification sound:", - (_) => { - Voice.setNotificationSound(sounds[_]); - }, - sounds, - { defaultIndex: sounds.indexOf(Voice.getNotificationSound()) } - ) - .watchForChange((_) => { - Voice.noises(sounds[_]); - }); - } + ), + } + ); + } + { + const sounds = Voice.sounds; + tas + .addSelect( + "Notification sound:", + _=>{ + Voice.setNotificationSound(sounds[_]); + }, + sounds, + { defaultIndex: sounds.indexOf(Voice.getNotificationSound()) } + ) + .watchForChange(_=>{ + Voice.noises(sounds[_]); + }); + } - { - const userinfos = getBulkInfo(); - tas.addColorInput( - "Accent color:", - (_) => { - userinfos.accent_color = _; - localStorage.setItem("userinfos", JSON.stringify(userinfos)); - document.documentElement.style.setProperty( - "--accent-color", - userinfos.accent_color - ); - }, - { initColor: userinfos.accent_color } - ); - } - } - { - const security = settings.addButton("Account Settings"); - const genSecurity = () => { - security.removeAll(); - if (this.mfa_enabled) { - security.addButtonInput("", "Disable 2FA", () => { - const form = security.addSubForm( - "2FA Disable", - (_: any) => { - if (_.message) { - switch (_.code) { - case 60008: - form.error("code", "Invalid code"); - break; - } - } else { - this.mfa_enabled = false; - security.returnFromSub(); - genSecurity(); - } - }, - { - fetchURL: this.info.api + "/users/@me/mfa/totp/disable", - headers: this.headers, - } - ); - form.addTextInput("Code:", "code", { required: true }); - }); - } else { - security.addButtonInput("", "Enable 2FA", async () => { - let secret = ""; - for (let i = 0; i < 18; i++) { - secret += "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[ - Math.floor(Math.random() * 32) - ]; - } - const form = security.addSubForm( - "2FA Setup", - (_: any) => { - if (_.message) { - switch (_.code) { - case 60008: - form.error("code", "Invalid code"); - break; - case 400: - form.error("password", "Incorrect password"); - break; - } - } else { - genSecurity(); - this.mfa_enabled = true; - security.returnFromSub(); - } - }, - { - fetchURL: this.info.api + "/users/@me/mfa/totp/enable/", - headers: this.headers, - } - ); - form.addTitle( - "Copy this secret into your totp(time-based one time password) app" - ); - form.addText( - `Your secret is: ${secret} and it's 6 digits, with a 30 second token period` - ); - form.addTextInput("Account Password:", "password", { - required: true, - password: true, - }); - form.addTextInput("Code:", "code", { required: true }); - form.setValue("secret", secret); - }); - } - security.addButtonInput("", "Change discriminator", () => { - const form = security.addSubForm( - "Change Discriminator", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("New discriminator:", "discriminator"); - }); - security.addButtonInput("", "Change email", () => { - const form = security.addSubForm( - "Change Email", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("Password:", "password", { password: true }); - if (this.mfa_enabled) { - form.addTextInput("Code:", "code"); - } - form.addTextInput("New email:", "email"); - }); - security.addButtonInput("", "Change username", () => { - const form = security.addSubForm( - "Change Username", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("Password:", "password", { password: true }); - if (this.mfa_enabled) { - form.addTextInput("Code:", "code"); - } - form.addTextInput("New username:", "username"); - }); - security.addButtonInput("", "Change password", () => { - const form = security.addSubForm( - "Change Password", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("Old password:", "password", { password: true }); - if (this.mfa_enabled) { - form.addTextInput("Code:", "code"); - } - let in1 = ""; - let in2 = ""; - form.addTextInput("New password:", "").watchForChange((text) => { - in1 = text; - }); - const copy = form.addTextInput("New password again:", ""); - copy.watchForChange((text) => { - in2 = text; - }); - form.setValue("new_password", () => { - if (in1 === in2) { - return in1; - } else { - throw new FormError(copy, "Passwords don't match"); - } - }); - }); - }; - genSecurity(); - } - { - const connections = settings.addButton("Connections"); - const connectionContainer = document.createElement("div"); - connectionContainer.id = "connection-container"; + { + const userinfos = getBulkInfo(); + tas.addColorInput( + "Accent color:", + _=>{ + userinfos.accent_color = _; + localStorage.setItem("userinfos", JSON.stringify(userinfos)); + document.documentElement.style.setProperty( + "--accent-color", + userinfos.accent_color + ); + }, + { initColor: userinfos.accent_color } + ); + } + } + { + const security = settings.addButton("Account Settings"); + const genSecurity = ()=>{ + security.removeAll(); + if(this.mfa_enabled){ + security.addButtonInput("", "Disable 2FA", ()=>{ + const form = security.addSubForm( + "2FA Disable", + (_: any)=>{ + if(_.message){ + switch(_.code){ + case 60008: + form.error("code", "Invalid code"); + break; + } + }else{ + this.mfa_enabled = false; + security.returnFromSub(); + genSecurity(); + } + }, + { + fetchURL: this.info.api + "/users/@me/mfa/totp/disable", + headers: this.headers, + } + ); + form.addTextInput("Code:", "code", { required: true }); + }); + }else{ + security.addButtonInput("", "Enable 2FA", async ()=>{ + let secret = ""; + for(let i = 0; i < 18; i++){ + secret += "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[ + Math.floor(Math.random() * 32) + ]; + } + const form = security.addSubForm( + "2FA Setup", + (_: any)=>{ + if(_.message){ + switch(_.code){ + case 60008: + form.error("code", "Invalid code"); + break; + case 400: + form.error("password", "Incorrect password"); + break; + } + }else{ + genSecurity(); + this.mfa_enabled = true; + security.returnFromSub(); + } + }, + { + fetchURL: this.info.api + "/users/@me/mfa/totp/enable/", + headers: this.headers, + } + ); + form.addTitle( + "Copy this secret into your totp(time-based one time password) app" + ); + form.addText( + `Your secret is: ${secret} and it's 6 digits, with a 30 second token period` + ); + form.addTextInput("Account Password:", "password", { + required: true, + password: true, + }); + form.addTextInput("Code:", "code", { required: true }); + form.setValue("secret", secret); + }); + } + security.addButtonInput("", "Change discriminator", ()=>{ + const form = security.addSubForm( + "Change Discriminator", + _=>{ + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("New discriminator:", "discriminator"); + }); + security.addButtonInput("", "Change email", ()=>{ + const form = security.addSubForm( + "Change Email", + _=>{ + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("Password:", "password", { password: true }); + if(this.mfa_enabled){ + form.addTextInput("Code:", "code"); + } + form.addTextInput("New email:", "email"); + }); + security.addButtonInput("", "Change username", ()=>{ + const form = security.addSubForm( + "Change Username", + _=>{ + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("Password:", "password", { password: true }); + if(this.mfa_enabled){ + form.addTextInput("Code:", "code"); + } + form.addTextInput("New username:", "username"); + }); + security.addButtonInput("", "Change password", ()=>{ + const form = security.addSubForm( + "Change Password", + _=>{ + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("Old password:", "password", { password: true }); + if(this.mfa_enabled){ + form.addTextInput("Code:", "code"); + } + let in1 = ""; + let in2 = ""; + form.addTextInput("New password:", "").watchForChange(text=>{ + in1 = text; + }); + const copy = form.addTextInput("New password again:", ""); + copy.watchForChange(text=>{ + in2 = text; + }); + form.setValue("new_password", ()=>{ + if(in1 === in2){ + return in1; + }else{ + throw new FormError(copy, "Passwords don't match"); + } + }); + }); + }; + genSecurity(); + } + { + const connections = settings.addButton("Connections"); + const connectionContainer = document.createElement("div"); + connectionContainer.id = "connection-container"; - fetch(this.info.api + "/connections", { - headers: this.headers, - }) - .then((r) => r.json()) - .then((json) => { - Object.keys(json) - .sort((key) => (json[key].enabled ? -1 : 1)) - .forEach((key) => { - const connection = json[key]; + fetch(this.info.api + "/connections", { + headers: this.headers, + }) + .then(r=>r.json()) + .then(json=>{ + Object.keys(json) + .sort(key=>(json[key].enabled ? -1 : 1)) + .forEach(key=>{ + const connection = json[key]; - const container = document.createElement("div"); - container.textContent = + const container = document.createElement("div"); + container.textContent = key.charAt(0).toUpperCase() + key.slice(1); - if (connection.enabled) { - container.addEventListener("click", async () => { - const connectionRes = await fetch( - this.info.api + "/connections/" + key + "/authorize", - { - headers: this.headers, - } - ); - const connectionJSON = await connectionRes.json(); - window.open( - connectionJSON.url, - "_blank", - "noopener noreferrer" - ); - }); - } else { - container.classList.add("disabled"); - container.title = + if(connection.enabled){ + container.addEventListener("click", async ()=>{ + const connectionRes = await fetch( + this.info.api + "/connections/" + key + "/authorize", + { + headers: this.headers, + } + ); + const connectionJSON = await connectionRes.json(); + window.open( + connectionJSON.url, + "_blank", + "noopener noreferrer" + ); + }); + }else{ + container.classList.add("disabled"); + container.title = "This connection has been disabled server-side."; - } + } - connectionContainer.appendChild(container); - }); - }); - connections.addHTMLArea(connectionContainer); - } - { - const devPortal = settings.addButton("Developer Portal"); + connectionContainer.appendChild(container); + }); + }); + connections.addHTMLArea(connectionContainer); + } + { + const devPortal = settings.addButton("Developer Portal"); - const teamsRes = await fetch(this.info.api + "/teams", { - headers: this.headers, - }); - const teams = await teamsRes.json(); + const teamsRes = await fetch(this.info.api + "/teams", { + headers: this.headers, + }); + const teams = await teamsRes.json(); - devPortal.addButtonInput("", "Create application", () => { - const form = devPortal.addSubForm( - "Create application", - (json: any) => { - if (json.message) form.error("name", json.message); - else { - devPortal.returnFromSub(); - this.manageApplication(json.id); - } - }, - { - fetchURL: this.info.api + "/applications", - headers: this.headers, - method: "POST", - } - ); + devPortal.addButtonInput("", "Create application", ()=>{ + const form = devPortal.addSubForm( + "Create application", + (json: any)=>{ + if(json.message) form.error("name", json.message); + else{ + devPortal.returnFromSub(); + this.manageApplication(json.id); + } + }, + { + fetchURL: this.info.api + "/applications", + headers: this.headers, + method: "POST", + } + ); - form.addTextInput("Name", "name", { required: true }); - form.addSelect( - "Team", - "team_id", - ["Personal", ...teams.map((team: { name: string }) => team.name)], - { - defaultIndex: 0, - } - ); - }); + form.addTextInput("Name", "name", { required: true }); + form.addSelect( + "Team", + "team_id", + ["Personal", ...teams.map((team: { name: string })=>team.name)], + { + defaultIndex: 0, + } + ); + }); - const appListContainer = document.createElement("div"); - appListContainer.id = "app-list-container"; - fetch(this.info.api + "/applications", { - headers: this.headers, - }) - .then((r) => r.json()) - .then((json) => { - json.forEach( - (application: { + const appListContainer = document.createElement("div"); + appListContainer.id = "app-list-container"; + fetch(this.info.api + "/applications", { + headers: this.headers, + }) + .then(r=>r.json()) + .then(json=>{ + json.forEach( + (application: { cover_image: any; icon: any; id: string | undefined; name: string | number; bot: any; - }) => { - const container = document.createElement("div"); + })=>{ + const container = document.createElement("div"); - if (application.cover_image || application.icon) { - const cover = document.createElement("img"); - cover.crossOrigin = "anonymous"; - cover.src = + if(application.cover_image || application.icon){ + const cover = document.createElement("img"); + cover.crossOrigin = "anonymous"; + cover.src = this.info.cdn + "/app-icons/" + application.id + "/" + (application.cover_image || application.icon) + ".png?size=256"; - cover.alt = ""; - cover.loading = "lazy"; - container.appendChild(cover); - } + cover.alt = ""; + cover.loading = "lazy"; + container.appendChild(cover); + } - const name = document.createElement("h2"); - name.textContent = + const name = document.createElement("h2"); + name.textContent = application.name + (application.bot ? " (Bot)" : ""); - container.appendChild(name); + container.appendChild(name); - container.addEventListener("click", async () => { - this.manageApplication(application.id); - }); - appListContainer.appendChild(container); - } - ); - }); - devPortal.addHTMLArea(appListContainer); - } - settings.show(); - } - async manageApplication(appId = "") { - const res = await fetch(this.info.api + "/applications/" + appId, { - headers: this.headers, - }); - const json = await res.json(); + container.addEventListener("click", async ()=>{ + this.manageApplication(application.id); + }); + appListContainer.appendChild(container); + } + ); + }); + devPortal.addHTMLArea(appListContainer); + } + settings.show(); + } + async manageApplication(appId = ""){ + const res = await fetch(this.info.api + "/applications/" + appId, { + headers: this.headers, + }); + const json = await res.json(); - const fields: any = {}; - const appDialog = new Dialog([ - "vdiv", - ["title", "Editing " + json.name], - [ - "vdiv", - [ - "textbox", - "Application name:", - json.name, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.name = target.value; - }, - ], - [ - "mdbox", - "Description:", - json.description, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.description = target.value; - }, - ], - [ - "vdiv", - json.icon - ? [ - "img", - this.info.cdn + + const fields: any = {}; + const appDialog = new Dialog([ + "vdiv", + ["title", "Editing " + json.name], + [ + "vdiv", + [ + "textbox", + "Application name:", + json.name, + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.name = target.value; + }, + ], + [ + "mdbox", + "Description:", + json.description, + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.description = target.value; + }, + ], + [ + "vdiv", + json.icon + ? [ + "img", + this.info.cdn + "/app-icons/" + appId + "/" + json.icon + ".png?size=128", - [128, 128], - ] - : ["text", "No icon"], - [ - "fileupload", - "Application icon:", - (event) => { - const reader = new FileReader(); - const files = (event.target as HTMLInputElement).files; - if (files) { - reader.readAsDataURL(files[0]); - reader.onload = () => { - fields.icon = reader.result; - }; - } - }, - ], - ], - ], - [ - "hdiv", - [ - "textbox", - "Privacy policy URL:", - json.privacy_policy_url || "", - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.privacy_policy_url = target.value; - }, - ], - [ - "textbox", - "Terms of Service URL:", - json.terms_of_service_url || "", - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.terms_of_service_url = target.value; - }, - ], - ], - [ - "hdiv", - [ - "checkbox", - "Make bot publicly inviteable?", - json.bot_public, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.bot_public = target.checked; - }, - ], - [ - "checkbox", - "Require code grant to invite the bot?", - json.bot_require_code_grant, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.bot_require_code_grant = target.checked; - }, - ], - ], - [ - "hdiv", - [ - "button", - "", - "Save changes", - async () => { - const updateRes = await fetch( - this.info.api + "/applications/" + appId, - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields), - } - ); - if (updateRes.ok) appDialog.hide(); - else { - const updateJSON = await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - }, - ], - [ - "button", - "", - (json.bot ? "Manage" : "Add") + " bot", - async () => { - if (!json.bot) { - if ( - !confirm( - "Are you sure you want to add a bot to this application? There's no going back." - ) - ) - return; + [128, 128], + ] + : ["text", "No icon"], + [ + "fileupload", + "Application icon:", + event=>{ + const reader = new FileReader(); + const files = (event.target as HTMLInputElement).files; + if(files){ + reader.readAsDataURL(files[0]); + reader.onload = ()=>{ + fields.icon = reader.result; + }; + } + }, + ], + ], + ], + [ + "hdiv", + [ + "textbox", + "Privacy policy URL:", + json.privacy_policy_url || "", + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.privacy_policy_url = target.value; + }, + ], + [ + "textbox", + "Terms of Service URL:", + json.terms_of_service_url || "", + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.terms_of_service_url = target.value; + }, + ], + ], + [ + "hdiv", + [ + "checkbox", + "Make bot publicly inviteable?", + json.bot_public, + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.bot_public = target.checked; + }, + ], + [ + "checkbox", + "Require code grant to invite the bot?", + json.bot_require_code_grant, + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.bot_require_code_grant = target.checked; + }, + ], + ], + [ + "hdiv", + [ + "button", + "", + "Save changes", + async ()=>{ + const updateRes = await fetch( + this.info.api + "/applications/" + appId, + { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(fields), + } + ); + if(updateRes.ok) appDialog.hide(); + else{ + const updateJSON = await updateRes.json(); + alert("An error occurred: " + updateJSON.message); + } + }, + ], + [ + "button", + "", + (json.bot ? "Manage" : "Add") + " bot", + async ()=>{ + if(!json.bot){ + if( + !confirm( + "Are you sure you want to add a bot to this application? There's no going back." + ) + ) + return; - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot", - { - method: "POST", - headers: this.headers, - } - ); - const updateJSON = await updateRes.json(); - alert("Bot token:\n" + updateJSON.token); - } + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot", + { + method: "POST", + headers: this.headers, + } + ); + const updateJSON = await updateRes.json(); + alert("Bot token:\n" + updateJSON.token); + } - appDialog.hide(); - this.manageBot(appId); - }, - ], - ], - ]); - appDialog.show(); - } - async manageBot(appId = "") { - const res = await fetch(this.info.api + "/applications/" + appId, { - headers: this.headers, - }); - const json = await res.json(); - if (!json.bot) - return alert( - "For some reason, this application doesn't have a bot (yet)." - ); + appDialog.hide(); + this.manageBot(appId); + }, + ], + ], + ]); + appDialog.show(); + } + async manageBot(appId = ""){ + const res = await fetch(this.info.api + "/applications/" + appId, { + headers: this.headers, + }); + const json = await res.json(); + if(!json.bot) + return alert( + "For some reason, this application doesn't have a bot (yet)." + ); - const fields: any = { - username: json.bot.username, - avatar: json.bot.avatar - ? this.info.cdn + + const fields: any = { + username: json.bot.username, + avatar: json.bot.avatar + ? this.info.cdn + "/app-icons/" + appId + "/" + json.bot.avatar + ".png?size=256" - : "", - }; - const botDialog = new Dialog([ - "vdiv", - ["title", "Editing bot: " + json.bot.username], - [ - "hdiv", - [ - "textbox", - "Bot username:", - json.bot.username, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.username = target.value; - }, - ], - [ - "vdiv", - fields.avatar - ? ["img", fields.avatar, [128, 128]] - : ["text", "No avatar"], - [ - "fileupload", - "Bot avatar:", - (event) => { - const reader = new FileReader(); - const files = (event.target as HTMLInputElement).files; - if (files) { - const file = files[0]; - reader.readAsDataURL(file); - reader.onload = () => { - fields.avatar = reader.result; - }; - } - }, - ], - ], - ], - [ - "hdiv", - [ - "button", - "", - "Save changes", - async () => { - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot", - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields), - } - ); - if (updateRes.ok) botDialog.hide(); - else { - const updateJSON = await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - }, - ], - [ - "button", - "", - "Reset token", - async () => { - if ( - !confirm( - "Are you sure you want to reset the bot token? Your bot will stop working until you update it." - ) - ) - return; + : "", + }; + const botDialog = new Dialog([ + "vdiv", + ["title", "Editing bot: " + json.bot.username], + [ + "hdiv", + [ + "textbox", + "Bot username:", + json.bot.username, + (event: Event)=>{ + const target = event.target as HTMLInputElement; + fields.username = target.value; + }, + ], + [ + "vdiv", + fields.avatar + ? ["img", fields.avatar, [128, 128]] + : ["text", "No avatar"], + [ + "fileupload", + "Bot avatar:", + event=>{ + const reader = new FileReader(); + const files = (event.target as HTMLInputElement).files; + if(files){ + const file = files[0]; + reader.readAsDataURL(file); + reader.onload = ()=>{ + fields.avatar = reader.result; + }; + } + }, + ], + ], + ], + [ + "hdiv", + [ + "button", + "", + "Save changes", + async ()=>{ + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot", + { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(fields), + } + ); + if(updateRes.ok) botDialog.hide(); + else{ + const updateJSON = await updateRes.json(); + alert("An error occurred: " + updateJSON.message); + } + }, + ], + [ + "button", + "", + "Reset token", + async ()=>{ + if( + !confirm( + "Are you sure you want to reset the bot token? Your bot will stop working until you update it." + ) + ) + return; - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot/reset", - { - method: "POST", - headers: this.headers, - } - ); - const updateJSON = await updateRes.json(); - alert("New token:\n" + updateJSON.token); - botDialog.hide(); - }, - ], - ], - ]); - botDialog.show(); - } + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot/reset", + { + method: "POST", + headers: this.headers, + } + ); + const updateJSON = await updateRes.json(); + alert("New token:\n" + updateJSON.token); + botDialog.hide(); + }, + ], + ], + ]); + botDialog.show(); + } - //---------- resolving members code ----------- - readonly waitingmembers: Map< + //---------- resolving members code ----------- + readonly waitingmembers: Map< string, Map void> > = new Map(); - readonly presences: Map = new Map(); - async resolvemember( - id: string, - guildid: string - ): Promise { - if (guildid === "@me") { - return undefined; - } - const guild = this.guildids.get(guildid); - const borked = true; - if (borked && guild && guild.member_count > 250) { - //sorry puyo, I need to fix member resolving while it's broken on large guilds - try { - const req = await fetch( - this.info.api + "/guilds/" + guild.id + "/members/" + id, - { - headers: this.headers, - } - ); - if (req.status !== 200) { - return undefined; - } - return await req.json(); - } catch { - return undefined; - } - } - let guildmap = this.waitingmembers.get(guildid); - if (!guildmap) { - guildmap = new Map(); - this.waitingmembers.set(guildid, guildmap); - } - const promise: Promise = new Promise((res) => { - guildmap.set(id, res); - this.getmembers(); - }); - return await promise; - } - fetchingmembers: Map = new Map(); - noncemap: Map void> = new Map(); - noncebuild: Map = new Map(); - async gotChunk(chunk: { + readonly presences: Map = new Map(); + async resolvemember( + id: string, + guildid: string + ): Promise{ + if(guildid === "@me"){ + return undefined; + } + const guild = this.guildids.get(guildid); + const borked = true; + if(borked && guild && guild.member_count > 250){ + //sorry puyo, I need to fix member resolving while it's broken on large guilds + try{ + const req = await fetch( + this.info.api + "/guilds/" + guild.id + "/members/" + id, + { + headers: this.headers, + } + ); + if(req.status !== 200){ + return undefined; + } + return await req.json(); + }catch{ + return undefined; + } + } + let guildmap = this.waitingmembers.get(guildid); + if(!guildmap){ + guildmap = new Map(); + this.waitingmembers.set(guildid, guildmap); + } + const promise: Promise = new Promise(res=>{ + guildmap.set(id, res); + this.getmembers(); + }); + return await promise; + } + fetchingmembers: Map = new Map(); + noncemap: Map void> = new Map(); + noncebuild: Map = new Map(); + async gotChunk(chunk: { chunk_index: number; chunk_count: number; nonce: string; not_found?: string[]; members?: memberjson[]; presences: presencejson[]; - }) { - for (const thing of chunk.presences) { - if (thing.user) { - this.presences.set(thing.user.id, thing); - } - } - chunk.members ??= []; - const arr = this.noncebuild.get(chunk.nonce); - if (!arr) return; - arr[0] = arr[0].concat(chunk.members); - if (chunk.not_found) { - arr[1] = chunk.not_found; - } - arr[2].push(chunk.chunk_index); - if (arr[2].length === chunk.chunk_count) { - this.noncebuild.delete(chunk.nonce); - const func = this.noncemap.get(chunk.nonce); - if (!func) return; - func([arr[0], arr[1]]); - this.noncemap.delete(chunk.nonce); - } - } - async getmembers() { - const promise = new Promise((res) => { - setTimeout(res, 10); - }); - await promise; //allow for more to be sent at once :P - if (this.ws) { - this.waitingmembers.forEach(async (value, guildid) => { - const keys = value.keys(); - if (this.fetchingmembers.has(guildid)) { - return; - } - const build: string[] = []; - for (const key of keys) { - build.push(key); - if (build.length === 100) { - break; - } - } - if (!build.length) { - this.waitingmembers.delete(guildid); - return; - } - const promise: Promise<[memberjson[], string[]]> = new Promise( - (res) => { - const nonce = "" + Math.floor(Math.random() * 100000000000); - this.noncemap.set(nonce, res); - this.noncebuild.set(nonce, [[], [], []]); - if (!this.ws) return; - this.ws.send( - JSON.stringify({ - op: 8, - d: { - user_ids: build, - guild_id: guildid, - limit: 100, - nonce, - presences: true, - }, - }) - ); - this.fetchingmembers.set(guildid, true); - } - ); - const prom = await promise; - const data = prom[0]; - for (const thing of data) { - if (value.has(thing.id)) { - const func = value.get(thing.id); - if (!func) { - value.delete(thing.id); - continue; - } - func(thing); - value.delete(thing.id); - } - } - for (const thing of prom[1]) { - if (value.has(thing)) { - const func = value.get(thing); - if (!func) { - value.delete(thing); - continue; - } - func(undefined); - value.delete(thing); - } - } - this.fetchingmembers.delete(guildid); - this.getmembers(); - }); - } - } - async pingEndpoint() { - const userInfo = getBulkInfo(); - if (!userInfo.instances) userInfo.instances = {}; - const wellknown = this.info.wellknown; - if (!userInfo.instances[wellknown]) { - const pingRes = await fetch(this.info.api + "/ping"); - const pingJSON = await pingRes.json(); - userInfo.instances[wellknown] = pingJSON; - localStorage.setItem("userinfos", JSON.stringify(userInfo)); - } - this.instancePing = userInfo.instances[wellknown].instance; + }){ + for(const thing of chunk.presences){ + if(thing.user){ + this.presences.set(thing.user.id, thing); + } + } + chunk.members ??= []; + const arr = this.noncebuild.get(chunk.nonce); + if(!arr)return; + arr[0] = arr[0].concat(chunk.members); + if(chunk.not_found){ + arr[1] = chunk.not_found; + } + arr[2].push(chunk.chunk_index); + if(arr[2].length === chunk.chunk_count){ + this.noncebuild.delete(chunk.nonce); + const func = this.noncemap.get(chunk.nonce); + if(!func)return; + func([arr[0], arr[1]]); + this.noncemap.delete(chunk.nonce); + } + } + async getmembers(){ + const promise = new Promise(res=>{ + setTimeout(res, 10); + }); + await promise; //allow for more to be sent at once :P + if(this.ws){ + this.waitingmembers.forEach(async (value, guildid)=>{ + const keys = value.keys(); + if(this.fetchingmembers.has(guildid)){ + return; + } + const build: string[] = []; + for(const key of keys){ + build.push(key); + if(build.length === 100){ + break; + } + } + if(!build.length){ + this.waitingmembers.delete(guildid); + return; + } + const promise: Promise<[memberjson[], string[]]> = new Promise( + res=>{ + const nonce = "" + Math.floor(Math.random() * 100000000000); + this.noncemap.set(nonce, res); + this.noncebuild.set(nonce, [[], [], []]); + if(!this.ws)return; + this.ws.send( + JSON.stringify({ + op: 8, + d: { + user_ids: build, + guild_id: guildid, + limit: 100, + nonce, + presences: true, + }, + }) + ); + this.fetchingmembers.set(guildid, true); + } + ); + const prom = await promise; + const data = prom[0]; + for(const thing of data){ + if(value.has(thing.id)){ + const func = value.get(thing.id); + if(!func){ + value.delete(thing.id); + continue; + } + func(thing); + value.delete(thing.id); + } + } + for(const thing of prom[1]){ + if(value.has(thing)){ + const func = value.get(thing); + if(!func){ + value.delete(thing); + continue; + } + func(); + value.delete(thing); + } + } + this.fetchingmembers.delete(guildid); + this.getmembers(); + }); + } + } + async pingEndpoint(){ + const userInfo = getBulkInfo(); + if(!userInfo.instances) userInfo.instances = {}; + const wellknown = this.info.wellknown; + if(!userInfo.instances[wellknown]){ + const pingRes = await fetch(this.info.api + "/ping"); + const pingJSON = await pingRes.json(); + userInfo.instances[wellknown] = pingJSON; + localStorage.setItem("userinfos", JSON.stringify(userInfo)); + } + this.instancePing = userInfo.instances[wellknown].instance; - this.pageTitle("Loading..."); - } - pageTitle(channelName = "", guildName = "") { - (document.getElementById("channelname") as HTMLSpanElement).textContent = + this.pageTitle("Loading..."); + } + pageTitle(channelName = "", guildName = ""){ + (document.getElementById("channelname") as HTMLSpanElement).textContent = channelName; - ( + ( document.getElementsByTagName("title")[0] as HTMLTitleElement - ).textContent = + ).textContent = channelName + (guildName ? " | " + guildName : "") + " | " + this.instancePing.name + " | Jank Client"; - } - async instanceStats() { - const res = await fetch(this.info.api + "/policies/stats", { - headers: this.headers, - }); - const json = await res.json(); + } + async instanceStats(){ + const res = await fetch(this.info.api + "/policies/stats", { + headers: this.headers, + }); + const json = await res.json(); - const dialog = new Dialog([ - "vdiv", - ["title", "Instance stats: " + this.instancePing.name], - ["text", "Registered users: " + json.counts.user], - ["text", "Servers: " + json.counts.guild], - ["text", "Messages: " + json.counts.message], - ["text", "Members: " + json.counts.members], - ]); - dialog.show(); - } + const dialog = new Dialog([ + "vdiv", + ["title", "Instance stats: " + this.instancePing.name], + ["text", "Registered users: " + json.counts.user], + ["text", "Servers: " + json.counts.guild], + ["text", "Messages: " + json.counts.message], + ["text", "Members: " + json.counts.members], + ]); + dialog.show(); + } } -export { Localuser }; +export{ Localuser }; diff --git a/src/webpage/login.html b/src/webpage/login.html index 16b2c3a..7c61b67 100644 --- a/src/webpage/login.html +++ b/src/webpage/login.html @@ -1,62 +1,62 @@ - - - - Jank Client - - - - - - - -
-

Login

-
-
-
-

-

+ + + + Jank Client + + + + + + + +
+

Login

+
+ +
+

+

-
-

+
+

-
-



-

+
+



+

-
- - - Don't have an account? -
- - +
+ + + Don't have an account? +
+ + diff --git a/src/webpage/login.ts b/src/webpage/login.ts index c9c308f..ebbe0f9 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -1,14 +1,14 @@ -import { Dialog } from "./dialog.js"; +import{ Dialog }from"./dialog.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); -function setTheme() { -let name = localStorage.getItem("theme"); -if (!name) { -localStorage.setItem("theme", "Dark"); -name = "Dark"; -} -document.body.className = name + "-theme"; +function setTheme(){ + let name = localStorage.getItem("theme"); + if(!name){ + localStorage.setItem("theme", "Dark"); + name = "Dark"; + } + document.body.className = name + "-theme"; } let instances: | { @@ -31,179 +31,179 @@ login?: string; | null; setTheme(); -function getBulkUsers() { -const json = getBulkInfo(); -for (const thing in json.users) { -json.users[thing] = new Specialuser(json.users[thing]); +function getBulkUsers(){ + const json = getBulkInfo(); + for(const thing in json.users){ + json.users[thing] = new Specialuser(json.users[thing]); + } + return json; } -return json; -} -function trimswitcher() { -const json = getBulkInfo(); -const map = new Map(); -for (const thing in json.users) { -const user = json.users[thing]; -let wellknown = user.serverurls.wellknown; -if (wellknown.at(-1) !== "/") { -wellknown += "/"; -} -wellknown += user.username; -if (map.has(wellknown)) { -const otheruser = map.get(wellknown); -if (otheruser[1].serverurls.wellknown.at(-1) === "/") { -delete json.users[otheruser[0]]; -map.set(wellknown, [thing, user]); -} else { -delete json.users[thing]; -} -} else { -map.set(wellknown, [thing, user]); -} -} -for (const thing in json.users) { -if (thing.at(-1) === "/") { -const user = json.users[thing]; -delete json.users[thing]; -json.users[thing.slice(0, -1)] = user; -} -} -localStorage.setItem("userinfos", JSON.stringify(json)); -console.log(json); +function trimswitcher(){ + const json = getBulkInfo(); + const map = new Map(); + for(const thing in json.users){ + const user = json.users[thing]; + let wellknown = user.serverurls.wellknown; + if(wellknown.at(-1) !== "/"){ + wellknown += "/"; + } + wellknown += user.username; + if(map.has(wellknown)){ + const otheruser = map.get(wellknown); + if(otheruser[1].serverurls.wellknown.at(-1) === "/"){ + delete json.users[otheruser[0]]; + map.set(wellknown, [thing, user]); + }else{ + delete json.users[thing]; + } + }else{ + map.set(wellknown, [thing, user]); + } + } + for(const thing in json.users){ + if(thing.at(-1) === "/"){ + const user = json.users[thing]; + delete json.users[thing]; + json.users[thing.slice(0, -1)] = user; + } + } + localStorage.setItem("userinfos", JSON.stringify(json)); + console.log(json); } -function getBulkInfo() { -return JSON.parse(localStorage.getItem("userinfos")!); +function getBulkInfo(){ + return JSON.parse(localStorage.getItem("userinfos")!); } -function setDefaults() { -let userinfos = getBulkInfo(); -if (!userinfos) { -localStorage.setItem( -"userinfos", -JSON.stringify({ -currentuser: null, -users: {}, -preferences: { -theme: "Dark", -notifications: false, -notisound: "three", -}, -}) -); -userinfos = getBulkInfo(); -} -if (userinfos.users === undefined) { -userinfos.users = {}; -} -if (userinfos.accent_color === undefined) { -userinfos.accent_color = "#242443"; -} -document.documentElement.style.setProperty( -"--accent-color", -userinfos.accent_color -); -if (userinfos.preferences === undefined) { -userinfos.preferences = { -theme: "Dark", -notifications: false, -notisound: "three", -}; -} -if (userinfos.preferences && userinfos.preferences.notisound === undefined) { -userinfos.preferences.notisound = "three"; -} -localStorage.setItem("userinfos", JSON.stringify(userinfos)); +function setDefaults(){ + let userinfos = getBulkInfo(); + if(!userinfos){ + localStorage.setItem( + "userinfos", + JSON.stringify({ + currentuser: null, + users: {}, + preferences: { + theme: "Dark", + notifications: false, + notisound: "three", + }, + }) + ); + userinfos = getBulkInfo(); + } + if(userinfos.users === undefined){ + userinfos.users = {}; + } + if(userinfos.accent_color === undefined){ + userinfos.accent_color = "#242443"; + } + document.documentElement.style.setProperty( + "--accent-color", + userinfos.accent_color + ); + if(userinfos.preferences === undefined){ + userinfos.preferences = { + theme: "Dark", + notifications: false, + notisound: "three", + }; + } + if(userinfos.preferences && userinfos.preferences.notisound === undefined){ + userinfos.preferences.notisound = "three"; + } + localStorage.setItem("userinfos", JSON.stringify(userinfos)); } setDefaults(); -class Specialuser { -serverurls: { +class Specialuser{ + serverurls: { api: string; cdn: string; gateway: string; wellknown: string; login: string; }; -email: string; -token: string; -loggedin; -json; -constructor(json: any) { -if (json instanceof Specialuser) { -console.error("specialuser can't construct from another specialuser"); + email: string; + token: string; + loggedin; + json; + constructor(json: any){ + if(json instanceof Specialuser){ + console.error("specialuser can't construct from another specialuser"); + } + this.serverurls = json.serverurls; + let apistring = new URL(json.serverurls.api).toString(); + apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; + this.serverurls.api = apistring; + this.serverurls.cdn = new URL(json.serverurls.cdn) + .toString() + .replace(/\/$/, ""); + this.serverurls.gateway = new URL(json.serverurls.gateway) + .toString() + .replace(/\/$/, ""); + this.serverurls.wellknown = new URL(json.serverurls.wellknown) + .toString() + .replace(/\/$/, ""); + this.serverurls.login = new URL(json.serverurls.login) + .toString() + .replace(/\/$/, ""); + this.email = json.email; + this.token = json.token; + this.loggedin = json.loggedin; + this.json = json; + this.json.localuserStore ??= {}; + if(!this.serverurls || !this.email || !this.token){ + console.error( + "There are fundamentally missing pieces of info missing from this user" + ); + } + } + set pfpsrc(e){ + this.json.pfpsrc = e; + this.updateLocal(); + } + get pfpsrc(){ + return this.json.pfpsrc; + } + set username(e){ + this.json.username = e; + this.updateLocal(); + } + get username(){ + return this.json.username; + } + set localuserStore(e){ + this.json.localuserStore = e; + this.updateLocal(); + } + get localuserStore(){ + return this.json.localuserStore; + } + get uid(){ + return this.email + this.serverurls.wellknown; + } + toJSON(){ + return this.json; + } + updateLocal(){ + const info = getBulkInfo(); + info.users[this.uid] = this.toJSON(); + localStorage.setItem("userinfos", JSON.stringify(info)); + } } -this.serverurls = json.serverurls; -let apistring = new URL(json.serverurls.api).toString(); -apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; -this.serverurls.api = apistring; -this.serverurls.cdn = new URL(json.serverurls.cdn) -.toString() -.replace(/\/$/, ""); -this.serverurls.gateway = new URL(json.serverurls.gateway) -.toString() -.replace(/\/$/, ""); -this.serverurls.wellknown = new URL(json.serverurls.wellknown) -.toString() -.replace(/\/$/, ""); -this.serverurls.login = new URL(json.serverurls.login) -.toString() -.replace(/\/$/, ""); -this.email = json.email; -this.token = json.token; -this.loggedin = json.loggedin; -this.json = json; -this.json.localuserStore ??= {}; -if (!this.serverurls || !this.email || !this.token) { -console.error( -"There are fundamentally missing pieces of info missing from this user" -); -} -} -set pfpsrc(e) { -this.json.pfpsrc = e; -this.updateLocal(); -} -get pfpsrc() { -return this.json.pfpsrc; -} -set username(e) { -this.json.username = e; -this.updateLocal(); -} -get username() { -return this.json.username; -} -set localuserStore(e) { -this.json.localuserStore = e; -this.updateLocal(); -} -get localuserStore() { -return this.json.localuserStore; -} -get uid() { -return this.email + this.serverurls.wellknown; -} -toJSON() { -return this.json; -} -updateLocal() { -const info = getBulkInfo(); -info.users[this.uid] = this.toJSON(); -localStorage.setItem("userinfos", JSON.stringify(info)); -} -} -function adduser(user: typeof Specialuser.prototype.json) { -user = new Specialuser(user); -const info = getBulkInfo(); -info.users[user.uid] = user; -info.currentuser = user.uid; -localStorage.setItem("userinfos", JSON.stringify(info)); -return user; +function adduser(user: typeof Specialuser.prototype.json){ + user = new Specialuser(user); + const info = getBulkInfo(); + info.users[user.uid] = user; + info.currentuser = user.uid; + localStorage.setItem("userinfos", JSON.stringify(info)); + return user; } const instancein = document.getElementById("instancein") as HTMLInputElement; let timeout: string | number | NodeJS.Timeout | undefined; // let instanceinfo; const stringURLMap = new Map(); - const stringURLsMap = new Map< +const stringURLsMap = new Map< string, { wellknown: string; @@ -213,7 +213,7 @@ const stringURLMap = new Map(); login?: string; } >(); - async function getapiurls(str: string): Promise< +async function getapiurls(str: string): Promise< | { api: string; cdn: string; @@ -222,99 +222,98 @@ const stringURLMap = new Map(); login: string; } | false - > { - if (!URL.canParse(str)) { - const val = stringURLMap.get(str); - if (val) { - str = val; - } else { - const val = stringURLsMap.get(str); - if (val) { - const responce = await fetch( - val.api + val.api.endsWith("/") ? "" : "/" + "ping" - ); - if (responce.ok) { - if (val.login) { - return val as { + >{ + if(!URL.canParse(str)){ + const val = stringURLMap.get(str); + if(val){ + str = val; + }else{ + const val = stringURLsMap.get(str); + if(val){ + const responce = await fetch( + val.api + val.api.endsWith("/") ? "" : "/" + "ping" + ); + if(responce.ok){ + if(val.login){ + return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; - } else { - val.login = val.api; - return val as { + }else{ + val.login = val.api; + return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; + } + } + } + } } - } - } - } - } - if (str.at(-1) !== "/") { - str += "/"; + if(str.at(-1) !== "/"){ + str += "/"; } let api: string; - try { - const info = await fetch(`${str}/.well-known/spacebar`).then((x) => - x.json() - ); - api = info.api; - } catch { - return false; + try{ + const info = await fetch(`${str}/.well-known/spacebar`).then(x=>x.json() + ); + api = info.api; + }catch{ + return false; } const url = new URL(api); - try { - const info = await fetch( - `${api}${ - url.pathname.includes("api") ? "" : "api" - }/policies/instance/domains` - ).then((x) => x.json()); - return { - api: info.apiEndpoint, - gateway: info.gateway, - cdn: info.cdn, - wellknown: str, - login: url.toString(), - }; - } catch { - const val = stringURLsMap.get(str); - if (val) { - const responce = await fetch( - val.api + val.api.endsWith("/") ? "" : "/" + "ping" - ); - if (responce.ok) { - if (val.login) { - return val as { + try{ + const info = await fetch( + `${api}${ + url.pathname.includes("api") ? "" : "api" + }/policies/instance/domains` + ).then(x=>x.json()); + return{ + api: info.apiEndpoint, + gateway: info.gateway, + cdn: info.cdn, + wellknown: str, + login: url.toString(), + }; + }catch{ + const val = stringURLsMap.get(str); + if(val){ + const responce = await fetch( + val.api + val.api.endsWith("/") ? "" : "/" + "ping" + ); + if(responce.ok){ + if(val.login){ + return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; - } else { - val.login = val.api; - return val as { + }else{ + val.login = val.api; + return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; + } + } + } + return false; } - } - } - return false; - } - } - async function checkInstance(instance?: string) { +} +async function checkInstance(instance?: string){ const verify = document.getElementById("verify"); - try { + try{ verify!.textContent = "Checking Instance"; const instanceValue = instance || (instancein as HTMLInputElement).value; const instanceinfo = (await getapiurls(instanceValue)) as { @@ -325,196 +324,196 @@ const stringURLMap = new Map(); login: string; value: string; }; - if (instanceinfo) { - instanceinfo.value = instanceValue; - localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); + if(instanceinfo){ + instanceinfo.value = instanceValue; + localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); verify!.textContent = "Instance is all good"; // @ts-ignore - if (checkInstance.alt) { + if(checkInstance.alt){ // @ts-ignore - checkInstance.alt(); + checkInstance.alt(); } - setTimeout((_: any) => { - console.log(verify!.textContent); + setTimeout((_: any)=>{ + console.log(verify!.textContent); verify!.textContent = ""; }, 3000); - } else { + }else{ verify!.textContent = "Invalid Instance, try again"; } - } catch { - console.log("catch"); + }catch{ + console.log("catch"); verify!.textContent = "Invalid Instance, try again"; } - } +} - if (instancein) { +if(instancein){ console.log(instancein); - instancein.addEventListener("keydown", (_) => { - const verify = document.getElementById("verify"); + instancein.addEventListener("keydown", _=>{ + const verify = document.getElementById("verify"); verify!.textContent = "Waiting to check Instance"; clearTimeout(timeout); - timeout = setTimeout(() => checkInstance(), 1000); + timeout = setTimeout(()=>checkInstance(), 1000); }); - if (localStorage.getItem("instanceinfo")) { - const json = JSON.parse(localStorage.getItem("instanceinfo")!); - if (json.value) { - (instancein as HTMLInputElement).value = json.value; - } else { - (instancein as HTMLInputElement).value = json.wellknown; - } - } else { - checkInstance("https://spacebar.chat/"); - } + if(localStorage.getItem("instanceinfo")){ + const json = JSON.parse(localStorage.getItem("instanceinfo")!); + if(json.value){ + (instancein as HTMLInputElement).value = json.value; + }else{ + (instancein as HTMLInputElement).value = json.wellknown; + } + }else{ + checkInstance("https://spacebar.chat/"); } +} - async function login(username: string, password: string, captcha: string) { - if (captcha === "") { - captcha = ""; +async function login(username: string, password: string, captcha: string){ + if(captcha === ""){ + captcha = ""; } const options = { - method: "POST", - body: JSON.stringify({ - login: username, - password, - undelete: false, - captcha_key: captcha, - }), - headers: { - "Content-type": "application/json; charset=UTF-8", - }, + method: "POST", + body: JSON.stringify({ + login: username, + password, + undelete: false, + captcha_key: captcha, + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, }; - try { - const info = JSON.parse(localStorage.getItem("instanceinfo")!); - const api = info.login + (info.login.startsWith("/") ? "/" : ""); - return await fetch(api + "/auth/login", options) - .then((response) => response.json()) - .then((response) => { - console.log(response, response.message); - if (response.message === "Invalid Form Body") { - return response.errors.login._errors[0].message; - console.log("test"); - } - //this.serverurls||!this.email||!this.token - console.log(response); + try{ + const info = JSON.parse(localStorage.getItem("instanceinfo")!); + const api = info.login + (info.login.startsWith("/") ? "/" : ""); + return await fetch(api + "/auth/login", options) + .then(response=>response.json()) + .then(response=>{ + console.log(response, response.message); + if(response.message === "Invalid Form Body"){ + return response.errors.login._errors[0].message; + console.log("test"); + } + //this.serverurls||!this.email||!this.token + console.log(response); - if (response.captcha_sitekey) { - const capt = document.getElementById("h-captcha"); - if (!capt!.children.length) { - const capty = document.createElement("div"); - capty.classList.add("h-captcha"); + if(response.captcha_sitekey){ + const capt = document.getElementById("h-captcha"); + if(!capt!.children.length){ + const capty = document.createElement("div"); + capty.classList.add("h-captcha"); - capty.setAttribute("data-sitekey", response.captcha_sitekey); - const script = document.createElement("script"); - script.src = "https://js.hcaptcha.com/1/api.js"; + capty.setAttribute("data-sitekey", response.captcha_sitekey); + const script = document.createElement("script"); + script.src = "https://js.hcaptcha.com/1/api.js"; capt!.append(script); capt!.append(capty); - } else { - eval("hcaptcha.reset()"); - } - } else { - console.log(response); - if (response.ticket) { - let onetimecode = ""; - new Dialog([ - "vdiv", - ["title", "2FA code:"], - [ - "textbox", - "", - "", - function (this: HTMLInputElement) { - onetimecode = this.value; - }, - ], - [ - "button", - "", - "Submit", - function () { - fetch(api + "/auth/mfa/totp", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - code: onetimecode, - ticket: response.ticket, - }), - }) - .then((r) => r.json()) - .then((response) => { - if (response.message) { - alert(response.message); - } else { - console.warn(response); - if (!response.token) return; - adduser({ - serverurls: JSON.parse( + }else{ + eval("hcaptcha.reset()"); + } + }else{ + console.log(response); + if(response.ticket){ + let onetimecode = ""; + new Dialog([ + "vdiv", + ["title", "2FA code:"], + [ + "textbox", + "", + "", + function(this: HTMLInputElement){ + onetimecode = this.value; + }, + ], + [ + "button", + "", + "Submit", + function(){ + fetch(api + "/auth/mfa/totp", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + code: onetimecode, + ticket: response.ticket, + }), + }) + .then(r=>r.json()) + .then(response=>{ + if(response.message){ + alert(response.message); + }else{ + console.warn(response); + if(!response.token)return; + adduser({ + serverurls: JSON.parse( localStorage.getItem("instanceinfo")! - ), - email: username, - token: response.token, - }).username = username; - const redir = new URLSearchParams( - window.location.search - ).get("goback"); - if (redir) { - window.location.href = redir; - } else { - window.location.href = "/channels/@me"; - } - } - }); - }, - ], - ]).show(); - } else { - console.warn(response); - if (!response.token) return; - adduser({ - serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), - email: username, - token: response.token, - }).username = username; - const redir = new URLSearchParams(window.location.search).get( - "goback" - ); - if (redir) { - window.location.href = redir; - } else { - window.location.href = "/channels/@me"; - } - return ""; - } - } - }); - } catch (error) { - console.error("Error:", error); - } + ), + email: username, + token: response.token, + }).username = username; + const redir = new URLSearchParams( + window.location.search + ).get("goback"); + if(redir){ + window.location.href = redir; + }else{ + window.location.href = "/channels/@me"; + } + } + }); + }, + ], + ]).show(); + }else{ + console.warn(response); + if(!response.token)return; + adduser({ + serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), + email: username, + token: response.token, + }).username = username; + const redir = new URLSearchParams(window.location.search).get( + "goback" + ); + if(redir){ + window.location.href = redir; + }else{ + window.location.href = "/channels/@me"; + } + return""; + } + } + }); + }catch(error){ + console.error("Error:", error); } +} - async function check(e: SubmitEvent) { +async function check(e: SubmitEvent){ e.preventDefault(); const target = e.target as HTMLFormElement; const h = await login( - (target[1] as HTMLInputElement).value, - (target[2] as HTMLInputElement).value, - (target[3] as HTMLInputElement).value + (target[1] as HTMLInputElement).value, + (target[2] as HTMLInputElement).value, + (target[3] as HTMLInputElement).value ); const wrongElement = document.getElementById("wrong"); - if (wrongElement) { - wrongElement.textContent = h; + if(wrongElement){ + wrongElement.textContent = h; } console.log(h); - } - if (document.getElementById("form")) { +} +if(document.getElementById("form")){ const form = document.getElementById("form"); - if (form) { - form.addEventListener("submit", (e: SubmitEvent) => check(e)); + if(form){ + form.addEventListener("submit", (e: SubmitEvent)=>check(e)); } - } - //this currently does not work, and need to be implemented better at some time. - /* +} +//this currently does not work, and need to be implemented better at some time. +/* if ("serviceWorker" in navigator){ navigator.serviceWorker.register("/service.js", { scope: "/", @@ -539,19 +538,19 @@ const stringURLMap = new Map(); }) } */ - const switchurl = document.getElementById("switch") as HTMLAreaElement; - if (switchurl) { +const switchurl = document.getElementById("switch") as HTMLAreaElement; +if(switchurl){ switchurl.href += window.location.search; const instance = new URLSearchParams(window.location.search).get("instance"); console.log(instance); - if (instance) { - instancein.value = instance; - checkInstance(""); + if(instance){ + instancein.value = instance; + checkInstance(""); } - } - export { checkInstance }; - trimswitcher(); - export { +} +export{ checkInstance }; +trimswitcher(); +export{ mobile, getBulkUsers, getBulkInfo, @@ -559,19 +558,19 @@ const stringURLMap = new Map(); Specialuser, getapiurls, adduser, - }; +}; - const datalist = document.getElementById("instances"); - console.warn(datalist); - export function getInstances() { +const datalist = document.getElementById("instances"); +console.warn(datalist); +export function getInstances(){ return instances; - } +} - fetch("/instances.json") - .then((_) => _.json()) +fetch("/instances.json") + .then(_=>_.json()) .then( - ( - json: { + ( + json: { name: string; description?: string; descriptionLong?: string; @@ -588,38 +587,38 @@ const stringURLMap = new Map(); login?: string; }; }[] - ) => { - instances = json; - if (datalist) { - console.warn(json); - if (instancein && instancein.value === "") { - instancein.value = json[0].name; - } - for (const instance of json) { - if (instance.display === false) { - continue; - } - const option = document.createElement("option"); - option.disabled = !instance.online; - option.value = instance.name; - if (instance.url) { - stringURLMap.set(option.value, instance.url); - if (instance.urls) { - stringURLsMap.set(instance.url, instance.urls); - } - } else if (instance.urls) { - stringURLsMap.set(option.value, instance.urls); - } else { - option.disabled = true; - } - if (instance.description) { - option.label = instance.description; - } else { - option.label = instance.name; - } - datalist.append(option); - } - checkInstance(""); - } - } + )=>{ + instances = json; + if(datalist){ + console.warn(json); + if(instancein && instancein.value === ""){ + instancein.value = json[0].name; + } + for(const instance of json){ + if(instance.display === false){ + continue; + } + const option = document.createElement("option"); + option.disabled = !instance.online; + option.value = instance.name; + if(instance.url){ + stringURLMap.set(option.value, instance.url); + if(instance.urls){ + stringURLsMap.set(instance.url, instance.urls); + } + }else if(instance.urls){ + stringURLsMap.set(option.value, instance.urls); + }else{ + option.disabled = true; + } + if(instance.description){ + option.label = instance.description; + }else{ + option.label = instance.name; + } + datalist.append(option); + } + checkInstance(""); + } + } ); diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index f16f002..93220bc 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -1,818 +1,818 @@ -import { Channel } from "./channel.js"; -import { Dialog } from "./dialog.js"; -import { Emoji } from "./emoji.js"; -import { Guild } from "./guild.js"; -import { Localuser } from "./localuser.js"; -import { Member } from "./member.js"; +import{ Channel }from"./channel.js"; +import{ Dialog }from"./dialog.js"; +import{ Emoji }from"./emoji.js"; +import{ Guild }from"./guild.js"; +import{ Localuser }from"./localuser.js"; +import{ Member }from"./member.js"; -class MarkDown { -txt: string[]; -keep: boolean; -stdsize: boolean; -owner: Localuser | Channel; -info: Localuser["info"]; -constructor( -text: string | string[], -owner: MarkDown["owner"], -{ keep = false, stdsize = false } = {} -) { -if (typeof text === typeof "") { -this.txt = (text as string).split(""); -} else { -this.txt = text as string[]; -} -if (this.txt === undefined) { -this.txt = []; -} -this.info = owner.info; -this.keep = keep; -this.owner = owner; -this.stdsize = stdsize; -} -get localuser() { -if (this.owner instanceof Localuser) { -return this.owner; -} else { -return this.owner.localuser; -} -} -get rawString() { -return this.txt.join(""); -} -get textContent() { -return this.makeHTML().textContent; -} -makeHTML({ keep = this.keep, stdsize = this.stdsize } = {}) { -return this.markdown(this.txt, { keep, stdsize }); -} -markdown(text: string | string[], { keep = false, stdsize = false } = {}) { -let txt: string[]; -if (typeof text === typeof "") { -txt = (text as string).split(""); -} else { -txt = text as string[]; -} -if (txt === undefined) { -txt = []; -} -const span = document.createElement("span"); -let current = document.createElement("span"); -function appendcurrent() { -if (current.innerHTML !== "") { -span.append(current); -current = document.createElement("span"); -} -} -for (let i = 0; i < txt.length; i++) { -if (txt[i] === "\n" || i === 0) { -const first = i === 0; -if (first) { -i--; -} -let element: HTMLElement = document.createElement("span"); -let keepys = ""; +class MarkDown{ + txt: string[]; + keep: boolean; + stdsize: boolean; + owner: Localuser | Channel; + info: Localuser["info"]; + constructor( + text: string | string[], + owner: MarkDown["owner"], + { keep = false, stdsize = false } = {} + ){ + if(typeof text === typeof ""){ + this.txt = (text as string).split(""); + }else{ + this.txt = text as string[]; + } + if(this.txt === undefined){ + this.txt = []; + } + this.info = owner.info; + this.keep = keep; + this.owner = owner; + this.stdsize = stdsize; + } + get localuser(){ + if(this.owner instanceof Localuser){ + return this.owner; + }else{ + return this.owner.localuser; + } + } + get rawString(){ + return this.txt.join(""); + } + get textContent(){ + return this.makeHTML().textContent; + } + makeHTML({ keep = this.keep, stdsize = this.stdsize } = {}){ + return this.markdown(this.txt, { keep, stdsize }); + } + markdown(text: string | string[], { keep = false, stdsize = false } = {}){ + let txt: string[]; + if(typeof text === typeof ""){ + txt = (text as string).split(""); + }else{ + txt = text as string[]; + } + if(txt === undefined){ + txt = []; + } + const span = document.createElement("span"); + let current = document.createElement("span"); + function appendcurrent(){ + if(current.innerHTML !== ""){ + span.append(current); + current = document.createElement("span"); + } + } + for(let i = 0; i < txt.length; i++){ + if(txt[i] === "\n" || i === 0){ + const first = i === 0; + if(first){ + i--; + } + let element: HTMLElement = document.createElement("span"); + let keepys = ""; -if (txt[i + 1] === "#") { -if (txt[i + 2] === "#") { -if (txt[i + 3] === "#" && txt[i + 4] === " ") { -element = document.createElement("h3"); -keepys = "### "; -i += 5; -} else if (txt[i + 3] === " ") { -element = document.createElement("h2"); -element.classList.add("h2md"); -keepys = "## "; -i += 4; -} -} else if (txt[i + 2] === " ") { -element = document.createElement("h1"); -keepys = "# "; -i += 3; -} -} else if (txt[i + 1] === ">" && txt[i + 2] === " ") { -element = document.createElement("div"); -const line = document.createElement("div"); -line.classList.add("quoteline"); -element.append(line); -element.classList.add("quote"); -keepys = "> "; -i += 3; -} -if (keepys) { -appendcurrent(); -if (!first && !stdsize) { -span.appendChild(document.createElement("br")); -} -const build: string[] = []; -for (; txt[i] !== "\n" && txt[i] !== undefined; i++) { -build.push(txt[i]); -} -try { -if (stdsize) { -element = document.createElement("span"); -} -if (keep) { -element.append(keepys); -//span.appendChild(document.createElement("br")); -} -element.appendChild(this.markdown(build, { keep, stdsize })); -span.append(element); -} finally { -i -= 1; -continue; -} -} -if (first) { -i++; -} -} -if (txt[i] === "\n") { -if (!stdsize) { -appendcurrent(); -span.append(document.createElement("br")); -} -continue; -} -if (txt[i] === "`") { -let count = 1; -if (txt[i + 1] === "`") { -count++; -if (txt[i + 2] === "`") { -count++; -} -} -let build = ""; -if (keep) { -build += "`".repeat(count); -} -let find = 0; -let j = i + count; -let init = true; -for ( -; -txt[j] !== undefined && + if(txt[i + 1] === "#"){ + if(txt[i + 2] === "#"){ + if(txt[i + 3] === "#" && txt[i + 4] === " "){ + element = document.createElement("h3"); + keepys = "### "; + i += 5; + }else if(txt[i + 3] === " "){ + element = document.createElement("h2"); + element.classList.add("h2md"); + keepys = "## "; + i += 4; + } + }else if(txt[i + 2] === " "){ + element = document.createElement("h1"); + keepys = "# "; + i += 3; + } + }else if(txt[i + 1] === ">" && txt[i + 2] === " "){ + element = document.createElement("div"); + const line = document.createElement("div"); + line.classList.add("quoteline"); + element.append(line); + element.classList.add("quote"); + keepys = "> "; + i += 3; + } + if(keepys){ + appendcurrent(); + if(!first && !stdsize){ + span.appendChild(document.createElement("br")); + } + const build: string[] = []; + for(; txt[i] !== "\n" && txt[i] !== undefined; i++){ + build.push(txt[i]); + } + try{ + if(stdsize){ + element = document.createElement("span"); + } + if(keep){ + element.append(keepys); + //span.appendChild(document.createElement("br")); + } + element.appendChild(this.markdown(build, { keep, stdsize })); + span.append(element); + }finally{ + i -= 1; + continue; + } + } + if(first){ + i++; + } + } + if(txt[i] === "\n"){ + if(!stdsize){ + appendcurrent(); + span.append(document.createElement("br")); + } + continue; + } + if(txt[i] === "`"){ + let count = 1; + if(txt[i + 1] === "`"){ + count++; + if(txt[i + 2] === "`"){ + count++; + } + } + let build = ""; + if(keep){ + build += "`".repeat(count); + } + let find = 0; + let j = i + count; + let init = true; + for( + ; + txt[j] !== undefined && (txt[j] !== "\n" || count === 3) && find !== count; -j++ -) { -if (txt[j] === "`") { -find++; -} else { -if (find !== 0) { -build += "`".repeat(find); -find = 0; -} -if (init && count === 3) { -if (txt[j] === " " || txt[j] === "\n") { -init = false; -} -if (keep) { -build += txt[j]; -} -continue; -} -build += txt[j]; -} -} -if (stdsize) { -build = build.replaceAll("\n", ""); -} -if (find === count) { -appendcurrent(); -i = j; -if (keep) { -build += "`".repeat(find); -} -if (count !== 3 && !stdsize) { -const samp = document.createElement("samp"); -samp.textContent = build; -span.appendChild(samp); -} else { -const pre = document.createElement("pre"); -if (build.at(-1) === "\n") { -build = build.substring(0, build.length - 1); -} -if (txt[i] === "\n") { -i++; -} -pre.textContent = build; -span.appendChild(pre); -} -i--; -continue; -} -} + j++ + ){ + if(txt[j] === "`"){ + find++; + }else{ + if(find !== 0){ + build += "`".repeat(find); + find = 0; + } + if(init && count === 3){ + if(txt[j] === " " || txt[j] === "\n"){ + init = false; + } + if(keep){ + build += txt[j]; + } + continue; + } + build += txt[j]; + } + } + if(stdsize){ + build = build.replaceAll("\n", ""); + } + if(find === count){ + appendcurrent(); + i = j; + if(keep){ + build += "`".repeat(find); + } + if(count !== 3 && !stdsize){ + const samp = document.createElement("samp"); + samp.textContent = build; + span.appendChild(samp); + }else{ + const pre = document.createElement("pre"); + if(build.at(-1) === "\n"){ + build = build.substring(0, build.length - 1); + } + if(txt[i] === "\n"){ + i++; + } + pre.textContent = build; + span.appendChild(pre); + } + i--; + continue; + } + } -if (txt[i] === "*") { -let count = 1; -if (txt[i + 1] === "*") { -count++; -if (txt[i + 2] === "*") { -count++; -} -} -let build: string[] = []; -let find = 0; -let j = i + count; -for (; txt[j] !== undefined && find !== count; j++) { -if (txt[j] === "*") { -find++; -} else { -build.push(txt[j]); -if (find !== 0) { -build = build.concat(new Array(find).fill("*")); -find = 0; -} -} -} -if (find === count && (count != 1 || txt[i + 1] !== " ")) { -appendcurrent(); -i = j; + if(txt[i] === "*"){ + let count = 1; + if(txt[i + 1] === "*"){ + count++; + if(txt[i + 2] === "*"){ + count++; + } + } + let build: string[] = []; + let find = 0; + let j = i + count; + for(; txt[j] !== undefined && find !== count; j++){ + if(txt[j] === "*"){ + find++; + }else{ + build.push(txt[j]); + if(find !== 0){ + build = build.concat(new Array(find).fill("*")); + find = 0; + } + } + } + if(find === count && (count != 1 || txt[i + 1] !== " ")){ + appendcurrent(); + i = j; -const stars = "*".repeat(count); -if (count === 1) { -const i = document.createElement("i"); -if (keep) { -i.append(stars); -} -i.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -i.append(stars); -} -span.appendChild(i); -} else if (count === 2) { -const b = document.createElement("b"); -if (keep) { -b.append(stars); -} -b.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -b.append(stars); -} -span.appendChild(b); -} else { -const b = document.createElement("b"); -const i = document.createElement("i"); -if (keep) { -b.append(stars); -} -b.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -b.append(stars); -} -i.appendChild(b); -span.appendChild(i); -} -i--; -continue; -} -} + const stars = "*".repeat(count); + if(count === 1){ + const i = document.createElement("i"); + if(keep){ + i.append(stars); + } + i.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + i.append(stars); + } + span.appendChild(i); + }else if(count === 2){ + const b = document.createElement("b"); + if(keep){ + b.append(stars); + } + b.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + b.append(stars); + } + span.appendChild(b); + }else{ + const b = document.createElement("b"); + const i = document.createElement("i"); + if(keep){ + b.append(stars); + } + b.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + b.append(stars); + } + i.appendChild(b); + span.appendChild(i); + } + i--; + continue; + } + } -if (txt[i] === "_") { -let count = 1; -if (txt[i + 1] === "_") { -count++; -if (txt[i + 2] === "_") { -count++; -} -} -let build: string[] = []; -let find = 0; -let j = i + count; -for (; txt[j] !== undefined && find !== count; j++) { -if (txt[j] === "_") { -find++; -} else { -build.push(txt[j]); -if (find !== 0) { -build = build.concat(new Array(find).fill("_")); -find = 0; -} -} -} -if ( -find === count && + if(txt[i] === "_"){ + let count = 1; + if(txt[i + 1] === "_"){ + count++; + if(txt[i + 2] === "_"){ + count++; + } + } + let build: string[] = []; + let find = 0; + let j = i + count; + for(; txt[j] !== undefined && find !== count; j++){ + if(txt[j] === "_"){ + find++; + }else{ + build.push(txt[j]); + if(find !== 0){ + build = build.concat(new Array(find).fill("_")); + find = 0; + } + } + } + if( + find === count && (count != 1 || txt[j + 1] === " " || txt[j + 1] === "\n" || txt[j + 1] === undefined) -) { -appendcurrent(); -i = j; -const underscores = "_".repeat(count); -if (count === 1) { -const i = document.createElement("i"); -if (keep) { -i.append(underscores); -} -i.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -i.append(underscores); -} -span.appendChild(i); -} else if (count === 2) { -const u = document.createElement("u"); -if (keep) { -u.append(underscores); -} -u.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -u.append(underscores); -} -span.appendChild(u); -} else { -const u = document.createElement("u"); -const i = document.createElement("i"); -if (keep) { -i.append(underscores); -} -i.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -i.append(underscores); -} -u.appendChild(i); -span.appendChild(u); -} -i--; -continue; -} -} + ){ + appendcurrent(); + i = j; + const underscores = "_".repeat(count); + if(count === 1){ + const i = document.createElement("i"); + if(keep){ + i.append(underscores); + } + i.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + i.append(underscores); + } + span.appendChild(i); + }else if(count === 2){ + const u = document.createElement("u"); + if(keep){ + u.append(underscores); + } + u.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + u.append(underscores); + } + span.appendChild(u); + }else{ + const u = document.createElement("u"); + const i = document.createElement("i"); + if(keep){ + i.append(underscores); + } + i.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + i.append(underscores); + } + u.appendChild(i); + span.appendChild(u); + } + i--; + continue; + } + } -if (txt[i] === "~" && txt[i + 1] === "~") { -const count = 2; -let build: string[] = []; -let find = 0; -let j = i + 2; -for (; txt[j] !== undefined && find !== count; j++) { -if (txt[j] === "~") { -find++; -} else { -build.push(txt[j]); -if (find !== 0) { -build = build.concat(new Array(find).fill("~")); -find = 0; -} -} -} -if (find === count) { -appendcurrent(); -i = j - 1; -const tildes = "~~"; -if (count === 2) { -const s = document.createElement("s"); -if (keep) { -s.append(tildes); -} -s.appendChild(this.markdown(build, { keep, stdsize })); -if (keep) { -s.append(tildes); -} -span.appendChild(s); -} -continue; -} -} -if (txt[i] === "|" && txt[i + 1] === "|") { -const count = 2; -let build: string[] = []; -let find = 0; -let j = i + 2; -for (; txt[j] !== undefined && find !== count; j++) { -if (txt[j] === "|") { -find++; -} else { -build.push(txt[j]); -if (find !== 0) { -build = build.concat(new Array(find).fill("~")); -find = 0; -} -} -} -if (find === count) { -appendcurrent(); -i = j - 1; -const pipes = "||"; -if (count === 2) { -const j = document.createElement("j"); -if (keep) { -j.append(pipes); -} -j.appendChild(this.markdown(build, { keep, stdsize })); -j.classList.add("spoiler"); -j.onclick = MarkDown.unspoil; -if (keep) { -j.append(pipes); -} -span.appendChild(j); -} -continue; -} -} -if ( -!keep && + if(txt[i] === "~" && txt[i + 1] === "~"){ + const count = 2; + let build: string[] = []; + let find = 0; + let j = i + 2; + for(; txt[j] !== undefined && find !== count; j++){ + if(txt[j] === "~"){ + find++; + }else{ + build.push(txt[j]); + if(find !== 0){ + build = build.concat(new Array(find).fill("~")); + find = 0; + } + } + } + if(find === count){ + appendcurrent(); + i = j - 1; + const tildes = "~~"; + if(count === 2){ + const s = document.createElement("s"); + if(keep){ + s.append(tildes); + } + s.appendChild(this.markdown(build, { keep, stdsize })); + if(keep){ + s.append(tildes); + } + span.appendChild(s); + } + continue; + } + } + if(txt[i] === "|" && txt[i + 1] === "|"){ + const count = 2; + let build: string[] = []; + let find = 0; + let j = i + 2; + for(; txt[j] !== undefined && find !== count; j++){ + if(txt[j] === "|"){ + find++; + }else{ + build.push(txt[j]); + if(find !== 0){ + build = build.concat(new Array(find).fill("~")); + find = 0; + } + } + } + if(find === count){ + appendcurrent(); + i = j - 1; + const pipes = "||"; + if(count === 2){ + const j = document.createElement("j"); + if(keep){ + j.append(pipes); + } + j.appendChild(this.markdown(build, { keep, stdsize })); + j.classList.add("spoiler"); + j.onclick = MarkDown.unspoil; + if(keep){ + j.append(pipes); + } + span.appendChild(j); + } + continue; + } + } + if( + !keep && txt[i] === "h" && txt[i + 1] === "t" && txt[i + 2] === "t" && txt[i + 3] === "p" -) { -let build = "http"; -let j = i + 4; -const endchars = new Set(["\\", "<", ">", "|", "]", " "]); -for (; txt[j] !== undefined; j++) { -const char = txt[j]; -if (endchars.has(char)) { -break; -} -build += char; -} -if (URL.canParse(build)) { -appendcurrent(); -const a = document.createElement("a"); -//a.href=build; -MarkDown.safeLink(a, build); -a.textContent = build; -a.target = "_blank"; -i = j - 1; -span.appendChild(a); -continue; -} -} -if (txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#")) { -let id = ""; -let j = i + 2; -const numbers = new Set([ -"0", -"1", -"2", -"3", -"4", -"5", -"6", -"7", -"8", -"9", -]); -for (; txt[j] !== undefined; j++) { -const char = txt[j]; -if (!numbers.has(char)) { -break; -} -id += char; -} + ){ + let build = "http"; + let j = i + 4; + const endchars = new Set(["\\", "<", ">", "|", "]", " "]); + for(; txt[j] !== undefined; j++){ + const char = txt[j]; + if(endchars.has(char)){ + break; + } + build += char; + } + if(URL.canParse(build)){ + appendcurrent(); + const a = document.createElement("a"); + //a.href=build; + MarkDown.safeLink(a, build); + a.textContent = build; + a.target = "_blank"; + i = j - 1; + span.appendChild(a); + continue; + } + } + if(txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#")){ + let id = ""; + let j = i + 2; + const numbers = new Set([ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ]); + for(; txt[j] !== undefined; j++){ + const char = txt[j]; + if(!numbers.has(char)){ + break; + } + id += char; + } -if (txt[j] === ">") { -appendcurrent(); -const mention = document.createElement("span"); -mention.classList.add("mentionMD"); -mention.contentEditable = "false"; -const char = txt[i + 1]; -i = j; -switch (char) { -case "@": -const user = this.localuser.userMap.get(id); -if (user) { -mention.textContent = `@${user.name}`; -let guild: null | Guild = null; -if (this.owner instanceof Channel) { -guild = this.owner.guild; -} -if (!keep) { -user.bind(mention, guild); -} -if (guild) { -Member.resolveMember(user, guild).then((member) => { -if (member) { -mention.textContent = `@${member.name}`; -} -}); -} -} else { -mention.textContent = `@unknown`; -} -break; -case "#": -const channel = this.localuser.channelids.get(id); -if (channel) { -mention.textContent = `#${channel.name}`; -if (!keep) { -mention.onclick = (_) => { -this.localuser.goToChannel(id); -}; -} -} else { -mention.textContent = `#unknown`; -} -break; -} -span.appendChild(mention); -mention.setAttribute("real", `<${char}${id}>`); -continue; -} -} -if (txt[i] === "<" && txt[i + 1] === "t" && txt[i + 2] === ":") { -let found = false; -const build = ["<", "t", ":"]; -let j = i + 3; -for (; txt[j] !== void 0; j++) { -build.push(txt[j]); + if(txt[j] === ">"){ + appendcurrent(); + const mention = document.createElement("span"); + mention.classList.add("mentionMD"); + mention.contentEditable = "false"; + const char = txt[i + 1]; + i = j; + switch(char){ + case"@": + const user = this.localuser.userMap.get(id); + if(user){ + mention.textContent = `@${user.name}`; + let guild: null | Guild = null; + if(this.owner instanceof Channel){ + guild = this.owner.guild; + } + if(!keep){ + user.bind(mention, guild); + } + if(guild){ + Member.resolveMember(user, guild).then(member=>{ + if(member){ + mention.textContent = `@${member.name}`; + } + }); + } + }else{ + mention.textContent = "@unknown"; + } + break; + case"#": + const channel = this.localuser.channelids.get(id); + if(channel){ + mention.textContent = `#${channel.name}`; + if(!keep){ + mention.onclick = _=>{ + this.localuser.goToChannel(id); + }; + } + }else{ + mention.textContent = "#unknown"; + } + break; + } + span.appendChild(mention); + mention.setAttribute("real", `<${char}${id}>`); + continue; + } + } + if(txt[i] === "<" && txt[i + 1] === "t" && txt[i + 2] === ":"){ + let found = false; + const build = ["<", "t", ":"]; + let j = i + 3; + for(; txt[j] !== void 0; j++){ + build.push(txt[j]); -if (txt[j] === ">") { -found = true; -break; -} -} + if(txt[j] === ">"){ + found = true; + break; + } + } -if (found) { -appendcurrent(); -i = j; -const parts = build -.join("") -.match(/^$/) as RegExpMatchArray; - const dateInput = new Date(Number.parseInt(parts[1]) * 1000); - let time = ""; - if (Number.isNaN(dateInput.getTime())) time = build.join(""); - else { - if (parts[3] === "d") - time = dateInput.toLocaleString(void 0, { - day: "2-digit", - month: "2-digit", - year: "numeric", - }); - else if (parts[3] === "D") - time = dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - }); - else if (!parts[3] || parts[3] === "f") - time = + if(found){ + appendcurrent(); + i = j; + const parts = build + .join("") + .match(/^$/) as RegExpMatchArray; + const dateInput = new Date(Number.parseInt(parts[1]) * 1000); + let time = ""; + if(Number.isNaN(dateInput.getTime())) time = build.join(""); + else{ + if(parts[3] === "d") + time = dateInput.toLocaleString(void 0, { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + else if(parts[3] === "D") + time = dateInput.toLocaleString(void 0, { + day: "numeric", + month: "long", + year: "numeric", + }); + else if(!parts[3] || parts[3] === "f") + time = dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", + day: "numeric", + month: "long", + year: "numeric", }) + " " + dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", + hour: "2-digit", + minute: "2-digit", }); - else if (parts[3] === "F") - time = + else if(parts[3] === "F") + time = dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - weekday: "long", + day: "numeric", + month: "long", + year: "numeric", + weekday: "long", }) + " " + dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", + hour: "2-digit", + minute: "2-digit", }); - else if (parts[3] === "t") - time = dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - }); - else if (parts[3] === "T") - time = dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - else if (parts[3] === "R") - time = + else if(parts[3] === "t") + time = dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + }); + else if(parts[3] === "T") + time = dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + else if(parts[3] === "R") + time = Math.round( - (Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60 + (Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60 ) + " minutes ago"; - } + } - const timeElem = document.createElement("span"); - timeElem.classList.add("markdown-timestamp"); - timeElem.textContent = time; - span.appendChild(timeElem); - continue; - } - } + const timeElem = document.createElement("span"); + timeElem.classList.add("markdown-timestamp"); + timeElem.textContent = time; + span.appendChild(timeElem); + continue; + } + } - if ( - txt[i] === "<" && + if( + txt[i] === "<" && (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")) - ) { - let found = false; - const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; - let j = i + build.length; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); + ){ + let found = false; + const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; + let j = i + build.length; + for(; txt[j] !== void 0; j++){ + build.push(txt[j]); - if (txt[j] === ">") { - found = true; - break; - } - } + if(txt[j] === ">"){ + found = true; + break; + } + } - if (found) { - const buildjoin = build.join(""); - const parts = buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); - if (parts && parts[2]) { - appendcurrent(); - i = j; - const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); - const owner = + if(found){ + const buildjoin = build.join(""); + const parts = buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); + if(parts && parts[2]){ + appendcurrent(); + i = j; + const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); + const owner = this.owner instanceof Channel ? this.owner.guild : this.owner; - const emoji = new Emoji( - { name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, - owner - ); - span.appendChild(emoji.getHTML(isEmojiOnly)); + const emoji = new Emoji( + { name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, + owner + ); + span.appendChild(emoji.getHTML(isEmojiOnly)); - continue; - } - } - } + continue; + } + } + } - if (txt[i] == "[" && !keep) { - let partsFound = 0; - let j = i + 1; - const build = ["["]; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); + if(txt[i] == "[" && !keep){ + let partsFound = 0; + let j = i + 1; + const build = ["["]; + for(; txt[j] !== void 0; j++){ + build.push(txt[j]); - if (partsFound === 0 && txt[j] === "]") { - if ( - txt[j + 1] === "(" && + if(partsFound === 0 && txt[j] === "]"){ + if( + txt[j + 1] === "(" && txt[j + 2] === "h" && txt[j + 3] === "t" && txt[j + 4] === "t" && txt[j + 5] === "p" && (txt[j + 6] === "s" || txt[j + 6] === ":") - ) { - partsFound++; - } else { - break; - } - } else if (partsFound === 1 && txt[j] === ")") { - partsFound++; - break; - } - } + ){ + partsFound++; + }else{ + break; + } + }else if(partsFound === 1 && txt[j] === ")"){ + partsFound++; + break; + } + } - if (partsFound === 2) { - appendcurrent(); + if(partsFound === 2){ + appendcurrent(); - const parts = build - .join("") - .match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/); - if (parts) { - const linkElem = document.createElement("a"); - if (URL.canParse(parts[2])) { - i = j; - MarkDown.safeLink(linkElem, parts[2]); - linkElem.textContent = parts[1]; - linkElem.target = "_blank"; - linkElem.rel = "noopener noreferrer"; - linkElem.title = + const parts = build + .join("") + .match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/); + if(parts){ + const linkElem = document.createElement("a"); + if(URL.canParse(parts[2])){ + i = j; + MarkDown.safeLink(linkElem, parts[2]); + linkElem.textContent = parts[1]; + linkElem.target = "_blank"; + linkElem.rel = "noopener noreferrer"; + linkElem.title = (parts[3] - ? parts[3].substring(2, parts[3].length - 1) + "\n\n" - : "") + parts[2]; - span.appendChild(linkElem); + ? parts[3].substring(2, parts[3].length - 1) + "\n\n" + : "") + parts[2]; + span.appendChild(linkElem); - continue; - } - } - } - } + continue; + } + } + } + } - current.textContent += txt[i]; + current.textContent += txt[i]; + } + appendcurrent(); + return span; } - appendcurrent(); - return span; + static unspoil(e: any): void{ + e.target.classList.remove("spoiler"); + e.target.classList.add("unspoiled"); } - static unspoil(e: any): void { - e.target.classList.remove("spoiler"); - e.target.classList.add("unspoiled"); - } - giveBox(box: HTMLDivElement) { - box.onkeydown = (_) => { - //console.log(_); - }; - let prevcontent = ""; - box.onkeyup = (_) => { - const content = MarkDown.gatherBoxText(box); - if (content !== prevcontent) { - prevcontent = content; - this.txt = content.split(""); - this.boxupdate(box); - } - }; - box.onpaste = (_) => { - if (!_.clipboardData) return; - console.log(_.clipboardData.types); - const data = _.clipboardData.getData("text"); + giveBox(box: HTMLDivElement){ + box.onkeydown = _=>{ + //console.log(_); + }; + let prevcontent = ""; + box.onkeyup = _=>{ + const content = MarkDown.gatherBoxText(box); + if(content !== prevcontent){ + prevcontent = content; + this.txt = content.split(""); + this.boxupdate(box); + } + }; + box.onpaste = _=>{ + if(!_.clipboardData)return; + console.log(_.clipboardData.types); + const data = _.clipboardData.getData("text"); - document.execCommand("insertHTML", false, data); - _.preventDefault(); - if (!box.onkeyup) return; - box.onkeyup(new KeyboardEvent("_")); - }; + document.execCommand("insertHTML", false, data); + _.preventDefault(); + if(!box.onkeyup)return; + box.onkeyup(new KeyboardEvent("_")); + }; } - boxupdate(box: HTMLElement) { - const restore = saveCaretPosition(box); - box.innerHTML = ""; - box.append(this.makeHTML({ keep: true })); - if (restore) { - restore(); + boxupdate(box: HTMLElement){ + const restore = saveCaretPosition(box); + box.innerHTML = ""; + box.append(this.makeHTML({ keep: true })); + if(restore){ + restore(); + } } - } - static gatherBoxText(element: HTMLElement): string { - if (element.tagName.toLowerCase() === "img") { - return (element as HTMLImageElement).alt; - } - if (element.tagName.toLowerCase() === "br") { - return "\n"; - } - if (element.hasAttribute("real")) { - return element.getAttribute("real") as string; - } - let build = ""; - for (const thing of Array.from(element.childNodes)) { - if (thing instanceof Text) { - const text = thing.textContent; - build += text; - continue; - } - const text = this.gatherBoxText(thing as HTMLElement); - if (text) { - build += text; - } - } - return build; + static gatherBoxText(element: HTMLElement): string{ + if(element.tagName.toLowerCase() === "img"){ + return(element as HTMLImageElement).alt; + } + if(element.tagName.toLowerCase() === "br"){ + return"\n"; + } + if(element.hasAttribute("real")){ + return element.getAttribute("real") as string; + } + let build = ""; + for(const thing of Array.from(element.childNodes)){ + if(thing instanceof Text){ + const text = thing.textContent; + build += text; + continue; + } + const text = this.gatherBoxText(thing as HTMLElement); + if(text){ + build += text; + } + } + return build; } static readonly trustedDomains = new Set([location.host]); - static safeLink(elm: HTMLElement, url: string) { - if (URL.canParse(url)) { - const Url = new URL(url); - if ( - elm instanceof HTMLAnchorElement && + static safeLink(elm: HTMLElement, url: string){ + if(URL.canParse(url)){ + const Url = new URL(url); + if( + elm instanceof HTMLAnchorElement && this.trustedDomains.has(Url.host) - ) { - elm.href = url; - elm.target = "_blank"; - return; - } - elm.onmouseup = (_) => { - if (_.button === 2) return; - console.log(":3"); - function open() { - const proxy = window.open(url, "_blank"); - if (proxy && _.button === 1) { - proxy.focus(); - } else if (proxy) { - window.focus(); - } - } - if (this.trustedDomains.has(Url.host)) { - open(); - } else { - const full: Dialog = new Dialog([ - "vdiv", - ["title", "You're leaving spacebar"], - [ - "text", - "You're going to " + + ){ + elm.href = url; + elm.target = "_blank"; + return; + } + elm.onmouseup = _=>{ + if(_.button === 2)return; + console.log(":3"); + function open(){ + const proxy = window.open(url, "_blank"); + if(proxy && _.button === 1){ + proxy.focus(); + }else if(proxy){ + window.focus(); + } + } + if(this.trustedDomains.has(Url.host)){ + open(); + }else{ + const full: Dialog = new Dialog([ + "vdiv", + ["title", "You're leaving spacebar"], + [ + "text", + "You're going to " + Url.host + " are you sure you want to go there?", - ], - [ - "hdiv", - ["button", "", "Nevermind", (_: any) => full.hide()], - [ - "button", - "", - "Go there", - (_: any) => { - open(); - full.hide(); - }, - ], - [ - "button", - "", - "Go there and trust in the future", - (_: any) => { - open(); - full.hide(); - this.trustedDomains.add(Url.host); - }, - ], - ], - ]); - full.show(); - } - }; - } else { - throw Error(url + " is not a valid URL"); - } + ], + [ + "hdiv", + ["button", "", "Nevermind", (_: any)=>full.hide()], + [ + "button", + "", + "Go there", + (_: any)=>{ + open(); + full.hide(); + }, + ], + [ + "button", + "", + "Go there and trust in the future", + (_: any)=>{ + open(); + full.hide(); + this.trustedDomains.add(Url.host); + }, + ], + ], + ]); + full.show(); + } + }; + }else{ + throw new Error(url + " is not a valid URL"); + } } /* static replace(base: HTMLElement, newelm: HTMLElement) { @@ -823,48 +823,48 @@ const parts = build } } */ - } +} - //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div - let text = ""; - function saveCaretPosition(context: Node) { +//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div +let text = ""; +function saveCaretPosition(context: Node){ const selection = window.getSelection(); - if (!selection) return; + if(!selection)return; const range = selection.getRangeAt(0); range.setStart(context, 0); text = selection.toString(); let len = text.length + 1; - for (const str in text.split("\n")) { - if (str.length !== 0) { - len--; + for(const str in text.split("\n")){ + if(str.length !== 0){ + len--; + } } - } - len += +(text[text.length - 1] === "\n"); + len += Number(text.at(-1) === "\n"); - return function restore() { - if (!selection) return; - const pos = getTextNodeAtPosition(context, len); - selection.removeAllRanges(); - const range = new Range(); - range.setStart(pos.node, pos.position); - selection.addRange(range); + return function restore(){ + if(!selection)return; + const pos = getTextNodeAtPosition(context, len); + selection.removeAllRanges(); + const range = new Range(); + range.setStart(pos.node, pos.position); + selection.addRange(range); }; - } +} - function getTextNodeAtPosition(root: Node, index: number) { +function getTextNodeAtPosition(root: Node, index: number){ const NODE_TYPE = NodeFilter.SHOW_TEXT; - const treeWalker = document.createTreeWalker(root, NODE_TYPE, (elem) => { - if (!elem.textContent) return 0; - if (index > elem.textContent.length) { - index -= elem.textContent.length; - return NodeFilter.FILTER_REJECT; - } - return NodeFilter.FILTER_ACCEPT; + const treeWalker = document.createTreeWalker(root, NODE_TYPE, elem=>{ + if(!elem.textContent)return 0; + if(index > elem.textContent.length){ + index -= elem.textContent.length; + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_ACCEPT; }); const c = treeWalker.nextNode(); - return { - node: c ? c : root, - position: index, + return{ + node: c ? c : root, + position: index, }; - } - export { MarkDown }; +} +export{ MarkDown }; diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 47faec4..a67f5dd 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -1,256 +1,256 @@ -import { User } from "./user.js"; -import { Role } from "./role.js"; -import { Guild } from "./guild.js"; -import { SnowFlake } from "./snowflake.js"; -import { memberjson, presencejson } from "./jsontypes.js"; -import { Dialog } from "./dialog.js"; +import{ User }from"./user.js"; +import{ Role }from"./role.js"; +import{ Guild }from"./guild.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ memberjson, presencejson }from"./jsontypes.js"; +import{ Dialog }from"./dialog.js"; -class Member extends SnowFlake { -static already = {}; -owner: Guild; -user: User; -roles: Role[] = []; -nick!: string; +class Member extends SnowFlake{ + static already = {}; + owner: Guild; + user: User; + roles: Role[] = []; + nick!: string; [key: string]: any; -private constructor(memberjson: memberjson, owner: Guild) { -super(memberjson.id); -this.owner = owner; -if (this.localuser.userMap.has(memberjson.id)) { -this.user = this.localuser.userMap.get(memberjson.id) as User; -} else if (memberjson.user) { -this.user = new User(memberjson.user, owner.localuser); -} else { -throw new Error("Missing user object of this member"); -} +private constructor(memberjson: memberjson, owner: Guild){ + super(memberjson.id); + this.owner = owner; + if(this.localuser.userMap.has(memberjson.id)){ + this.user = this.localuser.userMap.get(memberjson.id) as User; + }else if(memberjson.user){ + this.user = new User(memberjson.user, owner.localuser); + }else{ + throw new Error("Missing user object of this member"); + } -for (const key of Object.keys(memberjson)) { -if (key === "guild" || key === "owner") { -continue; -} + for(const key of Object.keys(memberjson)){ + if(key === "guild" || key === "owner"){ + continue; + } -if (key === "roles") { -for (const strrole of memberjson.roles) { -const role = this.guild.roleids.get(strrole); -if (!role) continue; -this.roles.push(role); + if(key === "roles"){ + for(const strrole of memberjson.roles){ + const role = this.guild.roleids.get(strrole); + if(!role)continue; + this.roles.push(role); + } + continue; + } + (this as any)[key] = (memberjson as any)[key]; + } + if(this.localuser.userMap.has(this?.id)){ + this.user = this.localuser.userMap.get(this?.id) as User; + } + this.roles.sort((a, b)=>{ + return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); + }); } -continue; +get guild(){ + return this.owner; } -(this as any)[key] = (memberjson as any)[key]; +get localuser(){ + return this.guild.localuser; } -if (this.localuser.userMap.has(this?.id)) { -this.user = this.localuser.userMap.get(this?.id) as User; -} -this.roles.sort((a, b) => { -return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); -}); -} -get guild() { -return this.owner; -} -get localuser() { -return this.guild.localuser; -} -get info() { -return this.owner.info; +get info(){ + return this.owner.info; } static async new( -memberjson: memberjson, -owner: Guild -): Promise { + memberjson: memberjson, + owner: Guild +): Promise{ let user: User; - if (owner.localuser.userMap.has(memberjson.id)) { - user = owner.localuser.userMap.get(memberjson.id) as User; - } else if (memberjson.user) { - user = new User(memberjson.user, owner.localuser); - } else { - throw new Error("missing user object of this member"); + if(owner.localuser.userMap.has(memberjson.id)){ + user = owner.localuser.userMap.get(memberjson.id) as User; + }else if(memberjson.user){ + user = new User(memberjson.user, owner.localuser); + }else{ + throw new Error("missing user object of this member"); } - if (user.members.has(owner)) { - let memb = user.members.get(owner); - if (memb === undefined) { - memb = new Member(memberjson, owner); - user.members.set(owner, memb); - return memb; - } else if (memb instanceof Promise) { - return await memb; //I should do something else, though for now this is "good enough" - } else { - return memb; + if(user.members.has(owner)){ + let memb = user.members.get(owner); + if(memb === undefined){ + memb = new Member(memberjson, owner); + user.members.set(owner, memb); + return memb; + }else if(memb instanceof Promise){ + return await memb; //I should do something else, though for now this is "good enough" + }else{ + return memb; + } + }else{ + const memb = new Member(memberjson, owner); + user.members.set(owner, memb); + return memb; } - } else { - const memb = new Member(memberjson, owner); - user.members.set(owner, memb); - return memb; - } - } - static async resolveMember( +} +static async resolveMember( user: User, guild: Guild - ): Promise { - const maybe = user.members.get(guild); - if (!user.members.has(guild)) { +): Promise{ + const maybe = user.members.get(guild); + if(!user.members.has(guild)){ const membpromise = guild.localuser.resolvemember(user.id, guild.id); - const promise = new Promise(async (res) => { + const promise = new Promise(async res=>{ const membjson = await membpromise; - if (membjson === undefined) { - return res(undefined); - } else { - const member = new Member(membjson, guild); - const map = guild.localuser.presences; - member.getPresence(map.get(member.id)); - map.delete(member.id); - res(member); - return member; + if(membjson === undefined){ + return res(); + }else{ + const member = new Member(membjson, guild); + const map = guild.localuser.presences; + member.getPresence(map.get(member.id)); + map.delete(member.id); + res(member); + return member; } - }); - user.members.set(guild, promise); - } - if (maybe instanceof Promise) { - return await maybe; - } else { - return maybe; - } - } - public getPresence(presence: presencejson | undefined) { - this.user.getPresence(presence); - } - /** + }); + user.members.set(guild, promise); + } + if(maybe instanceof Promise){ + return await maybe; + }else{ + return maybe; + } +} +public getPresence(presence: presencejson | undefined){ + this.user.getPresence(presence); +} +/** * @todo */ - highInfo() { - fetch( - this.info.api + +highInfo(){ + fetch( + this.info.api + "/users/" + this.id + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + this.guild.id, - { headers: this.guild.headers } - ); - } - hasRole(ID: string) { - console.log(this.roles, ID); - for (const thing of this.roles) { - if (thing.id === ID) { + { headers: this.guild.headers } + ); +} +hasRole(ID: string){ + console.log(this.roles, ID); + for(const thing of this.roles){ + if(thing.id === ID){ return true; - } - } - return false; - } - getColor() { - for (const thing of this.roles) { - const color = thing.getColor(); - if (color) { + } + } + return false; +} +getColor(){ + for(const thing of this.roles){ + const color = thing.getColor(); + if(color){ return color; - } - } - return ""; - } - isAdmin() { - for (const role of this.roles) { - if (role.permissions.getPermission("ADMINISTRATOR")) { + } + } + return""; +} +isAdmin(){ + for(const role of this.roles){ + if(role.permissions.getPermission("ADMINISTRATOR")){ return true; - } - } - return this.guild.properties.owner_id === this.user.id; - } - bind(html: HTMLElement) { - if (html.tagName === "SPAN") { - if (!this) { + } + } + return this.guild.properties.owner_id === this.user.id; +} +bind(html: HTMLElement){ + if(html.tagName === "SPAN"){ + if(!this){ return; - } - /* + } + /* if(this.error){ } */ - html.style.color = this.getColor(); - } + html.style.color = this.getColor(); + } - //this.profileclick(html); - } - profileclick(/* html: HTMLElement */) { - //to be implemented - } - get name() { - return this.nick || this.user.username; - } - kick() { - let reason = ""; - const menu = new Dialog([ - "vdiv", - ["title", "Kick " + this.name + " from " + this.guild.properties.name], - [ + //this.profileclick(html); +} +profileclick(/* html: HTMLElement */){ + //to be implemented +} +get name(){ + return this.nick || this.user.username; +} +kick(){ + let reason = ""; + const menu = new Dialog([ + "vdiv", + ["title", "Kick " + this.name + " from " + this.guild.properties.name], + [ "textbox", "Reason:", "", - function (e: Event) { - reason = (e.target as HTMLInputElement).value; + function(e: Event){ + reason = (e.target as HTMLInputElement).value; }, - ], - [ + ], + [ "button", "", "submit", - () => { - this.kickAPI(reason); - menu.hide(); + ()=>{ + this.kickAPI(reason); + menu.hide(); }, - ], - ]); - menu.show(); - } - kickAPI(reason: string) { - const headers = structuredClone(this.guild.headers); - (headers as any)["x-audit-log-reason"] = reason; - fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, { - method: "DELETE", - headers, - }); - } - ban() { - let reason = ""; - const menu = new Dialog([ - "vdiv", - ["title", "Ban " + this.name + " from " + this.guild.properties.name], - [ + ], + ]); + menu.show(); +} +kickAPI(reason: string){ + const headers = structuredClone(this.guild.headers); + (headers as any)["x-audit-log-reason"] = reason; + fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, { + method: "DELETE", + headers, + }); +} +ban(){ + let reason = ""; + const menu = new Dialog([ + "vdiv", + ["title", "Ban " + this.name + " from " + this.guild.properties.name], + [ "textbox", "Reason:", "", - function (e: Event) { - reason = (e.target as HTMLInputElement).value; + function(e: Event){ + reason = (e.target as HTMLInputElement).value; }, - ], - [ + ], + [ "button", "", "submit", - () => { - this.banAPI(reason); - menu.hide(); + ()=>{ + this.banAPI(reason); + menu.hide(); }, - ], - ]); - menu.show(); - } - banAPI(reason: string) { - const headers = structuredClone(this.guild.headers); - (headers as any)["x-audit-log-reason"] = reason; - fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, { - method: "PUT", - headers, - }); - } - hasPermission(name: string): boolean { - if (this.isAdmin()) { + ], + ]); + menu.show(); +} +banAPI(reason: string){ + const headers = structuredClone(this.guild.headers); + (headers as any)["x-audit-log-reason"] = reason; + fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, { + method: "PUT", + headers, + }); +} +hasPermission(name: string): boolean{ + if(this.isAdmin()){ + return true; + } + for(const thing of this.roles){ + if(thing.permissions.getPermission(name)){ return true; - } - for (const thing of this.roles) { - if (thing.permissions.getPermission(name)) { - return true; - } - } - return false; - } - } - export { Member }; + } + } + return false; +} +} +export{ Member }; diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 34e1e3e..bd573f6 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -1,19 +1,19 @@ -import { Contextmenu } from "./contextmenu.js"; -import { User } from "./user.js"; -import { Member } from "./member.js"; -import { MarkDown } from "./markdown.js"; -import { Embed } from "./embed.js"; -import { Channel } from "./channel.js"; -import { Localuser } from "./localuser.js"; -import { Role } from "./role.js"; -import { File } from "./file.js"; -import { SnowFlake } from "./snowflake.js"; -import { memberjson, messagejson } from "./jsontypes.js"; -import { Emoji } from "./emoji.js"; -import { Dialog } from "./dialog.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ User }from"./user.js"; +import{ Member }from"./member.js"; +import{ MarkDown }from"./markdown.js"; +import{ Embed }from"./embed.js"; +import{ Channel }from"./channel.js"; +import{ Localuser }from"./localuser.js"; +import{ Role }from"./role.js"; +import{ File }from"./file.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ memberjson, messagejson }from"./jsontypes.js"; +import{ Emoji }from"./emoji.js"; +import{ Dialog }from"./dialog.js"; -class Message extends SnowFlake { -static contextmenu = new Contextmenu("message menu"); +class Message extends SnowFlake{ + static contextmenu = new Contextmenu("message menu"); owner: Channel; headers: Localuser["headers"]; embeds!: Embed[]; @@ -26,8 +26,8 @@ static contextmenu = new Contextmenu("message menu"); timestamp!: number; content!: MarkDown; static del: Promise; - static resolve: Function; - /* + static resolve: Function; + /* weakdiv:WeakRef; set div(e:HTMLDivElement){ if(!e){ @@ -40,387 +40,387 @@ static contextmenu = new Contextmenu("message menu"); return this.weakdiv?.deref(); } //*/ - div: + div: | (HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement }) | undefined; - member: Member | undefined; - reactions!: messagejson["reactions"]; - static setup() { - this.del = new Promise((_) => { + member: Member | undefined; + reactions!: messagejson["reactions"]; + static setup(){ + this.del = new Promise(_=>{ this.resolve = _; - }); - Message.setupcmenu(); - } - static setupcmenu() { - Message.contextmenu.addbutton("Copy raw text", function (this: Message) { + }); + Message.setupcmenu(); + } + static setupcmenu(){ + Message.contextmenu.addbutton("Copy raw text", function(this: Message){ navigator.clipboard.writeText(this.content.rawString); - }); - Message.contextmenu.addbutton("Reply", function (this: Message) { + }); + Message.contextmenu.addbutton("Reply", function(this: Message){ this.channel.setReplying(this); - }); - Message.contextmenu.addbutton("Copy message id", function (this: Message) { + }); + Message.contextmenu.addbutton("Copy message id", function(this: Message){ navigator.clipboard.writeText(this.id); - }); - Message.contextmenu.addsubmenu( + }); + Message.contextmenu.addsubmenu( "Add reaction", - function (this: Message, _, e: MouseEvent) { - Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => { - this.reactionToggle(_); - }); + function(this: Message, _, e: MouseEvent){ + Emoji.emojiPicker(e.x, e.y, this.localuser).then(_=>{ + this.reactionToggle(_); + }); } - ); - Message.contextmenu.addbutton( + ); + Message.contextmenu.addbutton( "Edit", - function (this: Message) { - this.setEdit(); + function(this: Message){ + this.setEdit(); }, null, - function () { - return this.author.id === this.localuser.user.id; + function(){ + return this.author.id === this.localuser.user.id; } - ); - Message.contextmenu.addbutton( + ); + Message.contextmenu.addbutton( "Delete message", - function (this: Message) { - this.delete(); + function(this: Message){ + this.delete(); }, null, - function () { - return this.canDelete(); + function(){ + return this.canDelete(); } - ); - } - setEdit() { - this.channel.editing = this; - const markdown = ( + ); + } + setEdit(){ + this.channel.editing = this; + const markdown = ( document.getElementById("typebox") as HTMLDivElement & { markdown: MarkDown; } - )["markdown"] as MarkDown; - markdown.txt = this.content.rawString.split(""); - markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); + ).markdown as MarkDown; + markdown.txt = this.content.rawString.split(""); + markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); + } + constructor(messagejson: messagejson, owner: Channel){ + super(messagejson.id); + this.owner = owner; + this.headers = this.owner.headers; + this.giveData(messagejson); + this.owner.messages.set(this.id, this); + } + reactionToggle(emoji: string | Emoji){ + let remove = false; + for(const thing of this.reactions){ + if(thing.emoji.name === emoji){ + remove = thing.me; + break; } - constructor(messagejson: messagejson, owner: Channel) { - super(messagejson.id); - this.owner = owner; - this.headers = this.owner.headers; - this.giveData(messagejson); - this.owner.messages.set(this.id, this); - } - reactionToggle(emoji: string | Emoji) { - let remove = false; - for (const thing of this.reactions) { - if (thing.emoji.name === emoji) { - remove = thing.me; - break; - } - } - let reactiontxt: string; - if (emoji instanceof Emoji) { + } + let reactiontxt: string; + if(emoji instanceof Emoji){ reactiontxt = `${emoji.name}:${emoji.id}`; - } else { + }else{ reactiontxt = encodeURIComponent(emoji); - } - fetch( + } + fetch( `${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${reactiontxt}/@me`, { - method: remove ? "DELETE" : "PUT", - headers: this.headers, + method: remove ? "DELETE" : "PUT", + headers: this.headers, } - ); - } - giveData(messagejson: messagejson) { - const func = this.channel.infinite.snapBottom(); - for (const thing of Object.keys(messagejson)) { - if (thing === "attachments") { - this.attachments = []; - for (const thing of messagejson.attachments) { - this.attachments.push(new File(thing, this)); - } - continue; - } else if (thing === "content") { - this.content = new MarkDown(messagejson[thing], this.channel); - continue; - } else if (thing === "id") { - continue; - } else if (thing === "member") { - Member.new(messagejson.member as memberjson, this.guild).then((_) => { - this.member = _ as Member; - }); - continue; - } else if (thing === "embeds") { - this.embeds = []; - for (const thing in messagejson.embeds) { - this.embeds[thing] = new Embed(messagejson.embeds[thing], this); - } - continue; + ); + } + giveData(messagejson: messagejson){ + const func = this.channel.infinite.snapBottom(); + for(const thing of Object.keys(messagejson)){ + if(thing === "attachments"){ + this.attachments = []; + for(const thing of messagejson.attachments){ + this.attachments.push(new File(thing, this)); + } + continue; + }else if(thing === "content"){ + this.content = new MarkDown(messagejson[thing], this.channel); + continue; + }else if(thing === "id"){ + continue; + }else if(thing === "member"){ + Member.new(messagejson.member as memberjson, this.guild).then(_=>{ + this.member = _ as Member; + }); + continue; + }else if(thing === "embeds"){ + this.embeds = []; + for(const thing in messagejson.embeds){ + this.embeds[thing] = new Embed(messagejson.embeds[thing], this); + } + continue; } (this as any)[thing] = (messagejson as any)[thing]; - } - if (messagejson.reactions?.length) { + } + if(messagejson.reactions?.length){ console.log(messagejson.reactions, ":3"); - } + } - this.author = new User(messagejson.author, this.localuser); - for (const thing in messagejson.mentions) { + this.author = new User(messagejson.author, this.localuser); + for(const thing in messagejson.mentions){ this.mentions[thing] = new User( - messagejson.mentions[thing], - this.localuser + messagejson.mentions[thing], + this.localuser ); - } - if (!this.member && this.guild.id !== "@me") { - this.author.resolvemember(this.guild).then((_) => { - this.member = _; + } + if(!this.member && this.guild.id !== "@me"){ + this.author.resolvemember(this.guild).then(_=>{ + this.member = _; }); - } - if (this.mentions.length || this.mention_roles.length) { + } + if(this.mentions.length || this.mention_roles.length){ //currently mention_roles isn't implemented on the spacebar servers console.log(this.mentions, this.mention_roles); - } - if (this.mentionsuser(this.localuser.user)) { + } + if(this.mentionsuser(this.localuser.user)){ console.log(this); - } - if (this.div) { + } + if(this.div){ this.generateMessage(); - } - func(); - } - canDelete() { - return ( + } + func(); + } + canDelete(){ + return( this.channel.hasPermission("MANAGE_MESSAGES") || this.author === this.localuser.user - ); - } - get channel() { - return this.owner; - } - get guild() { - return this.owner.guild; - } - get localuser() { - return this.owner.localuser; - } - get info() { - return this.owner.info; - } - messageevents(obj: HTMLDivElement) { - // const func = Message.contextmenu.bindContextmenu(obj, this, undefined); - this.div = obj; - obj.classList.add("messagediv"); - } - deleteDiv() { - if (!this.div) return; - try { + ); + } + get channel(){ + return this.owner; + } + get guild(){ + return this.owner.guild; + } + get localuser(){ + return this.owner.localuser; + } + get info(){ + return this.owner.info; + } + messageevents(obj: HTMLDivElement){ + // const func = Message.contextmenu.bindContextmenu(obj, this, undefined); + this.div = obj; + obj.classList.add("messagediv"); + } + deleteDiv(){ + if(!this.div)return; + try{ this.div.remove(); this.div = undefined; - } catch (e) { + }catch(e){ console.error(e); - } - } - mentionsuser(userd: User | Member) { - if (userd instanceof User) { + } + } + mentionsuser(userd: User | Member){ + if(userd instanceof User){ return this.mentions.includes(userd); - } else if (userd instanceof Member) { + }else if(userd instanceof Member){ return this.mentions.includes(userd.user); - } else { - return; + }else{ + + } + } + getimages(){ + const build: File[] = []; + for(const thing of this.attachments){ + if(thing.content_type.startsWith("image/")){ + build.push(thing); } - } - getimages() { - const build: File[] = []; - for (const thing of this.attachments) { - if (thing.content_type.startsWith("image/")) { - build.push(thing); - } - } - return build; - } - async edit(content: string) { - return await fetch( + } + return build; + } + async edit(content: string){ + return await fetch( this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ content }), + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ content }), } - ); - } - delete() { - fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, { + ); + } + delete(){ + fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, { headers: this.headers, method: "DELETE", - }); - } - deleteEvent() { - console.log("deleted"); - if (this.div) { + }); + } + deleteEvent(){ + console.log("deleted"); + if(this.div){ this.div.remove(); this.div.innerHTML = ""; this.div = undefined; - } - const prev = this.channel.idToPrev.get(this.id); - const next = this.channel.idToNext.get(this.id); - this.channel.idToPrev.delete(this.id); - this.channel.idToNext.delete(this.id); - this.channel.messages.delete(this.id); - if (prev && next) { + } + const prev = this.channel.idToPrev.get(this.id); + const next = this.channel.idToNext.get(this.id); + this.channel.idToPrev.delete(this.id); + this.channel.idToNext.delete(this.id); + this.channel.messages.delete(this.id); + if(prev && next){ this.channel.idToPrev.set(next, prev); this.channel.idToNext.set(prev, next); - } else if (prev) { + }else if(prev){ this.channel.idToNext.delete(prev); - } else if (next) { + }else if(next){ this.channel.idToPrev.delete(next); - } - if (prev) { + } + if(prev){ const prevmessage = this.channel.messages.get(prev); - if (prevmessage) { - prevmessage.generateMessage(); + if(prevmessage){ + prevmessage.generateMessage(); } - } - if ( + } + if( this.channel.lastmessage === this || this.channel.lastmessageid === this.id - ) { - if (prev) { - this.channel.lastmessage = this.channel.messages.get(prev); - this.channel.lastmessageid = prev; - } else { - this.channel.lastmessage = undefined; - this.channel.lastmessageid = undefined; + ){ + if(prev){ + this.channel.lastmessage = this.channel.messages.get(prev); + this.channel.lastmessageid = prev; + }else{ + this.channel.lastmessage = undefined; + this.channel.lastmessageid = undefined; } + } + if(this.channel.lastreadmessageid === this.id){ + if(prev){ + this.channel.lastreadmessageid = prev; + }else{ + this.channel.lastreadmessageid = undefined; } - if (this.channel.lastreadmessageid === this.id) { - if (prev) { - this.channel.lastreadmessageid = prev; - } else { - this.channel.lastreadmessageid = undefined; - } - } - console.log("deleted done"); - } - reactdiv!: WeakRef; - blockedPropigate() { - const previd = this.channel.idToPrev.get(this.id); - if (!previd) { - this.generateMessage(); - return; - } - const premessage = this.channel.messages.get(previd); - if (premessage?.author === this.author) { - premessage.blockedPropigate(); - } else { - this.generateMessage(); - } - } - generateMessage(premessage?: Message | undefined, ignoredblock = false) { - if (!this.div) return; - if (!premessage) { - premessage = this.channel.messages.get( + } + console.log("deleted done"); + } + reactdiv!: WeakRef; + blockedPropigate(){ + const previd = this.channel.idToPrev.get(this.id); + if(!previd){ + this.generateMessage(); + return; + } + const premessage = this.channel.messages.get(previd); + if(premessage?.author === this.author){ + premessage.blockedPropigate(); + }else{ + this.generateMessage(); + } + } + generateMessage(premessage?: Message | undefined, ignoredblock = false){ + if(!this.div)return; + if(!premessage){ + premessage = this.channel.messages.get( this.channel.idToPrev.get(this.id) as string - ); - } - const div = this.div; - for (const user of this.mentions) { - if (user === this.localuser.user) { + ); + } + const div = this.div; + for(const user of this.mentions){ + if(user === this.localuser.user){ div.classList.add("mentioned"); - } - } - if (this === this.channel.replyingto) { - div.classList.add("replying"); - } - div.innerHTML = ""; - const build = document.createElement("div"); + } + } + if(this === this.channel.replyingto){ + div.classList.add("replying"); + } + div.innerHTML = ""; + const build = document.createElement("div"); - build.classList.add("flexltr", "message"); - div.classList.remove("zeroheight"); - if (this.author.relationshipType === 2) { - if (ignoredblock) { - if (premessage?.author !== this.author) { - const span = document.createElement("span"); - span.textContent = + build.classList.add("flexltr", "message"); + div.classList.remove("zeroheight"); + if(this.author.relationshipType === 2){ + if(ignoredblock){ + if(premessage?.author !== this.author){ + const span = document.createElement("span"); + span.textContent = "You have this user blocked, click to hide these messages."; - div.append(span); - span.classList.add("blocked"); - span.onclick = (_) => { - const scroll = this.channel.infinite.scrollTop; - let next: Message | undefined = this; - while (next?.author === this.author) { - next.generateMessage(); - next = this.channel.messages.get( + div.append(span); + span.classList.add("blocked"); + span.onclick = _=>{ + const scroll = this.channel.infinite.scrollTop; + let next: Message | undefined = this; + while(next?.author === this.author){ + next.generateMessage(); + next = this.channel.messages.get( this.channel.idToNext.get(next.id) as string - ); + ); + } + if(this.channel.infinite.scollDiv && scroll){ + this.channel.infinite.scollDiv.scrollTop = scroll; + } + }; } - if (this.channel.infinite.scollDiv && scroll) { - this.channel.infinite.scollDiv.scrollTop = scroll; - } - }; - } - } else { + }else{ div.classList.remove("topMessage"); - if (premessage?.author === this.author) { - div.classList.add("zeroheight"); - premessage.blockedPropigate(); - div.appendChild(build); - return div; - } else { - build.classList.add("blocked", "topMessage"); - const span = document.createElement("span"); - let count = 1; - let next = this.channel.messages.get( + if(premessage?.author === this.author){ + div.classList.add("zeroheight"); + premessage.blockedPropigate(); + div.appendChild(build); + return div; + }else{ + build.classList.add("blocked", "topMessage"); + const span = document.createElement("span"); + let count = 1; + let next = this.channel.messages.get( this.channel.idToNext.get(this.id) as string - ); - while (next?.author === this.author) { - count++; - next = this.channel.messages.get( + ); + while(next?.author === this.author){ + count++; + next = this.channel.messages.get( this.channel.idToNext.get(next.id) as string - ); - } - span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`; - build.append(span); - span.onclick = (_) => { - const scroll = this.channel.infinite.scrollTop; - const func = this.channel.infinite.snapBottom(); - let next: Message | undefined = this; - while (next?.author === this.author) { - next.generateMessage(undefined, true); - next = this.channel.messages.get( + ); + } + span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`; + build.append(span); + span.onclick = _=>{ + const scroll = this.channel.infinite.scrollTop; + const func = this.channel.infinite.snapBottom(); + let next: Message | undefined = this; + while(next?.author === this.author){ + next.generateMessage(undefined, true); + next = this.channel.messages.get( this.channel.idToNext.get(next.id) as string - ); - console.log("loopy"); + ); + console.log("loopy"); + } + if(this.channel.infinite.scollDiv && scroll){ + func(); + this.channel.infinite.scollDiv.scrollTop = scroll; + } + }; + div.appendChild(build); + return div; } - if (this.channel.infinite.scollDiv && scroll) { - func(); - this.channel.infinite.scollDiv.scrollTop = scroll; - } - }; - div.appendChild(build); - return div; - } - } - } - if (this.message_reference) { - const replyline = document.createElement("div"); - const line = document.createElement("hr"); - const minipfp = document.createElement("img"); - minipfp.classList.add("replypfp"); - replyline.appendChild(line); - replyline.appendChild(minipfp); - const username = document.createElement("span"); - replyline.appendChild(username); - const reply = document.createElement("div"); - username.classList.add("username"); - reply.classList.add("replytext"); - replyline.appendChild(reply); - const line2 = document.createElement("hr"); - replyline.appendChild(line2); - line2.classList.add("reply"); - line.classList.add("startreply"); - replyline.classList.add("replyflex"); - // TODO: Fix this - this.channel.getmessage(this.message_reference.id).then((message) => { - if (message.author.relationshipType === 2) { - username.textContent = "Blocked user"; - return; + } + } + if(this.message_reference){ + const replyline = document.createElement("div"); + const line = document.createElement("hr"); + const minipfp = document.createElement("img"); + minipfp.classList.add("replypfp"); + replyline.appendChild(line); + replyline.appendChild(minipfp); + const username = document.createElement("span"); + replyline.appendChild(username); + const reply = document.createElement("div"); + username.classList.add("username"); + reply.classList.add("replytext"); + replyline.appendChild(reply); + const line2 = document.createElement("hr"); + replyline.appendChild(line2); + line2.classList.add("reply"); + line.classList.add("startreply"); + replyline.classList.add("replyflex"); + // TODO: Fix this + this.channel.getmessage(this.message_reference.id).then(message=>{ + if(message.author.relationshipType === 2){ + username.textContent = "Blocked user"; + return; } const author = message.author; reply.appendChild(message.content.makeHTML({ stdsize: true })); @@ -428,19 +428,19 @@ static contextmenu = new Contextmenu("message menu"); author.bind(minipfp, this.guild); username.textContent = author.username; author.bind(username, this.guild); - }); - reply.onclick = (_) => { + }); + reply.onclick = _=>{ // TODO: FIX this this.channel.infinite.focus(this.message_reference.id); - }; - div.appendChild(replyline); - } - div.appendChild(build); - if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { - const pfpRow = document.createElement("div"); - pfpRow.classList.add("flexltr"); - let pfpparent, current; - if (premessage != null) { + }; + div.appendChild(replyline); + } + div.appendChild(build); + if({ 0: true, 19: true }[this.type] || this.attachments.length !== 0){ + const pfpRow = document.createElement("div"); + pfpRow.classList.add("flexltr"); + let pfpparent, current; + if(premessage != null){ pfpparent ??= premessage; // @ts-ignore // TODO: type this @@ -449,24 +449,24 @@ static contextmenu = new Contextmenu("message menu"); const old = new Date(pfpparent2.timestamp).getTime() / 1000; const newt = new Date(this.timestamp).getTime() / 1000; current = newt - old > 600; - } - const combine = + } + const combine = premessage?.author != this.author || current || this.message_reference; - if (combine) { + if(combine){ const pfp = this.author.buildpfp(); this.author.bind(pfp, this.guild, false); pfpRow.appendChild(pfp); - } else { - div["pfpparent"] = pfpparent; - } - pfpRow.classList.add("pfprow"); - build.appendChild(pfpRow); - const text = document.createElement("div"); - text.classList.add("flexttb"); - const texttxt = document.createElement("div"); - texttxt.classList.add("commentrow", "flexttb"); - text.appendChild(texttxt); - if (combine) { + }else{ + div.pfpparent = pfpparent; + } + pfpRow.classList.add("pfprow"); + build.appendChild(pfpRow); + const text = document.createElement("div"); + text.classList.add("flexttb"); + const texttxt = document.createElement("div"); + texttxt.classList.add("commentrow", "flexttb"); + text.appendChild(texttxt); + if(combine){ const username = document.createElement("span"); username.classList.add("username"); this.author.bind(username, this.guild); @@ -475,11 +475,11 @@ static contextmenu = new Contextmenu("message menu"); const userwrap = document.createElement("div"); userwrap.classList.add("flexltr"); userwrap.appendChild(username); - if (this.author.bot) { - const username = document.createElement("span"); - username.classList.add("bot"); - username.textContent = "BOT"; - userwrap.appendChild(username); + if(this.author.bot){ + const username = document.createElement("span"); + username.classList.add("bot"); + username.textContent = "BOT"; + userwrap.appendChild(username); } const time = document.createElement("span"); time.textContent = " " + formatTime(new Date(this.timestamp)); @@ -487,283 +487,282 @@ static contextmenu = new Contextmenu("message menu"); userwrap.appendChild(time); texttxt.appendChild(userwrap); - } else { + }else{ div.classList.remove("topMessage"); - } - const messaged = this.content.makeHTML(); - (div as any)["txt"] = messaged; - const messagedwrap = document.createElement("div"); - messagedwrap.classList.add("flexttb"); - messagedwrap.appendChild(messaged); - texttxt.appendChild(messagedwrap); + } + const messaged = this.content.makeHTML(); + (div as any).txt = messaged; + const messagedwrap = document.createElement("div"); + messagedwrap.classList.add("flexttb"); + messagedwrap.appendChild(messaged); + texttxt.appendChild(messagedwrap); - build.appendChild(text); - if (this.attachments.length) { + build.appendChild(text); + if(this.attachments.length){ console.log(this.attachments); const attach = document.createElement("div"); attach.classList.add("flexltr"); - for (const thing of this.attachments) { - attach.appendChild(thing.getHTML()); + for(const thing of this.attachments){ + attach.appendChild(thing.getHTML()); } messagedwrap.appendChild(attach); - } - if (this.embeds.length) { + } + if(this.embeds.length){ const embeds = document.createElement("div"); embeds.classList.add("flexltr"); - for (const thing of this.embeds) { - embeds.appendChild(thing.generateHTML()); + for(const thing of this.embeds){ + embeds.appendChild(thing.generateHTML()); } messagedwrap.appendChild(embeds); - } - // - } else if (this.type === 7) { - const text = document.createElement("div"); - text.classList.add("flexttb"); - const texttxt = document.createElement("div"); - text.appendChild(texttxt); - build.appendChild(text); - texttxt.classList.add("flexltr"); - const messaged = document.createElement("span"); - div["txt"] = messaged; - messaged.textContent = "welcome: "; - texttxt.appendChild(messaged); + } + // + }else if(this.type === 7){ + const text = document.createElement("div"); + text.classList.add("flexttb"); + const texttxt = document.createElement("div"); + text.appendChild(texttxt); + build.appendChild(text); + texttxt.classList.add("flexltr"); + const messaged = document.createElement("span"); + div.txt = messaged; + messaged.textContent = "welcome: "; + texttxt.appendChild(messaged); - const username = document.createElement("span"); - username.textContent = this.author.username; - //this.author.profileclick(username); - this.author.bind(username, this.guild); - texttxt.appendChild(username); - username.classList.add("username"); + const username = document.createElement("span"); + username.textContent = this.author.username; + //this.author.profileclick(username); + this.author.bind(username, this.guild); + texttxt.appendChild(username); + username.classList.add("username"); - const time = document.createElement("span"); - time.textContent = " " + formatTime(new Date(this.timestamp)); - time.classList.add("timestamp"); - texttxt.append(time); - div.classList.add("topMessage"); + const time = document.createElement("span"); + time.textContent = " " + formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + texttxt.append(time); + div.classList.add("topMessage"); + } + const reactions = document.createElement("div"); + reactions.classList.add("flexltr", "reactiondiv"); + this.reactdiv = new WeakRef(reactions); + this.updateReactions(); + div.append(reactions); + this.bindButtonEvent(); + return div; + } + bindButtonEvent(){ + if(this.div){ + let buttons: HTMLDivElement | undefined; + this.div.onmouseenter = _=>{ + if(buttons){ + buttons.remove(); + buttons = undefined; } - const reactions = document.createElement("div"); - reactions.classList.add("flexltr", "reactiondiv"); - this.reactdiv = new WeakRef(reactions); - this.updateReactions(); - div.append(reactions); - this.bindButtonEvent(); - return div; + if(this.div){ + buttons = document.createElement("div"); + buttons.classList.add("messageButtons", "flexltr"); + if(this.channel.hasPermission("SEND_MESSAGES")){ + const container = document.createElement("div"); + const reply = document.createElement("span"); + reply.classList.add("svgtheme", "svg-reply", "svgicon"); + container.append(reply); + buttons.append(container); + container.onclick = _=>{ + this.channel.setReplying(this); + }; + } + if(this.author === this.localuser.user){ + const container = document.createElement("div"); + const edit = document.createElement("span"); + edit.classList.add("svgtheme", "svg-edit", "svgicon"); + container.append(edit); + buttons.append(container); + container.onclick = _=>{ + this.setEdit(); + }; + } + if(this.canDelete()){ + const container = document.createElement("div"); + const reply = document.createElement("span"); + reply.classList.add("svgtheme", "svg-delete", "svgicon"); + container.append(reply); + buttons.append(container); + container.onclick = _=>{ + if(_.shiftKey){ + this.delete(); + return; + } + const diaolog = new Dialog([ + "hdiv", + ["title", "are you sure you want to delete this?"], + [ + "button", + "", + "yes", + ()=>{ + this.delete(); + diaolog.hide(); + }, + ], + [ + "button", + "", + "no", + ()=>{ + diaolog.hide(); + }, + ], + ]); + diaolog.show(); + }; + } + if(buttons.childNodes.length !== 0){ + this.div.append(buttons); + } } - bindButtonEvent() { - if (this.div) { - let buttons: HTMLDivElement | undefined; - this.div.onmouseenter = (_) => { - if (buttons) { - buttons.remove(); - buttons = undefined; + }; + this.div.onmouseleave = _=>{ + if(buttons){ + buttons.remove(); + buttons = undefined; } - if (this.div) { - buttons = document.createElement("div"); - buttons.classList.add("messageButtons", "flexltr"); - if (this.channel.hasPermission("SEND_MESSAGES")) { - const container = document.createElement("div"); - const reply = document.createElement("span"); - reply.classList.add("svgtheme", "svg-reply", "svgicon"); - container.append(reply); - buttons.append(container); - container.onclick = (_) => { - this.channel.setReplying(this); - }; - } - if (this.author === this.localuser.user) { - const container = document.createElement("div"); - const edit = document.createElement("span"); - edit.classList.add("svgtheme", "svg-edit", "svgicon"); - container.append(edit); - buttons.append(container); - container.onclick = (_) => { - this.setEdit(); - }; - } - if (this.canDelete()) { - const container = document.createElement("div"); - const reply = document.createElement("span"); - reply.classList.add("svgtheme", "svg-delete", "svgicon"); - container.append(reply); - buttons.append(container); - container.onclick = (_) => { - if (_.shiftKey) { - this.delete(); - return; - } - const diaolog = new Dialog([ - "hdiv", - ["title", "are you sure you want to delete this?"], - [ - "button", - "", - "yes", - () => { - this.delete(); - diaolog.hide(); - }, - ], - [ - "button", - "", - "no", - () => { - diaolog.hide(); - }, - ], - ]); - diaolog.show(); - }; - } - if (buttons.childNodes.length !== 0) { - this.div.append(buttons); - } - } - }; - this.div.onmouseleave = (_) => { - if (buttons) { - buttons.remove(); - buttons = undefined; - } - }; - } - } - updateReactions() { - const reactdiv = this.reactdiv.deref(); - if (!reactdiv) return; - const func = this.channel.infinite.snapBottom(); - reactdiv.innerHTML = ""; - for (const thing of this.reactions) { - const reaction = document.createElement("div"); - reaction.classList.add("reaction"); - if (thing.me) { + }; + } + } + updateReactions(){ + const reactdiv = this.reactdiv.deref(); + if(!reactdiv)return; + const func = this.channel.infinite.snapBottom(); + reactdiv.innerHTML = ""; + for(const thing of this.reactions){ + const reaction = document.createElement("div"); + reaction.classList.add("reaction"); + if(thing.me){ reaction.classList.add("meReacted"); - } - let emoji: HTMLElement; - if (thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)) { - if (/\d{17,21}/.test(thing.emoji.name)) - thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug + } + let emoji: HTMLElement; + if(thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)){ + if(/\d{17,21}/.test(thing.emoji.name)) + thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug const emo = new Emoji( thing.emoji as { name: string; id: string; animated: boolean }, this.guild ); emoji = emo.getHTML(false); - } else { + }else{ emoji = document.createElement("p"); emoji.textContent = thing.emoji.name; - } - const count = document.createElement("p"); - count.textContent = "" + thing.count; - count.classList.add("reactionCount"); - reaction.append(count); - reaction.append(emoji); - reactdiv.append(reaction); + } + const count = document.createElement("p"); + count.textContent = "" + thing.count; + count.classList.add("reactionCount"); + reaction.append(count); + reaction.append(emoji); + reactdiv.append(reaction); - reaction.onclick = (_) => { + reaction.onclick = _=>{ this.reactionToggle(thing.emoji.name); - }; - } - func(); - } - reactionAdd(data: { name: string }, member: Member | { id: string }) { - for (const thing of this.reactions) { - if (thing.emoji.name === data.name) { + }; + } + func(); + } + reactionAdd(data: { name: string }, member: Member | { id: string }){ + for(const thing of this.reactions){ + if(thing.emoji.name === data.name){ thing.count++; - if (member.id === this.localuser.user.id) { - thing.me = true; - this.updateReactions(); - return; + if(member.id === this.localuser.user.id){ + thing.me = true; + this.updateReactions(); + return; } - } - } - this.reactions.push({ - count: 1, - emoji: data, - me: member.id === this.localuser.user.id, - }); - this.updateReactions(); - } - reactionRemove(data: { name: string }, id: string) { - console.log("test"); - for (const i in this.reactions) { - const thing = this.reactions[i]; - console.log(thing, data); - if (thing.emoji.name === data.name) { + } + } + this.reactions.push({ + count: 1, + emoji: data, + me: member.id === this.localuser.user.id, + }); + this.updateReactions(); + } + reactionRemove(data: { name: string }, id: string){ + console.log("test"); + for(const i in this.reactions){ + const thing = this.reactions[i]; + console.log(thing, data); + if(thing.emoji.name === data.name){ thing.count--; - if (thing.count === 0) { - this.reactions.splice(Number(i), 1); - this.updateReactions(); - return; + if(thing.count === 0){ + this.reactions.splice(Number(i), 1); + this.updateReactions(); + return; } - if (id === this.localuser.user.id) { - thing.me = false; - this.updateReactions(); - return; + if(id === this.localuser.user.id){ + thing.me = false; + this.updateReactions(); + return; } - } - } - } - reactionRemoveAll() { - this.reactions = []; - this.updateReactions(); - } - reactionRemoveEmoji(emoji: Emoji) { - for (const i in this.reactions) { - const reaction = this.reactions[i]; - if ( + } + } + } + reactionRemoveAll(){ + this.reactions = []; + this.updateReactions(); + } + reactionRemoveEmoji(emoji: Emoji){ + for(const i in this.reactions){ + const reaction = this.reactions[i]; + if( (reaction.emoji.id && reaction.emoji.id == emoji.id) || (!reaction.emoji.id && reaction.emoji.name == emoji.name) - ) { + ){ this.reactions.splice(Number(i), 1); this.updateReactions(); break; - } - } - } - buildhtml(premessage?: Message | undefined): HTMLElement { - if (this.div) { - console.error(`HTML for ${this.id} already exists, aborting`); - return this.div; - } - try { - const div = document.createElement("div"); - this.div = div; - this.messageevents(div); - return this.generateMessage(premessage) as HTMLElement; - } catch (e) { - console.error(e); - } - return this.div as HTMLElement; - } - } - let now: string; - let yesterdayStr: string; + } + } + } + buildhtml(premessage?: Message | undefined): HTMLElement{ + if(this.div){ + console.error(`HTML for ${this.id} already exists, aborting`); + return this.div; + } + try{ + const div = document.createElement("div"); + this.div = div; + this.messageevents(div); + return this.generateMessage(premessage) as HTMLElement; + }catch(e){ + console.error(e); + } + return this.div as HTMLElement; + } +} +let now: string; +let yesterdayStr: string; - function formatTime(date: Date) { - updateTimes(); - const datestring = date.toLocaleDateString(); - const formatTime = (date: Date) => - date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); +function formatTime(date: Date){ + updateTimes(); + const datestring = date.toLocaleDateString(); + const formatTime = (date: Date)=>date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); - if (datestring === now) { - return `Today at ${formatTime(date)}`; - } else if (datestring === yesterdayStr) { - return `Yesterday at ${formatTime(date)}`; - } else { - return `${date.toLocaleDateString()} at ${formatTime(date)}`; - } - } - let tomorrow = 0; - updateTimes(); - function updateTimes() { - if (tomorrow < Date.now()) { - const d = new Date(); - tomorrow = d.setHours(24, 0, 0, 0); - now = new Date().toLocaleDateString(); - const yesterday = new Date(now); - yesterday.setDate(new Date().getDate() - 1); - yesterdayStr = yesterday.toLocaleDateString(); - } - } - Message.setup(); - export { Message }; + if(datestring === now){ + return`Today at ${formatTime(date)}`; + }else if(datestring === yesterdayStr){ + return`Yesterday at ${formatTime(date)}`; + }else{ + return`${date.toLocaleDateString()} at ${formatTime(date)}`; + } +} +let tomorrow = 0; +updateTimes(); +function updateTimes(){ + if(tomorrow < Date.now()){ + const d = new Date(); + tomorrow = d.setHours(24, 0, 0, 0); + now = new Date().toLocaleDateString(); + const yesterday = new Date(now); + yesterday.setDate(new Date().getDate() - 1); + yesterdayStr = yesterday.toLocaleDateString(); + } +} +Message.setup(); +export{ Message }; diff --git a/src/webpage/permissions.ts b/src/webpage/permissions.ts index b6216d1..678b1ba 100644 --- a/src/webpage/permissions.ts +++ b/src/webpage/permissions.ts @@ -1,347 +1,347 @@ -class Permissions { -allow: bigint; -deny: bigint; -readonly hasDeny: boolean; -constructor(allow: string, deny: string = "") { -this.hasDeny = Boolean(deny); -try { -this.allow = BigInt(allow); -this.deny = BigInt(deny); -} catch { -this.allow = 0n; -this.deny = 0n; -console.error( -`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` -); -} -} -getPermissionbit(b: number, big: bigint): boolean { -return Boolean((big >> BigInt(b)) & 1n); -} -setPermissionbit(b: number, state: boolean, big: bigint): bigint { -const bit = 1n << BigInt(b); -return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 -} -static map: { +class Permissions{ + allow: bigint; + deny: bigint; + readonly hasDeny: boolean; + constructor(allow: string, deny: string = ""){ + this.hasDeny = Boolean(deny); + try{ + this.allow = BigInt(allow); + this.deny = BigInt(deny); + }catch{ + this.allow = 0n; + this.deny = 0n; + console.error( + `Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` + ); + } + } + getPermissionbit(b: number, big: bigint): boolean{ + return Boolean((big >> BigInt(b)) & 1n); + } + setPermissionbit(b: number, state: boolean, big: bigint): bigint{ + const bit = 1n << BigInt(b); + return(big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 + } + static map: { [key: number | string]: | { name: string; readableName: string; description: string } | number; }; -static info: { name: string; readableName: string; description: string }[]; -static makeMap() { -Permissions.info = [ -//for people in the future, do not reorder these, the creation of the map realize on the order -{ -name: "CREATE_INSTANT_INVITE", -readableName: "Create invite", -description: "Allows the user to create invites for the guild", -}, -{ -name: "KICK_MEMBERS", -readableName: "Kick members", -description: "Allows the user to kick members from the guild", -}, -{ -name: "BAN_MEMBERS", -readableName: "Ban members", -description: "Allows the user to ban members from the guild", -}, -{ -name: "ADMINISTRATOR", -readableName: "Administrator", -description: + static info: { name: string; readableName: string; description: string }[]; + static makeMap(){ + Permissions.info = [ + //for people in the future, do not reorder these, the creation of the map realize on the order + { + name: "CREATE_INSTANT_INVITE", + readableName: "Create invite", + description: "Allows the user to create invites for the guild", + }, + { + name: "KICK_MEMBERS", + readableName: "Kick members", + description: "Allows the user to kick members from the guild", + }, + { + name: "BAN_MEMBERS", + readableName: "Ban members", + description: "Allows the user to ban members from the guild", + }, + { + name: "ADMINISTRATOR", + readableName: "Administrator", + description: "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", -}, -{ -name: "MANAGE_CHANNELS", -readableName: "Manage channels", -description: "Allows the user to manage and edit channels", -}, -{ -name: "MANAGE_GUILD", -readableName: "Manage guild", -description: "Allows management and editing of the guild", -}, -{ -name: "ADD_REACTIONS", -readableName: "Add reactions", -description: "Allows user to add reactions to messages", -}, -{ -name: "VIEW_AUDIT_LOG", -readableName: "View audit log", -description: "Allows the user to view the audit log", -}, -{ -name: "PRIORITY_SPEAKER", -readableName: "Priority speaker", -description: "Allows for using priority speaker in a voice channel", -}, -{ -name: "STREAM", -readableName: "Video", -description: "Allows the user to stream", -}, -{ -name: "VIEW_CHANNEL", -readableName: "View channels", -description: "Allows the user to view the channel", -}, -{ -name: "SEND_MESSAGES", -readableName: "Send messages", -description: "Allows user to send messages", -}, -{ -name: "SEND_TTS_MESSAGES", -readableName: "Send text-to-speech messages", -description: "Allows the user to send text-to-speech messages", -}, -{ -name: "MANAGE_MESSAGES", -readableName: "Manage messages", -description: "Allows the user to delete messages that aren't their own", -}, -{ -name: "EMBED_LINKS", -readableName: "Embed links", -description: "Allow links sent by this user to auto-embed", -}, -{ -name: "ATTACH_FILES", -readableName: "Attach files", -description: "Allows the user to attach files", -}, -{ -name: "READ_MESSAGE_HISTORY", -readableName: "Read message history", -description: "Allows user to read the message history", -}, -{ -name: "MENTION_EVERYONE", -readableName: "Mention @everyone, @here and all roles", -description: "Allows the user to mention everyone", -}, -{ -name: "USE_EXTERNAL_EMOJIS", -readableName: "Use external emojis", -description: "Allows the user to use external emojis", -}, -{ -name: "VIEW_GUILD_INSIGHTS", -readableName: "View guild insights", -description: "Allows the user to see guild insights", -}, -{ -name: "CONNECT", -readableName: "Connect", -description: "Allows the user to connect to a voice channel", -}, -{ -name: "SPEAK", -readableName: "Speak", -description: "Allows the user to speak in a voice channel", -}, -{ -name: "MUTE_MEMBERS", -readableName: "Mute members", -description: "Allows user to mute other members", -}, -{ -name: "DEAFEN_MEMBERS", -readableName: "Deafen members", -description: "Allows user to deafen other members", -}, -{ -name: "MOVE_MEMBERS", -readableName: "Move members", -description: "Allows the user to move members between voice channels", -}, -{ -name: "USE_VAD", -readableName: "Use voice activity detection", -description: + }, + { + name: "MANAGE_CHANNELS", + readableName: "Manage channels", + description: "Allows the user to manage and edit channels", + }, + { + name: "MANAGE_GUILD", + readableName: "Manage guild", + description: "Allows management and editing of the guild", + }, + { + name: "ADD_REACTIONS", + readableName: "Add reactions", + description: "Allows user to add reactions to messages", + }, + { + name: "VIEW_AUDIT_LOG", + readableName: "View audit log", + description: "Allows the user to view the audit log", + }, + { + name: "PRIORITY_SPEAKER", + readableName: "Priority speaker", + description: "Allows for using priority speaker in a voice channel", + }, + { + name: "STREAM", + readableName: "Video", + description: "Allows the user to stream", + }, + { + name: "VIEW_CHANNEL", + readableName: "View channels", + description: "Allows the user to view the channel", + }, + { + name: "SEND_MESSAGES", + readableName: "Send messages", + description: "Allows user to send messages", + }, + { + name: "SEND_TTS_MESSAGES", + readableName: "Send text-to-speech messages", + description: "Allows the user to send text-to-speech messages", + }, + { + name: "MANAGE_MESSAGES", + readableName: "Manage messages", + description: "Allows the user to delete messages that aren't their own", + }, + { + name: "EMBED_LINKS", + readableName: "Embed links", + description: "Allow links sent by this user to auto-embed", + }, + { + name: "ATTACH_FILES", + readableName: "Attach files", + description: "Allows the user to attach files", + }, + { + name: "READ_MESSAGE_HISTORY", + readableName: "Read message history", + description: "Allows user to read the message history", + }, + { + name: "MENTION_EVERYONE", + readableName: "Mention @everyone, @here and all roles", + description: "Allows the user to mention everyone", + }, + { + name: "USE_EXTERNAL_EMOJIS", + readableName: "Use external emojis", + description: "Allows the user to use external emojis", + }, + { + name: "VIEW_GUILD_INSIGHTS", + readableName: "View guild insights", + description: "Allows the user to see guild insights", + }, + { + name: "CONNECT", + readableName: "Connect", + description: "Allows the user to connect to a voice channel", + }, + { + name: "SPEAK", + readableName: "Speak", + description: "Allows the user to speak in a voice channel", + }, + { + name: "MUTE_MEMBERS", + readableName: "Mute members", + description: "Allows user to mute other members", + }, + { + name: "DEAFEN_MEMBERS", + readableName: "Deafen members", + description: "Allows user to deafen other members", + }, + { + name: "MOVE_MEMBERS", + readableName: "Move members", + description: "Allows the user to move members between voice channels", + }, + { + name: "USE_VAD", + readableName: "Use voice activity detection", + description: "Allows users to speak in a voice channel by simply talking", -}, -{ -name: "CHANGE_NICKNAME", -readableName: "Change nickname", -description: "Allows the user to change their own nickname", -}, -{ -name: "MANAGE_NICKNAMES", -readableName: "Manage nicknames", -description: "Allows user to change nicknames of other members", -}, -{ -name: "MANAGE_ROLES", -readableName: "Manage roles", -description: "Allows user to edit and manage roles", -}, -{ -name: "MANAGE_WEBHOOKS", -readableName: "Manage webhooks", -description: "Allows management and editing of webhooks", -}, -{ -name: "MANAGE_GUILD_EXPRESSIONS", -readableName: "Manage expressions", -description: "Allows for managing emoji, stickers, and soundboards", -}, -{ -name: "USE_APPLICATION_COMMANDS", -readableName: "Use application commands", -description: "Allows the user to use application commands", -}, -{ -name: "REQUEST_TO_SPEAK", -readableName: "Request to speak", -description: "Allows user to request to speak in stage channel", -}, -{ -name: "MANAGE_EVENTS", -readableName: "Manage events", -description: "Allows user to edit and manage events", -}, -{ -name: "MANAGE_THREADS", -readableName: "Manage threads", -description: + }, + { + name: "CHANGE_NICKNAME", + readableName: "Change nickname", + description: "Allows the user to change their own nickname", + }, + { + name: "MANAGE_NICKNAMES", + readableName: "Manage nicknames", + description: "Allows user to change nicknames of other members", + }, + { + name: "MANAGE_ROLES", + readableName: "Manage roles", + description: "Allows user to edit and manage roles", + }, + { + name: "MANAGE_WEBHOOKS", + readableName: "Manage webhooks", + description: "Allows management and editing of webhooks", + }, + { + name: "MANAGE_GUILD_EXPRESSIONS", + readableName: "Manage expressions", + description: "Allows for managing emoji, stickers, and soundboards", + }, + { + name: "USE_APPLICATION_COMMANDS", + readableName: "Use application commands", + description: "Allows the user to use application commands", + }, + { + name: "REQUEST_TO_SPEAK", + readableName: "Request to speak", + description: "Allows user to request to speak in stage channel", + }, + { + name: "MANAGE_EVENTS", + readableName: "Manage events", + description: "Allows user to edit and manage events", + }, + { + name: "MANAGE_THREADS", + readableName: "Manage threads", + description: "Allows the user to delete and archive threads and view all private threads", -}, -{ -name: "CREATE_PUBLIC_THREADS", -readableName: "Create public threads", -description: "Allows the user to create public threads", -}, -{ -name: "CREATE_PRIVATE_THREADS", -readableName: "Create private threads", -description: "Allows the user to create private threads", -}, -{ -name: "USE_EXTERNAL_STICKERS", -readableName: "Use external stickers", -description: "Allows user to use external stickers", -}, -{ -name: "SEND_MESSAGES_IN_THREADS", -readableName: "Send messages in threads", -description: "Allows the user to send messages in threads", -}, -{ -name: "USE_EMBEDDED_ACTIVITIES", -readableName: "Use activities", -description: "Allows the user to use embedded activities", -}, -{ -name: "MODERATE_MEMBERS", -readableName: "Timeout members", -description: + }, + { + name: "CREATE_PUBLIC_THREADS", + readableName: "Create public threads", + description: "Allows the user to create public threads", + }, + { + name: "CREATE_PRIVATE_THREADS", + readableName: "Create private threads", + description: "Allows the user to create private threads", + }, + { + name: "USE_EXTERNAL_STICKERS", + readableName: "Use external stickers", + description: "Allows user to use external stickers", + }, + { + name: "SEND_MESSAGES_IN_THREADS", + readableName: "Send messages in threads", + description: "Allows the user to send messages in threads", + }, + { + name: "USE_EMBEDDED_ACTIVITIES", + readableName: "Use activities", + description: "Allows the user to use embedded activities", + }, + { + name: "MODERATE_MEMBERS", + readableName: "Timeout members", + description: "Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels", -}, -{ -name: "VIEW_CREATOR_MONETIZATION_ANALYTICS", -readableName: "View creator monetization analytics", -description: "Allows for viewing role subscription insights", -}, -{ -name: "USE_SOUNDBOARD", -readableName: "Use soundboard", -description: "Allows for using soundboard in a voice channel", -}, -{ -name: "CREATE_GUILD_EXPRESSIONS", -readableName: "Create expressions", -description: + }, + { + name: "VIEW_CREATOR_MONETIZATION_ANALYTICS", + readableName: "View creator monetization analytics", + description: "Allows for viewing role subscription insights", + }, + { + name: "USE_SOUNDBOARD", + readableName: "Use soundboard", + description: "Allows for using soundboard in a voice channel", + }, + { + name: "CREATE_GUILD_EXPRESSIONS", + readableName: "Create expressions", + description: "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", -}, -{ -name: "CREATE_EVENTS", -readableName: "Create events", -description: + }, + { + name: "CREATE_EVENTS", + readableName: "Create events", + description: "Allows for creating scheduled events, and editing and deleting those created by the current user.", -}, -{ -name: "USE_EXTERNAL_SOUNDS", -readableName: "Use external sounds", -description: + }, + { + name: "USE_EXTERNAL_SOUNDS", + readableName: "Use external sounds", + description: "Allows the usage of custom soundboard sounds from other servers", -}, -{ -name: "SEND_VOICE_MESSAGES", -readableName: "Send voice messages", -description: "Allows sending voice messages", -}, -{ -name: "SEND_POLLS", -readableName: "Create polls", -description: "Allows sending polls", -}, -{ -name: "USE_EXTERNAL_APPS", -readableName: "Use external apps", -description: + }, + { + name: "SEND_VOICE_MESSAGES", + readableName: "Send voice messages", + description: "Allows sending voice messages", + }, + { + name: "SEND_POLLS", + readableName: "Create polls", + description: "Allows sending polls", + }, + { + name: "USE_EXTERNAL_APPS", + readableName: "Use external apps", + description: "Allows user-installed apps to send public responses. " + "When disabled, users will still be allowed to use their apps but the responses will be ephemeral. " + "This only applies to apps not also installed to the server.", -}, -]; -Permissions.map = {}; -let i = 0; -for (const thing of Permissions.info) { -Permissions.map[i] = thing; -Permissions.map[thing.name] = i; -i++; -} -} -getPermission(name: string): number { -if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) { -return 1; -} else if ( -this.getPermissionbit(Permissions.map[name] as number, this.deny) -) { -return -1; -} else { -return 0; -} -} -hasPermission(name: string): boolean { -if (this.deny) { -console.warn( -"This function may of been used in error, think about using getPermision instead" -); -} -if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) -return true; -if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR"); -return false; -} -setPermission(name: string, setto: number): void { -const bit = Permissions.map[name] as number; -if (!bit) { -return console.error( -"Tried to set permission to " + + }, + ]; + Permissions.map = {}; + let i = 0; + for(const thing of Permissions.info){ + Permissions.map[i] = thing; + Permissions.map[thing.name] = i; + i++; + } + } + getPermission(name: string): number{ + if(this.getPermissionbit(Permissions.map[name] as number, this.allow)){ + return 1; + }else if( + this.getPermissionbit(Permissions.map[name] as number, this.deny) + ){ + return-1; + }else{ + return 0; + } + } + hasPermission(name: string): boolean{ + if(this.deny){ + console.warn( + "This function may of been used in error, think about using getPermision instead" + ); + } + if(this.getPermissionbit(Permissions.map[name] as number, this.allow)) + return true; + if(name != "ADMINISTRATOR")return this.hasPermission("ADMINISTRATOR"); + return false; + } + setPermission(name: string, setto: number): void{ + const bit = Permissions.map[name] as number; + if(!bit){ + return console.error( + "Tried to set permission to " + setto + " for " + name + " but it doesn't exist" -); -} + ); + } -if (setto === 0) { -this.deny = this.setPermissionbit(bit, false, this.deny); -this.allow = this.setPermissionbit(bit, false, this.allow); -} else if (setto === 1) { -this.deny = this.setPermissionbit(bit, false, this.deny); -this.allow = this.setPermissionbit(bit, true, this.allow); -} else if (setto === -1) { -this.deny = this.setPermissionbit(bit, true, this.deny); -this.allow = this.setPermissionbit(bit, false, this.allow); -} else { -console.error("invalid number entered:" + setto); -} -} + if(setto === 0){ + this.deny = this.setPermissionbit(bit, false, this.deny); + this.allow = this.setPermissionbit(bit, false, this.allow); + }else if(setto === 1){ + this.deny = this.setPermissionbit(bit, false, this.deny); + this.allow = this.setPermissionbit(bit, true, this.allow); + }else if(setto === -1){ + this.deny = this.setPermissionbit(bit, true, this.deny); + this.allow = this.setPermissionbit(bit, false, this.allow); + }else{ + console.error("invalid number entered:" + setto); + } + } } Permissions.makeMap(); -export { Permissions }; +export{ Permissions }; diff --git a/src/webpage/register.ts b/src/webpage/register.ts index f08c224..85e99f8 100644 --- a/src/webpage/register.ts +++ b/src/webpage/register.ts @@ -1,152 +1,152 @@ -import { checkInstance, adduser } from "./login.js"; +import{ checkInstance, adduser }from"./login.js"; const registerElement = document.getElementById("register"); -if (registerElement) { -registerElement.addEventListener("submit", registertry); +if(registerElement){ + registerElement.addEventListener("submit", registertry); } -async function registertry(e: Event) { -e.preventDefault(); -const elements = (e.target as HTMLFormElement) -.elements as HTMLFormControlsCollection; -const email = (elements[1] as HTMLInputElement).value; -const username = (elements[2] as HTMLInputElement).value; -const password = (elements[3] as HTMLInputElement).value; -const confirmPassword = (elements[4] as HTMLInputElement).value; -const dateofbirth = (elements[5] as HTMLInputElement).value; -const consent = (elements[6] as HTMLInputElement).checked; -const captchaKey = (elements[7] as HTMLInputElement)?.value; +async function registertry(e: Event){ + e.preventDefault(); + const elements = (e.target as HTMLFormElement) + .elements as HTMLFormControlsCollection; + const email = (elements[1] as HTMLInputElement).value; + const username = (elements[2] as HTMLInputElement).value; + const password = (elements[3] as HTMLInputElement).value; + const confirmPassword = (elements[4] as HTMLInputElement).value; + const dateofbirth = (elements[5] as HTMLInputElement).value; + const consent = (elements[6] as HTMLInputElement).checked; + const captchaKey = (elements[7] as HTMLInputElement)?.value; -if (password !== confirmPassword) { -(document.getElementById("wrong") as HTMLElement).textContent = + if(password !== confirmPassword){ + (document.getElementById("wrong") as HTMLElement).textContent = "Passwords don't match"; -return; + return; + } + + const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); + const apiurl = new URL(instanceInfo.api); + + try{ + const response = await fetch(apiurl + "/auth/register", { + body: JSON.stringify({ + date_of_birth: dateofbirth, + email, + username, + password, + consent, + captcha_key: captchaKey, + }), + headers: { + "content-type": "application/json", + }, + method: "POST", + }); + + const data = await response.json(); + + if(data.captcha_sitekey){ + const capt = document.getElementById("h-captcha"); + if(capt && !capt.children.length){ + const capty = document.createElement("div"); + capty.classList.add("h-captcha"); + capty.setAttribute("data-sitekey", data.captcha_sitekey); + const script = document.createElement("script"); + script.src = "https://js.hcaptcha.com/1/api.js"; + capt.append(script); + capt.append(capty); + }else{ + eval("hcaptcha.reset()"); + } + return; + } + + if(!data.token){ + handleErrors(data.errors, elements); + }else{ + adduser({ + serverurls: instanceInfo, + email, + token: data.token, + }).username = username; + localStorage.setItem("token", data.token); + const redir = new URLSearchParams(window.location.search).get("goback"); + window.location.href = redir ? redir : "/channels/@me"; + } + }catch(error){ + console.error("Registration failed:", error); + } } -const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); -const apiurl = new URL(instanceInfo.api); - -try { -const response = await fetch(apiurl + "/auth/register", { -body: JSON.stringify({ -date_of_birth: dateofbirth, -email, -username, -password, -consent, -captcha_key: captchaKey, -}), -headers: { -"content-type": "application/json", -}, -method: "POST", -}); - -const data = await response.json(); - -if (data.captcha_sitekey) { -const capt = document.getElementById("h-captcha"); -if (capt && !capt.children.length) { -const capty = document.createElement("div"); -capty.classList.add("h-captcha"); -capty.setAttribute("data-sitekey", data.captcha_sitekey); -const script = document.createElement("script"); -script.src = "https://js.hcaptcha.com/1/api.js"; -capt.append(script); -capt.append(capty); -} else { -eval("hcaptcha.reset()"); -} -return; -} - -if (!data.token) { -handleErrors(data.errors, elements); -} else { -adduser({ -serverurls: instanceInfo, -email, -token: data.token, -}).username = username; -localStorage.setItem("token", data.token); -const redir = new URLSearchParams(window.location.search).get("goback"); -window.location.href = redir ? redir : "/channels/@me"; -} -} catch (error) { -console.error("Registration failed:", error); -} -} - -function handleErrors(errors: any, elements: HTMLFormControlsCollection) { -if (errors.consent) { -showError(elements[6] as HTMLElement, errors.consent._errors[0].message); -} else if (errors.password) { -showError( +function handleErrors(errors: any, elements: HTMLFormControlsCollection){ + if(errors.consent){ + showError(elements[6] as HTMLElement, errors.consent._errors[0].message); + }else if(errors.password){ + showError( elements[3] as HTMLElement, "Password: " + errors.password._errors[0].message -); -} else if (errors.username) { -showError( + ); + }else if(errors.username){ + showError( elements[2] as HTMLElement, "Username: " + errors.username._errors[0].message -); -} else if (errors.email) { -showError( + ); + }else if(errors.email){ + showError( elements[1] as HTMLElement, "Email: " + errors.email._errors[0].message -); -} else if (errors.date_of_birth) { -showError( + ); + }else if(errors.date_of_birth){ + showError( elements[5] as HTMLElement, "Date of Birth: " + errors.date_of_birth._errors[0].message -); -} else { -(document.getElementById("wrong") as HTMLElement).textContent = + ); + }else{ + (document.getElementById("wrong") as HTMLElement).textContent = errors[Object.keys(errors)[0]]._errors[0].message; -} + } } -function showError(element: HTMLElement, message: string) { -const parent = element.parentElement!; -let errorElement = parent.getElementsByClassName( -"suberror" -)[0] as HTMLElement; -if (!errorElement) { -const div = document.createElement("div"); -div.classList.add("suberror", "suberrora"); -parent.append(div); -errorElement = div; -} else { -errorElement.classList.remove("suberror"); -setTimeout(() => { -errorElement.classList.add("suberror"); -}, 100); -} -errorElement.textContent = message; +function showError(element: HTMLElement, message: string){ + const parent = element.parentElement!; + let errorElement = parent.getElementsByClassName( + "suberror" + )[0] as HTMLElement; + if(!errorElement){ + const div = document.createElement("div"); + div.classList.add("suberror", "suberrora"); + parent.append(div); + errorElement = div; + }else{ + errorElement.classList.remove("suberror"); + setTimeout(()=>{ + errorElement.classList.add("suberror"); + }, 100); + } + errorElement.textContent = message; } let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null; -async function tosLogic() { -const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); -const apiurl = new URL(instanceInfo.api); -const response = await fetch(apiurl.toString() + "/ping"); -const data = await response.json(); -const tosPage = data.instance.tosPage; +async function tosLogic(){ + const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); + const apiurl = new URL(instanceInfo.api); + const response = await fetch(apiurl.toString() + "/ping"); + const data = await response.json(); + const tosPage = data.instance.tosPage; -if (tosPage) { + if(tosPage){ document.getElementById("TOSbox")!.innerHTML = -'I agree to the Terms of Service:'; +"I agree to the Terms of Service:"; TOSa = document.getElementById("TOSa") as HTMLAnchorElement; TOSa.href = tosPage; -} else { + }else{ document.getElementById("TOSbox")!.textContent = "This instance has no Terms of Service, accept ToS anyways:"; TOSa = null; -} -console.log(tosPage); + } + console.log(tosPage); } tosLogic(); -(checkInstance as any)["alt"] = tosLogic; +(checkInstance as any).alt = tosLogic; diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 43be79c..6ab0848 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -1,48 +1,48 @@ -import { Permissions } from "./permissions.js"; -import { Localuser } from "./localuser.js"; -import { Guild } from "./guild.js"; -import { SnowFlake } from "./snowflake.js"; -import { rolesjson } from "./jsontypes.js"; -class Role extends SnowFlake { -permissions: Permissions; -owner: Guild; -color!: number; -name!: string; -info: Guild["info"]; -hoist!: boolean; -icon!: string; -mentionable!: boolean; -unicode_emoji!: string; -headers: Guild["headers"]; -constructor(json: rolesjson, owner: Guild) { -super(json.id); -this.headers = owner.headers; -this.info = owner.info; -for (const thing of Object.keys(json)) { -if (thing === "id") { -continue; +import{ Permissions }from"./permissions.js"; +import{ Localuser }from"./localuser.js"; +import{ Guild }from"./guild.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ rolesjson }from"./jsontypes.js"; +class Role extends SnowFlake{ + permissions: Permissions; + owner: Guild; + color!: number; + name!: string; + info: Guild["info"]; + hoist!: boolean; + icon!: string; + mentionable!: boolean; + unicode_emoji!: string; + headers: Guild["headers"]; + constructor(json: rolesjson, owner: Guild){ + super(json.id); + this.headers = owner.headers; + this.info = owner.info; + for(const thing of Object.keys(json)){ + if(thing === "id"){ + continue; + } + (this as any)[thing] = (json as any)[thing]; + } + this.permissions = new Permissions(json.permissions); + this.owner = owner; + } + get guild(): Guild{ + return this.owner; + } + get localuser(): Localuser{ + return this.guild.localuser; + } + getColor(): string | null{ + if(this.color === 0){ + return null; + } + return`#${this.color.toString(16)}`; + } } -(this as any)[thing] = (json as any)[thing]; -} -this.permissions = new Permissions(json.permissions); -this.owner = owner; -} -get guild(): Guild { -return this.owner; -} -get localuser(): Localuser { -return this.guild.localuser; -} -getColor(): string | null { -if (this.color === 0) { -return null; -} -return `#${this.color.toString(16)}`; -} -} -export { Role }; -import { Options } from "./settings.js"; -class PermissionToggle implements OptionsElement { +export{ Role }; +import{ Options }from"./settings.js"; +class PermissionToggle implements OptionsElement{ readonly rolejson: { name: string; readableName: string; @@ -52,76 +52,76 @@ class PermissionToggle implements OptionsElement { owner: Options; value!: number; constructor( - roleJSON: PermissionToggle["rolejson"], - permissions: Permissions, - owner: Options - ) { - this.rolejson = roleJSON; - this.permissions = permissions; - this.owner = owner; + roleJSON: PermissionToggle["rolejson"], + permissions: Permissions, + owner: Options + ){ + this.rolejson = roleJSON; + this.permissions = permissions; + this.owner = owner; } - watchForChange() {} - generateHTML(): HTMLElement { - const div = document.createElement("div"); - div.classList.add("setting"); - const name = document.createElement("span"); - name.textContent = this.rolejson.readableName; - name.classList.add("settingsname"); - div.append(name); + watchForChange(){} + generateHTML(): HTMLElement{ + const div = document.createElement("div"); + div.classList.add("setting"); + const name = document.createElement("span"); + name.textContent = this.rolejson.readableName; + name.classList.add("settingsname"); + div.append(name); - div.append(this.generateCheckbox()); - const p = document.createElement("p"); - p.textContent = this.rolejson.description; - div.appendChild(p); - return div; + div.append(this.generateCheckbox()); + const p = document.createElement("p"); + p.textContent = this.rolejson.description; + div.appendChild(p); + return div; } - generateCheckbox(): HTMLElement { - const div = document.createElement("div"); - div.classList.add("tritoggle"); - const state = this.permissions.getPermission(this.rolejson.name); + generateCheckbox(): HTMLElement{ + const div = document.createElement("div"); + div.classList.add("tritoggle"); + const state = this.permissions.getPermission(this.rolejson.name); - const on = document.createElement("input"); - on.type = "radio"; - on.name = this.rolejson.name; - div.append(on); - if (state === 1) { - on.checked = true; - } - on.onclick = (_) => { - this.permissions.setPermission(this.rolejson.name, 1); - this.owner.changed(); - }; + const on = document.createElement("input"); + on.type = "radio"; + on.name = this.rolejson.name; + div.append(on); + if(state === 1){ + on.checked = true; + } + on.onclick = _=>{ + this.permissions.setPermission(this.rolejson.name, 1); + this.owner.changed(); + }; - const no = document.createElement("input"); - no.type = "radio"; - no.name = this.rolejson.name; - div.append(no); - if (state === 0) { - no.checked = true; + const no = document.createElement("input"); + no.type = "radio"; + no.name = this.rolejson.name; + div.append(no); + if(state === 0){ + no.checked = true; + } + no.onclick = _=>{ + this.permissions.setPermission(this.rolejson.name, 0); + this.owner.changed(); + }; + if(this.permissions.hasDeny){ + const off = document.createElement("input"); + off.type = "radio"; + off.name = this.rolejson.name; + div.append(off); + if(state === -1){ + off.checked = true; + } + off.onclick = _=>{ + this.permissions.setPermission(this.rolejson.name, -1); + this.owner.changed(); + }; + } + return div; } - no.onclick = (_) => { - this.permissions.setPermission(this.rolejson.name, 0); - this.owner.changed(); - }; - if (this.permissions.hasDeny) { - const off = document.createElement("input"); - off.type = "radio"; - off.name = this.rolejson.name; - div.append(off); - if (state === -1) { - off.checked = true; - } - off.onclick = (_) => { - this.permissions.setPermission(this.rolejson.name, -1); - this.owner.changed(); - }; - } - return div; - } - submit() {} - } - import { OptionsElement, Buttons } from "./settings.js"; - class RoleList extends Buttons { + submit(){} +} +import{ OptionsElement, Buttons }from"./settings.js"; +class RoleList extends Buttons{ readonly permissions: [Role, Permissions][]; permission: Permissions; readonly guild: Guild; @@ -131,50 +131,50 @@ class PermissionToggle implements OptionsElement { onchange: Function; curid!: string; constructor( - permissions: [Role, Permissions][], - guild: Guild, - onchange: Function, - channel = false - ) { - super("Roles"); - this.guild = guild; - this.permissions = permissions; - this.channel = channel; - this.onchange = onchange; - const options = new Options("", this); - if (channel) { - this.permission = new Permissions("0", "0"); - } else { - this.permission = new Permissions("0"); + permissions: [Role, Permissions][], + guild: Guild, + onchange: Function, + channel = false + ){ + super("Roles"); + this.guild = guild; + this.permissions = permissions; + this.channel = channel; + this.onchange = onchange; + const options = new Options("", this); + if(channel){ + this.permission = new Permissions("0", "0"); + }else{ + this.permission = new Permissions("0"); + } + for(const thing of Permissions.info){ + options.options.push( + new PermissionToggle(thing, this.permission, options) + ); + } + for(const i of permissions){ + console.log(i); + this.buttons.push([i[0].name, i[0].id]); + } + this.options = options; } - for (const thing of Permissions.info) { - options.options.push( - new PermissionToggle(thing, this.permission, options) - ); + handleString(str: string): HTMLElement{ + this.curid = str; + const arr = this.permissions.find(_=>_[0].id === str); + if(arr){ + const perm = arr[1]; + this.permission.deny = perm.deny; + this.permission.allow = perm.allow; + const role = this.permissions.find(e=>e[0].id === str); + if(role){ + this.options.name = role[0].name; + this.options.haschanged = false; + } + } + return this.options.generateHTML(); } - for (const i of permissions) { - console.log(i); - this.buttons.push([i[0].name, i[0].id]); + save(){ + this.onchange(this.curid, this.permission); } - this.options = options; - } - handleString(str: string): HTMLElement { - this.curid = str; - const arr = this.permissions.find((_) => _[0].id === str); - if (arr) { - const perm = arr[1]; - this.permission.deny = perm.deny; - this.permission.allow = perm.allow; - const role = this.permissions.find((e) => e[0].id === str); - if (role) { - this.options.name = role[0].name; - this.options.haschanged = false; - } - } - return this.options.generateHTML(); - } - save() { - this.onchange(this.curid, this.permission); - } - } - export { RoleList }; +} +export{ RoleList }; diff --git a/src/webpage/service.ts b/src/webpage/service.ts index d711670..cf93f36 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -1,96 +1,96 @@ -function deleteoldcache() { -caches.delete("cache"); -console.log("this ran :P"); +function deleteoldcache(){ + caches.delete("cache"); + console.log("this ran :P"); } -async function putInCache(request: URL | RequestInfo, response: Response) { -console.log(request, response); -const cache = await caches.open("cache"); -console.log("Grabbed"); -try { -console.log(await cache.put(request, response)); -} catch (error) { -console.error(error); -} +async function putInCache(request: URL | RequestInfo, response: Response){ + console.log(request, response); + const cache = await caches.open("cache"); + console.log("Grabbed"); + try{ + console.log(await cache.put(request, response)); + }catch(error){ + console.error(error); + } } console.log("test"); let lastcache: string; -self.addEventListener("activate", async () => { -console.log("test2"); -checkCache(); -}); -async function checkCache() { -if (checkedrecently) { -return; -} -const promise = await caches.match("/getupdates"); -if (promise) { -lastcache = await promise.text(); -} -console.log(lastcache); -fetch("/getupdates").then(async (data) => { -const text = await data.clone().text(); -console.log(text, lastcache); -if (lastcache !== text) { -deleteoldcache(); -putInCache("/getupdates", data.clone()); -} -checkedrecently = true; -setTimeout((_: any) => { -checkedrecently = false; -}, 1000 * 60 * 30); +self.addEventListener("activate", async ()=>{ + console.log("test2"); + checkCache(); }); +async function checkCache(){ + if(checkedrecently){ + return; + } + const promise = await caches.match("/getupdates"); + if(promise){ + lastcache = await promise.text(); + } + console.log(lastcache); + fetch("/getupdates").then(async data=>{ + const text = await data.clone().text(); + console.log(text, lastcache); + if(lastcache !== text){ + deleteoldcache(); + putInCache("/getupdates", data.clone()); + } + checkedrecently = true; + setTimeout((_: any)=>{ + checkedrecently = false; + }, 1000 * 60 * 30); + }); } var checkedrecently = false; -function samedomain(url: string | URL) { -return new URL(url).origin === self.origin; +function samedomain(url: string | URL){ + return new URL(url).origin === self.origin; } -function isindexhtml(url: string | URL) { -console.log(url); -if (new URL(url).pathname.startsWith("/channels")) { -return true; -} -return false; +function isindexhtml(url: string | URL){ + console.log(url); + if(new URL(url).pathname.startsWith("/channels")){ + return true; + } + return false; } async function getfile(event: { request: { url: URL | RequestInfo; clone: () => string | URL | Request }; -}) { -checkCache(); -if (!samedomain(event.request.url.toString())) { -return await fetch(event.request.clone()); -} -const responseFromCache = await caches.match(event.request.url); -console.log(responseFromCache, caches); -if (responseFromCache) { -console.log("cache hit"); -return responseFromCache; -} -if (isindexhtml(event.request.url.toString())) { -console.log("is index.html"); -const responseFromCache = await caches.match("/index.html"); -if (responseFromCache) { -console.log("cache hit"); -return responseFromCache; -} -const responseFromNetwork = await fetch("/index.html"); -await putInCache("/index.html", responseFromNetwork.clone()); -return responseFromNetwork; -} -const responseFromNetwork = await fetch(event.request.clone()); -console.log(event.request.clone()); -await putInCache(event.request.clone(), responseFromNetwork.clone()); -try { -return responseFromNetwork; -} catch (e) { -console.error(e); -return e; -} -} -self.addEventListener("fetch", (event: any) => { -try { -event.respondWith(getfile(event)); -} catch (e) { -console.error(e); +}){ + checkCache(); + if(!samedomain(event.request.url.toString())){ + return await fetch(event.request.clone()); + } + const responseFromCache = await caches.match(event.request.url); + console.log(responseFromCache, caches); + if(responseFromCache){ + console.log("cache hit"); + return responseFromCache; + } + if(isindexhtml(event.request.url.toString())){ + console.log("is index.html"); + const responseFromCache = await caches.match("/index.html"); + if(responseFromCache){ + console.log("cache hit"); + return responseFromCache; + } + const responseFromNetwork = await fetch("/index.html"); + await putInCache("/index.html", responseFromNetwork.clone()); + return responseFromNetwork; + } + const responseFromNetwork = await fetch(event.request.clone()); + console.log(event.request.clone()); + await putInCache(event.request.clone(), responseFromNetwork.clone()); + try{ + return responseFromNetwork; + }catch(e){ + console.error(e); + return e; + } } +self.addEventListener("fetch", (event: any)=>{ + try{ + event.respondWith(getfile(event)); + }catch(e){ + console.error(e); + } }); diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 6aa3425..2eed7ec 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -6,24 +6,24 @@ interface OptionsElement { value: x; } //future me stuff - class Buttons implements OptionsElement { - readonly name: string; - readonly buttons: [string, Options | string][]; - buttonList!: HTMLDivElement; - warndiv!: HTMLElement; - value: unknown; - constructor(name: string) { +class Buttons implements OptionsElement{ + readonly name: string; + readonly buttons: [string, Options | string][]; + buttonList!: HTMLDivElement; + warndiv!: HTMLElement; + value: unknown; + constructor(name: string){ this.buttons = []; this.name = name; - } - add(name: string, thing?: Options | undefined) { - if (!thing) { - thing = new Options(name, this); + } + add(name: string, thing?: Options | undefined){ + if(!thing){ + thing = new Options(name, this); } this.buttons.push([name, thing]); return thing; - } - generateHTML() { + } + generateHTML(){ const buttonList = document.createElement("div"); buttonList.classList.add("Buttons"); buttonList.classList.add("flexltr"); @@ -32,1082 +32,1082 @@ interface OptionsElement { htmlarea.classList.add("flexgrow"); const buttonTable = document.createElement("div"); buttonTable.classList.add("flexttb", "settingbuttons"); - for (const thing of this.buttons) { - const button = document.createElement("button"); - button.classList.add("SettingsButton"); - button.textContent = thing[0]; - button.onclick = (_) => { - this.generateHTMLArea(thing[1], htmlarea); - if (this.warndiv) { - this.warndiv.remove(); - } - }; - buttonTable.append(button); + for(const thing of this.buttons){ + const button = document.createElement("button"); + button.classList.add("SettingsButton"); + button.textContent = thing[0]; + button.onclick = _=>{ + this.generateHTMLArea(thing[1], htmlarea); + if(this.warndiv){ + this.warndiv.remove(); + } + }; + buttonTable.append(button); } this.generateHTMLArea(this.buttons[0][1], htmlarea); buttonList.append(buttonTable); buttonList.append(htmlarea); return buttonList; - } - handleString(str: string): HTMLElement { + } + handleString(str: string): HTMLElement{ const div = document.createElement("span"); div.textContent = str; return div; - } - private generateHTMLArea( + } + private generateHTMLArea( buttonInfo: Options | string, htmlarea: HTMLElement - ) { + ){ let html: HTMLElement; - if (buttonInfo instanceof Options) { - buttonInfo.subOptions = undefined; - html = buttonInfo.generateHTML(); - } else { - html = this.handleString(buttonInfo); + if(buttonInfo instanceof Options){ + buttonInfo.subOptions = undefined; + html = buttonInfo.generateHTML(); + }else{ + html = this.handleString(buttonInfo); } htmlarea.innerHTML = ""; htmlarea.append(html); - } - changed(html: HTMLElement) { + } + changed(html: HTMLElement){ this.warndiv = html; this.buttonList.append(html); - } - watchForChange() {} - save() {} - submit() {} - } + } + watchForChange(){} + save(){} + submit(){} +} - class TextInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: string) => void; - value: string; - input!: WeakRef; - password: boolean; - constructor( - label: string, - onSubmit: (str: string) => void, - owner: Options, - { initText = "", password = false } = {} - ) { - this.label = label; - this.value = initText; - this.owner = owner; - this.onSubmit = onSubmit; - this.password = password; +class TextInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: string) => void; + value: string; + input!: WeakRef; + password: boolean; + constructor( + label: string, + onSubmit: (str: string) => void, + owner: Options, + { initText = "", password = false } = {} + ){ + this.label = label; + this.value = initText; + this.owner = owner; + this.onSubmit = onSubmit; + this.password = password; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.value = this.value; + input.type = this.password ? "password" : "text"; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + private onChange(){ + this.owner.changed(); + const input = this.input.deref(); + if(input){ + const value = input.value as string; + this.onchange(value); + this.value = value; + } + } + onchange: (str: string) => void = _=>{}; + watchForChange(func: (str: string) => void){ + this.onchange = func; + } + submit(){ + this.onSubmit(this.value); + } +} + +class SettingsText implements OptionsElement{ + readonly onSubmit!: (str: string) => void; + value!: void; + readonly text: string; + constructor(text: string){ + this.text = text; + } + generateHTML(): HTMLSpanElement{ + const span = document.createElement("span"); + span.innerText = this.text; + return span; + } + watchForChange(){} + submit(){} +} +class SettingsTitle implements OptionsElement{ + readonly onSubmit!: (str: string) => void; + value!: void; + readonly text: string; + constructor(text: string){ + this.text = text; + } + generateHTML(): HTMLSpanElement{ + const span = document.createElement("h2"); + span.innerText = this.text; + return span; + } + watchForChange(){} + submit(){} +} +class CheckboxInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: boolean) => void; + value: boolean; + input!: WeakRef; + constructor( + label: string, + onSubmit: (str: boolean) => void, + owner: Options, + { initState = false } = {} + ){ + this.label = label; + this.value = initState; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = this.value; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + private onChange(){ + this.owner.changed(); + const input = this.input.deref(); + if(input){ + const value = input.checked as boolean; + this.onchange(value); + this.value = value; + } + } + onchange: (str: boolean) => void = _=>{}; + watchForChange(func: (str: boolean) => void){ + this.onchange = func; + } + submit(){ + this.onSubmit(this.value); + } +} + +class ButtonInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onClick: () => void; + textContent: string; + value!: void; + constructor( + label: string, + textContent: string, + onClick: () => void, + owner: Options, + {} = {} + ){ + this.label = label; + this.owner = owner; + this.onClick = onClick; + this.textContent = textContent; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const button = document.createElement("button"); + button.textContent = this.textContent; + button.onclick = this.onClickEvent.bind(this); + div.append(button); + return div; + } + private onClickEvent(){ + this.onClick(); + } + watchForChange(){} + submit(){} +} + +class ColorInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: string) => void; + colorContent: string; + input!: WeakRef; + value!: string; + constructor( + label: string, + onSubmit: (str: string) => void, + owner: Options, + { initColor = "" } = {} + ){ + this.label = label; + this.colorContent = initColor; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.value = this.colorContent; + input.type = "color"; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + private onChange(){ + this.owner.changed(); + const input = this.input.deref(); + if(input){ + const value = input.value as string; + this.value = value; + this.onchange(value); + this.colorContent = value; + } + } + onchange: (str: string) => void = _=>{}; + watchForChange(func: (str: string) => void){ + this.onchange = func; + } + submit(){ + this.onSubmit(this.colorContent); + } +} + +class SelectInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: number) => void; + options: string[]; + index: number; + select!: WeakRef; + get value(){ + return this.index; + } + constructor( + label: string, + onSubmit: (str: number) => void, + options: string[], + owner: Options, + { defaultIndex = 0 } = {} + ){ + this.label = label; + this.index = defaultIndex; + this.owner = owner; + this.onSubmit = onSubmit; + this.options = options; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const select = document.createElement("select"); + + select.onchange = this.onChange.bind(this); + for(const thing of this.options){ + const option = document.createElement("option"); + option.textContent = thing; + select.appendChild(option); + } + this.select = new WeakRef(select); + select.selectedIndex = this.index; + div.append(select); + return div; + } + private onChange(){ + this.owner.changed(); + const select = this.select.deref(); + if(select){ + const value = select.selectedIndex; + this.onchange(value); + this.index = value; + } + } + onchange: (str: number) => void = _=>{}; + watchForChange(func: (str: number) => void){ + this.onchange = func; + } + submit(){ + this.onSubmit(this.index); + } +} +class MDInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: string) => void; + value: string; + input!: WeakRef; + constructor( + label: string, + onSubmit: (str: string) => void, + owner: Options, + { initText = "" } = {} + ){ + this.label = label; + this.value = initText; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + div.append(document.createElement("br")); + const input = document.createElement("textarea"); + input.value = this.value; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + onChange(){ + this.owner.changed(); + const input = this.input.deref(); + if(input){ + const value = input.value as string; + this.onchange(value); + this.value = value; + } + } + onchange: (str: string) => void = _=>{}; + watchForChange(func: (str: string) => void){ + this.onchange = func; + } + submit(){ + this.onSubmit(this.value); + } +} +class FileInput implements OptionsElement{ + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: FileList | null) => void; + input!: WeakRef; + value!: FileList | null; + clear: boolean; + constructor( + label: string, + onSubmit: (str: FileList | null) => void, + owner: Options, + { clear = false } = {} + ){ + this.label = label; + this.owner = owner; + this.onSubmit = onSubmit; + this.clear = clear; + } + generateHTML(): HTMLDivElement{ + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.type = "file"; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + if(this.clear){ + const button = document.createElement("button"); + button.textContent = "Clear"; + button.onclick = _=>{ + if(this.onchange){ + this.onchange(null); } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.value = this.value; - input.type = this.password ? "password" : "text"; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - private onChange() { + this.value = null; this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.value as string; - this.onchange(value); - this.value = value; - } - } - onchange: (str: string) => void = (_) => {}; - watchForChange(func: (str: string) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.value); - } - } + }; + div.append(button); + } + return div; + } + onChange(){ + this.owner.changed(); + const input = this.input.deref(); + if(input){ + this.value = input.files; + if(this.onchange){ + this.onchange(input.files); + } + } + } + onchange: ((str: FileList | null) => void) | null = null; + watchForChange(func: (str: FileList | null) => void){ + this.onchange = func; + } + submit(){ + const input = this.input.deref(); + if(input){ + this.onSubmit(input.files); + } + } +} - class SettingsText implements OptionsElement { - readonly onSubmit!: (str: string) => void; - value!: void; - readonly text: string; - constructor(text: string) { - this.text = text; - } - generateHTML(): HTMLSpanElement { - const span = document.createElement("span"); - span.innerText = this.text; - return span; - } - watchForChange() {} - submit() {} - } - class SettingsTitle implements OptionsElement { - readonly onSubmit!: (str: string) => void; - value!: void; - readonly text: string; - constructor(text: string) { - this.text = text; +class HtmlArea implements OptionsElement{ + submit: () => void; + html: (() => HTMLElement) | HTMLElement; + value!: void; + constructor(html: (() => HTMLElement) | HTMLElement, submit: () => void){ + this.submit = submit; + this.html = html; + } + generateHTML(): HTMLElement{ + if(this.html instanceof Function){ + return this.html(); + }else{ + return this.html; + } + } + watchForChange(){} +} +class Options implements OptionsElement{ + name: string; + haschanged = false; + readonly options: OptionsElement[]; + readonly owner: Buttons | Options | Form; + readonly ltr: boolean; + value!: void; + readonly html: WeakMap, WeakRef> = + new WeakMap(); + container: WeakRef = new WeakRef( + document.createElement("div") + ); + constructor( + name: string, + owner: Buttons | Options | Form, + { ltr = false } = {} + ){ + this.name = name; + this.options = []; + this.owner = owner; + this.ltr = ltr; + } + removeAll(){ + while(this.options.length){ + this.options.pop(); + } + const container = this.container.deref(); + if(container){ + container.innerHTML = ""; + } + } + watchForChange(){} + addOptions(name: string, { ltr = false } = {}){ + const options = new Options(name, this, { ltr }); + this.options.push(options); + this.generate(options); + return options; + } + subOptions: Options | Form | undefined; + addSubOptions(name: string, { ltr = false } = {}){ + const options = new Options(name, this, { ltr }); + this.subOptions = options; + const container = this.container.deref(); + if(container){ + this.generateContainter(); + }else{ + throw new Error( + "Tried to make a subOptions when the options weren't rendered" + ); + } + return options; + } + addSubForm( + name: string, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ){ + const options = new Form(name, this, onSubmit, { + ltr, + submitText, + fetchURL, + headers, + method, + traditionalSubmit, + }); + this.subOptions = options; + const container = this.container.deref(); + if(container){ + this.generateContainter(); + }else{ + throw new Error( + "Tried to make a subForm when the options weren't rendered" + ); + } + return options; + } + returnFromSub(){ + this.subOptions = undefined; + this.generateContainter(); + } + addSelect( + label: string, + onSubmit: (str: number) => void, + selections: string[], + { defaultIndex = 0 } = {} + ){ + const select = new SelectInput(label, onSubmit, selections, this, { + defaultIndex, + }); + this.options.push(select); + this.generate(select); + return select; + } + addFileInput( + label: string, + onSubmit: (files: FileList | null) => void, + { clear = false } = {} + ){ + const FI = new FileInput(label, onSubmit, this, { clear }); + this.options.push(FI); + this.generate(FI); + return FI; + } + addTextInput( + label: string, + onSubmit: (str: string) => void, + { initText = "", password = false } = {} + ){ + const textInput = new TextInput(label, onSubmit, this, { + initText, + password, + }); + this.options.push(textInput); + this.generate(textInput); + return textInput; + } + addColorInput( + label: string, + onSubmit: (str: string) => void, + { initColor = "" } = {} + ){ + const colorInput = new ColorInput(label, onSubmit, this, { initColor }); + this.options.push(colorInput); + this.generate(colorInput); + return colorInput; + } + addMDInput( + label: string, + onSubmit: (str: string) => void, + { initText = "" } = {} + ){ + const mdInput = new MDInput(label, onSubmit, this, { initText }); + this.options.push(mdInput); + this.generate(mdInput); + return mdInput; + } + addHTMLArea( + html: (() => HTMLElement) | HTMLElement, + submit: () => void = ()=>{} + ){ + const htmlarea = new HtmlArea(html, submit); + this.options.push(htmlarea); + this.generate(htmlarea); + return htmlarea; + } + addButtonInput(label: string, textContent: string, onSubmit: () => void){ + const button = new ButtonInput(label, textContent, onSubmit, this); + this.options.push(button); + this.generate(button); + return button; + } + addCheckboxInput( + label: string, + onSubmit: (str: boolean) => void, + { initState = false } = {} + ){ + const box = new CheckboxInput(label, onSubmit, this, { initState }); + this.options.push(box); + this.generate(box); + return box; + } + addText(str: string){ + const text = new SettingsText(str); + this.options.push(text); + this.generate(text); + return text; + } + addTitle(str: string){ + const text = new SettingsTitle(str); + this.options.push(text); + this.generate(text); + return text; + } + addForm( + name: string, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ){ + const options = new Form(name, this, onSubmit, { + ltr, + submitText, + fetchURL, + headers, + method, + traditionalSubmit, + }); + this.options.push(options); + this.generate(options); + return options; + } + generate(elm: OptionsElement){ + const container = this.container.deref(); + if(container){ + const div = document.createElement("div"); + if(!(elm instanceof Options)){ + div.classList.add("optionElement"); + } + const html = elm.generateHTML(); + div.append(html); + this.html.set(elm, new WeakRef(div)); + container.append(div); + } + } + title: WeakRef = new WeakRef(document.createElement("h2")); + generateHTML(): HTMLElement{ + const div = document.createElement("div"); + div.classList.add("titlediv"); + const title = document.createElement("h2"); + title.textContent = this.name; + div.append(title); + if(this.name !== "") title.classList.add("settingstitle"); + this.title = new WeakRef(title); + const container = document.createElement("div"); + this.container = new WeakRef(container); + container.classList.add(this.ltr ? "flexltr" : "flexttb", "flexspace"); + this.generateContainter(); + div.append(container); + return div; + } + generateContainter(){ + const container = this.container.deref(); + if(container){ + const title = this.title.deref(); + if(title) title.innerHTML = ""; + container.innerHTML = ""; + if(this.subOptions){ + container.append(this.subOptions.generateHTML()); //more code needed, though this is enough for now + if(title){ + const name = document.createElement("span"); + name.innerText = this.name; + name.classList.add("clickable"); + name.onclick = ()=>{ + this.returnFromSub(); + }; + title.append(name, " > ", this.subOptions.name); + } + }else{ + for(const thing of this.options){ + this.generate(thing); + } + if(title){ + title.innerText = this.name; + } + } + if(title && title.innerText !== ""){ + title.classList.add("settingstitle"); + }else if(title){ + title.classList.remove("settingstitle"); + } + }else{ + console.warn("tried to generate container, but it did not exist"); + } + } + changed(){ + if(this.owner instanceof Options || this.owner instanceof Form){ + this.owner.changed(); + return; + } + if(!this.haschanged){ + const div = document.createElement("div"); + div.classList.add("flexltr", "savediv"); + const span = document.createElement("span"); + div.append(span); + span.textContent = "Careful, you have unsaved changes"; + const button = document.createElement("button"); + button.textContent = "Save changes"; + div.append(button); + this.haschanged = true; + this.owner.changed(div); + + button.onclick = _=>{ + if(this.owner instanceof Buttons){ + this.owner.save(); + } + div.remove(); + this.submit(); + }; + } + } + submit(){ + this.haschanged = false; + for(const thing of this.options){ + thing.submit(); + } + } +} +class FormError extends Error{ + elem: OptionsElement; + message: string; + constructor(elem: OptionsElement, message: string){ + super(message); + this.message = message; + this.elem = elem; + } +} +export{ FormError }; +class Form implements OptionsElement{ + name: string; + readonly options: Options; + readonly owner: Options; + readonly ltr: boolean; + readonly names: Map> = new Map(); + readonly required: WeakSet> = new WeakSet(); + readonly submitText: string; + readonly fetchURL: string; + readonly headers = {}; + readonly method: string; + value!: object; + traditionalSubmit: boolean; + values: { [key: string]: any } = {}; + constructor( + name: string, + owner: Options, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ){ + this.traditionalSubmit = traditionalSubmit; + this.name = name; + this.method = method; + this.submitText = submitText; + this.options = new Options("", this, { ltr }); + this.owner = owner; + this.fetchURL = fetchURL; + this.headers = headers; + this.ltr = ltr; + this.onSubmit = onSubmit; + } + setValue(key: string, value: any){ + //the value can't really be anything, but I don't care enough to fix this + this.values[key] = value; + } + addSelect( + label: string, + formName: string, + selections: string[], + { defaultIndex = 0, required = false } = {} + ){ + const select = this.options.addSelect(label, _=>{}, selections, { + defaultIndex, + }); + this.names.set(formName, select); + if(required){ + this.required.add(select); + } + return select; + } + readonly fileOptions: Map = new Map(); + addFileInput( + label: string, + formName: string, + { required = false, files = "one", clear = false } = {} + ){ + const FI = this.options.addFileInput(label, _=>{}, { clear }); + if(files !== "one" && files !== "multi") + throw new Error("files should equal one or multi"); + this.fileOptions.set(FI, { files }); + this.names.set(formName, FI); + if(required){ + this.required.add(FI); + } + return FI; + } + + addTextInput( + label: string, + formName: string, + { initText = "", required = false, password = false } = {} + ){ + const textInput = this.options.addTextInput(label, _=>{}, { + initText, + password, + }); + this.names.set(formName, textInput); + if(required){ + this.required.add(textInput); + } + return textInput; + } + addColorInput( + label: string, + formName: string, + { initColor = "", required = false } = {} + ){ + const colorInput = this.options.addColorInput(label, _=>{}, { + initColor, + }); + this.names.set(formName, colorInput); + if(required){ + this.required.add(colorInput); + } + return colorInput; + } + + addMDInput( + label: string, + formName: string, + { initText = "", required = false } = {} + ){ + const mdInput = this.options.addMDInput(label, _=>{}, { initText }); + this.names.set(formName, mdInput); + if(required){ + this.required.add(mdInput); + } + return mdInput; + } + + addCheckboxInput( + label: string, + formName: string, + { initState = false, required = false } = {} + ){ + const box = this.options.addCheckboxInput(label, _=>{}, { initState }); + this.names.set(formName, box); + if(required){ + this.required.add(box); + } + return box; + } + addText(str: string){ + this.options.addText(str); + } + addTitle(str: string){ + this.options.addTitle(str); + } + generateHTML(): HTMLElement{ + const div = document.createElement("div"); + div.append(this.options.generateHTML()); + div.classList.add("FormSettings"); + if(!this.traditionalSubmit){ + const button = document.createElement("button"); + button.onclick = _=>{ + this.submit(); + }; + button.textContent = this.submitText; + div.append(button); + } + return div; + } + onSubmit: (arg1: object) => void; + watchForChange(func: (arg1: object) => void){ + this.onSubmit = func; + } + changed(){ + if(this.traditionalSubmit){ + this.owner.changed(); + } + } + async submit(){ + const build = {}; + for(const key of Object.keys(this.values)){ + const thing = this.values[key]; + if(thing instanceof Function){ + try{ + (build as any)[key] = thing(); + }catch(e: any){ + if(e instanceof FormError){ + const elm = this.options.html.get(e.elem); + if(elm){ + const html = elm.deref(); + if(html){ + this.makeError(html, e.message); + } } - generateHTML(): HTMLSpanElement { - const span = document.createElement("h2"); - span.innerText = this.text; - return span; - } - watchForChange() {} - submit() {} - } - class CheckboxInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: boolean) => void; - value: boolean; - input!: WeakRef; - constructor( - label: string, - onSubmit: (str: boolean) => void, - owner: Options, - { initState = false } = {} - ) { - this.label = label; - this.value = initState; - this.owner = owner; - this.onSubmit = onSubmit; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.type = "checkbox"; - input.checked = this.value; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - private onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.checked as boolean; - this.onchange(value); - this.value = value; - } - } - onchange: (str: boolean) => void = (_) => {}; - watchForChange(func: (str: boolean) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.value); - } - } - - class ButtonInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onClick: () => void; - textContent: string; - value!: void; - constructor( - label: string, - textContent: string, - onClick: () => void, - owner: Options, - {} = {} - ) { - this.label = label; - this.owner = owner; - this.onClick = onClick; - this.textContent = textContent; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const button = document.createElement("button"); - button.textContent = this.textContent; - button.onclick = this.onClickEvent.bind(this); - div.append(button); - return div; - } - private onClickEvent() { - this.onClick(); - } - watchForChange() {} - submit() {} - } - - class ColorInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: string) => void; - colorContent: string; - input!: WeakRef; - value!: string; - constructor( - label: string, - onSubmit: (str: string) => void, - owner: Options, - { initColor = "" } = {} - ) { - this.label = label; - this.colorContent = initColor; - this.owner = owner; - this.onSubmit = onSubmit; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.value = this.colorContent; - input.type = "color"; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - private onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.value as string; - this.value = value; - this.onchange(value); - this.colorContent = value; - } - } - onchange: (str: string) => void = (_) => {}; - watchForChange(func: (str: string) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.colorContent); - } - } - - class SelectInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: number) => void; - options: string[]; - index: number; - select!: WeakRef; - get value() { - return this.index; - } - constructor( - label: string, - onSubmit: (str: number) => void, - options: string[], - owner: Options, - { defaultIndex = 0 } = {} - ) { - this.label = label; - this.index = defaultIndex; - this.owner = owner; - this.onSubmit = onSubmit; - this.options = options; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const select = document.createElement("select"); - - select.onchange = this.onChange.bind(this); - for (const thing of this.options) { - const option = document.createElement("option"); - option.textContent = thing; - select.appendChild(option); - } - this.select = new WeakRef(select); - select.selectedIndex = this.index; - div.append(select); - return div; - } - private onChange() { - this.owner.changed(); - const select = this.select.deref(); - if (select) { - const value = select.selectedIndex; - this.onchange(value); - this.index = value; - } - } - onchange: (str: number) => void = (_) => {}; - watchForChange(func: (str: number) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.index); - } - } - class MDInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: string) => void; - value: string; - input!: WeakRef; - constructor( - label: string, - onSubmit: (str: string) => void, - owner: Options, - { initText = "" } = {} - ) { - this.label = label; - this.value = initText; - this.owner = owner; - this.onSubmit = onSubmit; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - div.append(document.createElement("br")); - const input = document.createElement("textarea"); - input.value = this.value; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.value as string; - this.onchange(value); - this.value = value; - } - } - onchange: (str: string) => void = (_) => {}; - watchForChange(func: (str: string) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.value); - } - } - class FileInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: FileList | null) => void; - input!: WeakRef; - value!: FileList | null; - clear: boolean; - constructor( - label: string, - onSubmit: (str: FileList | null) => void, - owner: Options, - { clear = false } = {} - ) { - this.label = label; - this.owner = owner; - this.onSubmit = onSubmit; - this.clear = clear; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.type = "file"; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - if (this.clear) { - const button = document.createElement("button"); - button.textContent = "Clear"; - button.onclick = (_) => { - if (this.onchange) { - this.onchange(null); - } - this.value = null; - this.owner.changed(); - }; - div.append(button); - } - return div; - } - onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - this.value = input.files; - if (this.onchange) { - this.onchange(input.files); - } - } - } - onchange: ((str: FileList | null) => void) | null = null; - watchForChange(func: (str: FileList | null) => void) { - this.onchange = func; - } - submit() { - const input = this.input.deref(); - if (input) { - this.onSubmit(input.files); - } - } - } - - class HtmlArea implements OptionsElement { - submit: () => void; - html: (() => HTMLElement) | HTMLElement; - value!: void; - constructor(html: (() => HTMLElement) | HTMLElement, submit: () => void) { - this.submit = submit; - this.html = html; - } - generateHTML(): HTMLElement { - if (this.html instanceof Function) { - return this.html(); - } else { - return this.html; - } - } - watchForChange() {} - } - class Options implements OptionsElement { - name: string; - haschanged = false; - readonly options: OptionsElement[]; - readonly owner: Buttons | Options | Form; - readonly ltr: boolean; - value!: void; - readonly html: WeakMap, WeakRef> = - new WeakMap(); - container: WeakRef = new WeakRef( - document.createElement("div") - ); - constructor( - name: string, - owner: Buttons | Options | Form, - { ltr = false } = {} - ) { - this.name = name; - this.options = []; - this.owner = owner; - this.ltr = ltr; - } - removeAll() { - while (this.options.length) { - this.options.pop(); - } - const container = this.container.deref(); - if (container) { - container.innerHTML = ""; - } - } - watchForChange() {} - addOptions(name: string, { ltr = false } = {}) { - const options = new Options(name, this, { ltr }); - this.options.push(options); - this.generate(options); - return options; - } - subOptions: Options | Form | undefined; - addSubOptions(name: string, { ltr = false } = {}) { - const options = new Options(name, this, { ltr }); - this.subOptions = options; - const container = this.container.deref(); - if (container) { - this.generateContainter(); - } else { - throw new Error( - "Tried to make a subOptions when the options weren't rendered" - ); - } - return options; - } - addSubForm( - name: string, - onSubmit: (arg1: object) => void, - { - ltr = false, - submitText = "Submit", - fetchURL = "", - headers = {}, - method = "POST", - traditionalSubmit = false, - } = {} - ) { - const options = new Form(name, this, onSubmit, { - ltr, - submitText, - fetchURL, - headers, - method, - traditionalSubmit, - }); - this.subOptions = options; - const container = this.container.deref(); - if (container) { - this.generateContainter(); - } else { - throw new Error( - "Tried to make a subForm when the options weren't rendered" - ); - } - return options; - } - returnFromSub() { - this.subOptions = undefined; - this.generateContainter(); - } - addSelect( - label: string, - onSubmit: (str: number) => void, - selections: string[], - { defaultIndex = 0 } = {} - ) { - const select = new SelectInput(label, onSubmit, selections, this, { - defaultIndex, - }); - this.options.push(select); - this.generate(select); - return select; - } - addFileInput( - label: string, - onSubmit: (files: FileList | null) => void, - { clear = false } = {} - ) { - const FI = new FileInput(label, onSubmit, this, { clear }); - this.options.push(FI); - this.generate(FI); - return FI; - } - addTextInput( - label: string, - onSubmit: (str: string) => void, - { initText = "", password = false } = {} - ) { - const textInput = new TextInput(label, onSubmit, this, { - initText, - password, - }); - this.options.push(textInput); - this.generate(textInput); - return textInput; - } - addColorInput( - label: string, - onSubmit: (str: string) => void, - { initColor = "" } = {} - ) { - const colorInput = new ColorInput(label, onSubmit, this, { initColor }); - this.options.push(colorInput); - this.generate(colorInput); - return colorInput; - } - addMDInput( - label: string, - onSubmit: (str: string) => void, - { initText = "" } = {} - ) { - const mdInput = new MDInput(label, onSubmit, this, { initText }); - this.options.push(mdInput); - this.generate(mdInput); - return mdInput; - } - addHTMLArea( - html: (() => HTMLElement) | HTMLElement, - submit: () => void = () => {} - ) { - const htmlarea = new HtmlArea(html, submit); - this.options.push(htmlarea); - this.generate(htmlarea); - return htmlarea; - } - addButtonInput(label: string, textContent: string, onSubmit: () => void) { - const button = new ButtonInput(label, textContent, onSubmit, this); - this.options.push(button); - this.generate(button); - return button; - } - addCheckboxInput( - label: string, - onSubmit: (str: boolean) => void, - { initState = false } = {} - ) { - const box = new CheckboxInput(label, onSubmit, this, { initState }); - this.options.push(box); - this.generate(box); - return box; - } - addText(str: string) { - const text = new SettingsText(str); - this.options.push(text); - this.generate(text); - return text; - } - addTitle(str: string) { - const text = new SettingsTitle(str); - this.options.push(text); - this.generate(text); - return text; - } - addForm( - name: string, - onSubmit: (arg1: object) => void, - { - ltr = false, - submitText = "Submit", - fetchURL = "", - headers = {}, - method = "POST", - traditionalSubmit = false, - } = {} - ) { - const options = new Form(name, this, onSubmit, { - ltr, - submitText, - fetchURL, - headers, - method, - traditionalSubmit, - }); - this.options.push(options); - this.generate(options); - return options; - } - generate(elm: OptionsElement) { - const container = this.container.deref(); - if (container) { - const div = document.createElement("div"); - if (!(elm instanceof Options)) { - div.classList.add("optionElement"); - } - const html = elm.generateHTML(); - div.append(html); - this.html.set(elm, new WeakRef(div)); - container.append(div); - } - } - title: WeakRef = new WeakRef(document.createElement("h2")); - generateHTML(): HTMLElement { - const div = document.createElement("div"); - div.classList.add("titlediv"); - const title = document.createElement("h2"); - title.textContent = this.name; - div.append(title); - if (this.name !== "") title.classList.add("settingstitle"); - this.title = new WeakRef(title); - const container = document.createElement("div"); - this.container = new WeakRef(container); - container.classList.add(this.ltr ? "flexltr" : "flexttb", "flexspace"); - this.generateContainter(); - div.append(container); - return div; - } - generateContainter() { - const container = this.container.deref(); - if (container) { - const title = this.title.deref(); - if (title) title.innerHTML = ""; - container.innerHTML = ""; - if (this.subOptions) { - container.append(this.subOptions.generateHTML()); //more code needed, though this is enough for now - if (title) { - const name = document.createElement("span"); - name.innerText = this.name; - name.classList.add("clickable"); - name.onclick = () => { - this.returnFromSub(); - }; - title.append(name, " > ", this.subOptions.name); - } - } else { - for (const thing of this.options) { - this.generate(thing); - } - if (title) { - title.innerText = this.name; - } - } - if (title && title.innerText !== "") { - title.classList.add("settingstitle"); - } else if (title) { - title.classList.remove("settingstitle"); - } - } else { - console.warn("tried to generate container, but it did not exist"); - } - } - changed() { - if (this.owner instanceof Options || this.owner instanceof Form) { - this.owner.changed(); - return; - } - if (!this.haschanged) { - const div = document.createElement("div"); - div.classList.add("flexltr", "savediv"); - const span = document.createElement("span"); - div.append(span); - span.textContent = "Careful, you have unsaved changes"; - const button = document.createElement("button"); - button.textContent = "Save changes"; - div.append(button); - this.haschanged = true; - this.owner.changed(div); - - button.onclick = (_) => { - if (this.owner instanceof Buttons) { - this.owner.save(); - } - div.remove(); - this.submit(); - }; - } - } - submit() { - this.haschanged = false; - for (const thing of this.options) { - thing.submit(); - } - } - } - class FormError extends Error { - elem: OptionsElement; - message: string; - constructor(elem: OptionsElement, message: string) { - super(message); - this.message = message; - this.elem = elem; - } - } - export { FormError }; - class Form implements OptionsElement { - name: string; - readonly options: Options; - readonly owner: Options; - readonly ltr: boolean; - readonly names: Map> = new Map(); - readonly required: WeakSet> = new WeakSet(); - readonly submitText: string; - readonly fetchURL: string; - readonly headers = {}; - readonly method: string; - value!: object; - traditionalSubmit: boolean; - values: { [key: string]: any } = {}; - constructor( - name: string, - owner: Options, - onSubmit: (arg1: object) => void, - { - ltr = false, - submitText = "Submit", - fetchURL = "", - headers = {}, - method = "POST", - traditionalSubmit = false, - } = {} - ) { - this.traditionalSubmit = traditionalSubmit; - this.name = name; - this.method = method; - this.submitText = submitText; - this.options = new Options("", this, { ltr }); - this.owner = owner; - this.fetchURL = fetchURL; - this.headers = headers; - this.ltr = ltr; - this.onSubmit = onSubmit; - } - setValue(key: string, value: any) { - //the value can't really be anything, but I don't care enough to fix this - this.values[key] = value; - } - addSelect( - label: string, - formName: string, - selections: string[], - { defaultIndex = 0, required = false } = {} - ) { - const select = this.options.addSelect(label, (_) => {}, selections, { - defaultIndex, - }); - this.names.set(formName, select); - if (required) { - this.required.add(select); - } - return select; - } - readonly fileOptions: Map = new Map(); - addFileInput( - label: string, - formName: string, - { required = false, files = "one", clear = false } = {} - ) { - const FI = this.options.addFileInput(label, (_) => {}, { clear }); - if (files !== "one" && files !== "multi") - throw new Error("files should equal one or multi"); - this.fileOptions.set(FI, { files }); - this.names.set(formName, FI); - if (required) { - this.required.add(FI); - } - return FI; - } - - addTextInput( - label: string, - formName: string, - { initText = "", required = false, password = false } = {} - ) { - const textInput = this.options.addTextInput(label, (_) => {}, { - initText, - password, - }); - this.names.set(formName, textInput); - if (required) { - this.required.add(textInput); - } - return textInput; - } - addColorInput( - label: string, - formName: string, - { initColor = "", required = false } = {} - ) { - const colorInput = this.options.addColorInput(label, (_) => {}, { - initColor, - }); - this.names.set(formName, colorInput); - if (required) { - this.required.add(colorInput); - } - return colorInput; - } - - addMDInput( - label: string, - formName: string, - { initText = "", required = false } = {} - ) { - const mdInput = this.options.addMDInput(label, (_) => {}, { initText }); - this.names.set(formName, mdInput); - if (required) { - this.required.add(mdInput); - } - return mdInput; - } - - addCheckboxInput( - label: string, - formName: string, - { initState = false, required = false } = {} - ) { - const box = this.options.addCheckboxInput(label, (_) => {}, { initState }); - this.names.set(formName, box); - if (required) { - this.required.add(box); - } - return box; - } - addText(str: string) { - this.options.addText(str); - } - addTitle(str: string) { - this.options.addTitle(str); - } - generateHTML(): HTMLElement { - const div = document.createElement("div"); - div.append(this.options.generateHTML()); - div.classList.add("FormSettings"); - if (!this.traditionalSubmit) { - const button = document.createElement("button"); - button.onclick = (_) => { - this.submit(); - }; - button.textContent = this.submitText; - div.append(button); - } - return div; - } - onSubmit: (arg1: object) => void; - watchForChange(func: (arg1: object) => void) { - this.onSubmit = func; - } - changed() { - if (this.traditionalSubmit) { - this.owner.changed(); - } - } - async submit() { - const build = {}; - for (const key of Object.keys(this.values)) { - const thing = this.values[key]; - if (thing instanceof Function) { - try { - (build as any)[key] = thing(); - } catch (e: any) { - if (e instanceof FormError) { - const elm = this.options.html.get(e.elem); - if (elm) { - const html = elm.deref(); - if (html) { - this.makeError(html, e.message); - } - } - } - return; - } - } else { - (build as any)[thing] = thing; - } - } - const promises: Promise[] = []; - for (const thing of this.names.keys()) { - if (thing === "") continue; - const input = this.names.get(thing) as OptionsElement; - if (input instanceof SelectInput) { - (build as any)[thing] = input.options[input.value]; - continue; - } else if (input instanceof FileInput) { - const options = this.fileOptions.get(input); - if (!options) { - throw new Error( - "FileInput without its options is in this form, this should never happen." - ); - } - if (options.files === "one") { - console.log(input.value); - if (input.value) { - const reader = new FileReader(); - reader.readAsDataURL(input.value[0]); - const promise = new Promise((res) => { - reader.onload = () => { - (build as any)[thing] = reader.result; - res(); - }; - }); - promises.push(promise); - } - } else { - console.error(options.files + " is not currently implemented"); - } - } - (build as any)[thing] = input.value; - } - await Promise.allSettled(promises); - if (this.fetchURL !== "") { - fetch(this.fetchURL, { - method: this.method, - body: JSON.stringify(build), - headers: this.headers, - }) - .then((_) => _.json()) - .then((json) => { - if (json.errors && this.errors(json.errors)) return; - this.onSubmit(json); - }); - } else { - this.onSubmit(build); - } - console.warn("needs to be implemented"); - } - errors(errors: { + } + return; + } + }else{ + (build as any)[thing] = thing; + } + } + const promises: Promise[] = []; + for(const thing of this.names.keys()){ + if(thing === "")continue; + const input = this.names.get(thing) as OptionsElement; + if(input instanceof SelectInput){ + (build as any)[thing] = input.options[input.value]; + continue; + }else if(input instanceof FileInput){ + const options = this.fileOptions.get(input); + if(!options){ + throw new Error( + "FileInput without its options is in this form, this should never happen." + ); + } + if(options.files === "one"){ + console.log(input.value); + if(input.value){ + const reader = new FileReader(); + reader.readAsDataURL(input.value[0]); + const promise = new Promise(res=>{ + reader.onload = ()=>{ + (build as any)[thing] = reader.result; + res(); + }; + }); + promises.push(promise); + } + }else{ + console.error(options.files + " is not currently implemented"); + } + } + (build as any)[thing] = input.value; + } + await Promise.allSettled(promises); + if(this.fetchURL !== ""){ + fetch(this.fetchURL, { + method: this.method, + body: JSON.stringify(build), + headers: this.headers, + }) + .then(_=>_.json()) + .then(json=>{ + if(json.errors && this.errors(json.errors))return; + this.onSubmit(json); + }); + }else{ + this.onSubmit(build); + } + console.warn("needs to be implemented"); + } + errors(errors: { code: number; message: string; errors: { [key: string]: { _errors: { message: string; code: string } } }; - }) { - if (!(errors instanceof Object)) { - return; - } - for (const error of Object.keys(errors)) { - const elm = this.names.get(error); - if (elm) { - const ref = this.options.html.get(elm); - if (ref && ref.deref()) { - const html = ref.deref() as HTMLDivElement; - this.makeError(html, errors["errors"][error]._errors.message); - return true; - } - } - } - return false; - } - error(formElm: string, errorMessage: string) { - const elm = this.names.get(formElm); - if (elm) { - const htmlref = this.options.html.get(elm); - if (htmlref) { - const html = htmlref.deref(); - if (html) { - this.makeError(html, errorMessage); - } - } - } else { - console.warn(formElm + " is not a valid form property"); - } - } - makeError(e: HTMLDivElement, message: string) { - let element = e.getElementsByClassName("suberror")[0] as HTMLElement; - if (!element) { - const div = document.createElement("div"); - div.classList.add("suberror", "suberrora"); - e.append(div); - element = div; - } else { - element.classList.remove("suberror"); - setTimeout((_) => { - element.classList.add("suberror"); - }, 100); - } - element.textContent = message; - } - } - class Settings extends Buttons { - static readonly Buttons = Buttons; - static readonly Options = Options; - html!: HTMLElement | null; - constructor(name: string) { - super(name); - } - addButton(name: string, { ltr = false } = {}): Options { - const options = new Options(name, this, { ltr }); - this.add(name, options); - return options; - } - show() { - const background = document.createElement("div"); - background.classList.add("background"); + }){ + if(!(errors instanceof Object)){ + return; + } + for(const error of Object.keys(errors)){ + const elm = this.names.get(error); + if(elm){ + const ref = this.options.html.get(elm); + if(ref && ref.deref()){ + const html = ref.deref() as HTMLDivElement; + this.makeError(html, errors.errors[error]._errors.message); + return true; + } + } + } + return false; + } + error(formElm: string, errorMessage: string){ + const elm = this.names.get(formElm); + if(elm){ + const htmlref = this.options.html.get(elm); + if(htmlref){ + const html = htmlref.deref(); + if(html){ + this.makeError(html, errorMessage); + } + } + }else{ + console.warn(formElm + " is not a valid form property"); + } + } + makeError(e: HTMLDivElement, message: string){ + let element = e.getElementsByClassName("suberror")[0] as HTMLElement; + if(!element){ + const div = document.createElement("div"); + div.classList.add("suberror", "suberrora"); + e.append(div); + element = div; + }else{ + element.classList.remove("suberror"); + setTimeout(_=>{ + element.classList.add("suberror"); + }, 100); + } + element.textContent = message; + } +} +class Settings extends Buttons{ + static readonly Buttons = Buttons; + static readonly Options = Options; + html!: HTMLElement | null; + constructor(name: string){ + super(name); + } + addButton(name: string, { ltr = false } = {}): Options{ + const options = new Options(name, this, { ltr }); + this.add(name, options); + return options; + } + show(){ + const background = document.createElement("div"); + background.classList.add("background"); - const title = document.createElement("h2"); - title.textContent = this.name; - title.classList.add("settingstitle"); - background.append(title); + const title = document.createElement("h2"); + title.textContent = this.name; + title.classList.add("settingstitle"); + background.append(title); - background.append(this.generateHTML()); + background.append(this.generateHTML()); - const exit = document.createElement("span"); - exit.textContent = "✖"; - exit.classList.add("exitsettings"); - background.append(exit); - exit.onclick = (_) => { - this.hide(); - }; - document.body.append(background); - this.html = background; - } - hide() { - if (this.html) { - this.html.remove(); - this.html = null; - } - } - } + const exit = document.createElement("span"); + exit.textContent = "✖"; + exit.classList.add("exitsettings"); + background.append(exit); + exit.onclick = _=>{ + this.hide(); + }; + document.body.append(background); + this.html = background; + } + hide(){ + if(this.html){ + this.html.remove(); + this.html = null; + } + } +} - export { Settings, OptionsElement, Buttons, Options }; +export{ Settings, OptionsElement, Buttons, Options }; diff --git a/src/webpage/snowflake.ts b/src/webpage/snowflake.ts index 8e23a81..e6a4603 100644 --- a/src/webpage/snowflake.ts +++ b/src/webpage/snowflake.ts @@ -1,20 +1,20 @@ -abstract class SnowFlake { -public readonly id: string; -constructor(id: string) { -this.id = id; +abstract class SnowFlake{ + public readonly id: string; + constructor(id: string){ + this.id = id; + } + getUnixTime(): number{ + return SnowFlake.stringToUnixTime(this.id); + } + static stringToUnixTime(str: string){ + try{ + return Number((BigInt(str) >> 22n) + 1420070400000n); + }catch{ + console.error( + `The ID is corrupted, it's ${str} when it should be some number.` + ); + return 0; + } + } } -getUnixTime(): number { -return SnowFlake.stringToUnixTime(this.id); -} -static stringToUnixTime(str: string) { -try { -return Number((BigInt(str) >> 22n) + 1420070400000n); -} catch { -console.error( -`The ID is corrupted, it's ${str} when it should be some number.` -); -return 0; -} -} -} -export { SnowFlake }; +export{ SnowFlake }; diff --git a/src/webpage/user.ts b/src/webpage/user.ts index 8197f1d..2f06f4c 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -1,489 +1,489 @@ -import { Member } from "./member.js"; -import { MarkDown } from "./markdown.js"; -import { Contextmenu } from "./contextmenu.js"; -import { Localuser } from "./localuser.js"; -import { Guild } from "./guild.js"; -import { SnowFlake } from "./snowflake.js"; -import { presencejson, userjson } from "./jsontypes.js"; +import{ Member }from"./member.js"; +import{ MarkDown }from"./markdown.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ Localuser }from"./localuser.js"; +import{ Guild }from"./guild.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ presencejson, userjson }from"./jsontypes.js"; -class User extends SnowFlake { -owner: Localuser; -hypotheticalpfp!: boolean; -avatar!: string | null; -username!: string; -nickname: string | null = null; -relationshipType: 0 | 1 | 2 | 3 | 4 = 0; -bio!: MarkDown; -discriminator!: string; -pronouns!: string; -bot!: boolean; -public_flags!: number; -accent_color!: number; -banner: string | undefined; -hypotheticalbanner!: boolean; -premium_since!: string; -premium_type!: number; -theme_colors!: string; -badge_ids!: string[]; -members: WeakMap> = - new WeakMap(); +class User extends SnowFlake{ + owner: Localuser; + hypotheticalpfp!: boolean; + avatar!: string | null; + username!: string; + nickname: string | null = null; + relationshipType: 0 | 1 | 2 | 3 | 4 = 0; + bio!: MarkDown; + discriminator!: string; + pronouns!: string; + bot!: boolean; + public_flags!: number; + accent_color!: number; + banner: string | undefined; + hypotheticalbanner!: boolean; + premium_since!: string; + premium_type!: number; + theme_colors!: string; + badge_ids!: string[]; + members: WeakMap> = + new WeakMap(); private status!: string; resolving: false | Promise = false; - constructor(userjson: userjson, owner: Localuser, dontclone = false) { + constructor(userjson: userjson, owner: Localuser, dontclone = false){ super(userjson.id); this.owner = owner; - if (!owner) { - console.error("missing localuser"); + if(!owner){ + console.error("missing localuser"); } - if (dontclone) { - for (const key of Object.keys(userjson)) { - if (key === "bio") { - this.bio = new MarkDown(userjson[key], this.localuser); - continue; - } - if (key === "id") { - continue; - } - (this as any)[key] = (userjson as any)[key]; - } - this.hypotheticalpfp = false; - } else { - return User.checkuser(userjson, owner); - } - } - - clone(): User { - return new User( - { - username: this.username, - id: this.id + "#clone", - public_flags: this.public_flags, - discriminator: this.discriminator, - avatar: this.avatar, - accent_color: this.accent_color, - banner: this.banner, - bio: this.bio.rawString, - premium_since: this.premium_since, - premium_type: this.premium_type, - bot: this.bot, - theme_colors: this.theme_colors, - pronouns: this.pronouns, - badge_ids: this.badge_ids, - }, - this.owner - ); - } - - public getPresence(presence: presencejson | undefined): void { - if (presence) { - this.setstatus(presence.status); - } else { - this.setstatus("offline"); - } - } - - setstatus(status: string): void { - this.status = status; - } - - async getStatus(): Promise { - return this.status || "offline"; + if(dontclone){ + for(const key of Object.keys(userjson)){ + if(key === "bio"){ + this.bio = new MarkDown(userjson[key], this.localuser); + continue; + } + if(key === "id"){ + continue; + } + (this as any)[key] = (userjson as any)[key]; } + this.hypotheticalpfp = false; + }else{ + return User.checkuser(userjson, owner); + } + } - static contextmenu = new Contextmenu("User Menu"); + clone(): User{ + return new User( + { + username: this.username, + id: this.id + "#clone", + public_flags: this.public_flags, + discriminator: this.discriminator, + avatar: this.avatar, + accent_color: this.accent_color, + banner: this.banner, + bio: this.bio.rawString, + premium_since: this.premium_since, + premium_type: this.premium_type, + bot: this.bot, + theme_colors: this.theme_colors, + pronouns: this.pronouns, + badge_ids: this.badge_ids, + }, + this.owner + ); + } - static setUpContextMenu(): void { - this.contextmenu.addbutton("Copy user id", function (this: User) { - navigator.clipboard.writeText(this.id); - }); - this.contextmenu.addbutton("Message user", function (this: User) { - fetch(this.info.api + "/users/@me/channels", { + public getPresence(presence: presencejson | undefined): void{ + if(presence){ + this.setstatus(presence.status); + }else{ + this.setstatus("offline"); + } + } + + setstatus(status: string): void{ + this.status = status; + } + + async getStatus(): Promise{ + return this.status || "offline"; + } + + static contextmenu = new Contextmenu("User Menu"); + + static setUpContextMenu(): void{ + this.contextmenu.addbutton("Copy user id", function(this: User){ + navigator.clipboard.writeText(this.id); + }); + this.contextmenu.addbutton("Message user", function(this: User){ + fetch(this.info.api + "/users/@me/channels", { method: "POST", body: JSON.stringify({ recipients: [this.id] }), headers: this.localuser.headers, - }) - .then((res) => res.json()) - .then((json) => { - this.localuser.goToChannel(json.id); + }) + .then(res=>res.json()) + .then(json=>{ + this.localuser.goToChannel(json.id); }); - }); - this.contextmenu.addbutton( - "Block user", - function (this: User) { + }); + this.contextmenu.addbutton( + "Block user", + function(this: User){ this.block(); - }, - null, - function () { + }, + null, + function(){ return this.relationshipType !== 2; - } - ); + } + ); - this.contextmenu.addbutton( - "Unblock user", - function (this: User) { + this.contextmenu.addbutton( + "Unblock user", + function(this: User){ this.unblock(); - }, - null, - function () { + }, + null, + function(){ return this.relationshipType === 2; - } - ); - this.contextmenu.addbutton("Friend request", function (this: User) { - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + } + ); + this.contextmenu.addbutton("Friend request", function(this: User){ + fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { method: "PUT", headers: this.owner.headers, body: JSON.stringify({ - type: 1, + type: 1, }), - }); - }); - this.contextmenu.addbutton( - "Kick member", - function (this: User, member: Member | undefined) { + }); + }); + this.contextmenu.addbutton( + "Kick member", + (this: User, member: Member | undefined)=>{ member?.kick(); - }, - null, - (member) => { - if (!member) return false; + }, + null, + member=>{ + if(!member)return false; const us = member.guild.member; - if (member.id === us.id) { - return false; + if(member.id === us.id){ + return false; } - if (member.id === member.guild.properties.owner_id) { - return false; + if(member.id === member.guild.properties.owner_id){ + return false; } return us.hasPermission("KICK_MEMBERS") || false; - } - ); - this.contextmenu.addbutton( - "Ban member", - function (this: User, member: Member | undefined) { + } + ); + this.contextmenu.addbutton( + "Ban member", + (this: User, member: Member | undefined)=>{ member?.ban(); - }, - null, - (member) => { - if (!member) return false; + }, + null, + member=>{ + if(!member)return false; const us = member.guild.member; - if (member.id === us.id) { - return false; + if(member.id === us.id){ + return false; } - if (member.id === member.guild.properties.owner_id) { - return false; + if(member.id === member.guild.properties.owner_id){ + return false; } return us.hasPermission("BAN_MEMBERS") || false; - } - ); - } + } + ); + } - static checkuser(user: User | userjson, owner: Localuser): User { - if (owner.userMap.has(user.id)) { - return owner.userMap.get(user.id) as User; - } else { - const tempuser = new User(user as userjson, owner, true); - owner.userMap.set(user.id, tempuser); - return tempuser; - } - } + static checkuser(user: User | userjson, owner: Localuser): User{ + if(owner.userMap.has(user.id)){ + return owner.userMap.get(user.id) as User; + }else{ + const tempuser = new User(user as userjson, owner, true); + owner.userMap.set(user.id, tempuser); + return tempuser; + } + } - get info() { - return this.owner.info; - } + get info(){ + return this.owner.info; + } - get localuser() { - return this.owner; - } + get localuser(){ + return this.owner; + } - get name() { - return this.username; - } + get name(){ + return this.username; + } - async resolvemember(guild: Guild): Promise { - return await Member.resolveMember(this, guild); + async resolvemember(guild: Guild): Promise{ + return await Member.resolveMember(this, guild); + } + + async getUserProfile(): Promise{ + return await fetch( + `${this.info.api}/users/${this.id.replace( + "#clone", + "" + )}/profile?with_mutual_guilds=true&with_mutual_friends=true`, + { + headers: this.localuser.headers, + } + ).then(res=>res.json()); + } + + async getBadge(id: string): Promise{ + if(this.localuser.badges.has(id)){ + return this.localuser.badges.get(id); + }else{ + if(this.resolving){ + await this.resolving; + return this.localuser.badges.get(id); + } + + const prom = await this.getUserProfile(); + this.resolving = prom; + const badges = prom.badges; + this.resolving = false; + for(const badge of badges){ + this.localuser.badges.set(badge.id, badge); + } + return this.localuser.badges.get(id); + } + } + + buildpfp(): HTMLImageElement{ + const pfp = document.createElement("img"); + pfp.loading = "lazy"; + pfp.src = this.getpfpsrc(); + pfp.classList.add("pfp"); + pfp.classList.add("userid:" + this.id); + return pfp; + } + + async buildstatuspfp(): Promise{ + const div = document.createElement("div"); + div.style.position = "relative"; + const pfp = this.buildpfp(); + div.append(pfp); + const status = document.createElement("div"); + status.classList.add("statusDiv"); + switch(await this.getStatus()){ + case"offline": + status.classList.add("offlinestatus"); + break; + case"online": + default: + status.classList.add("onlinestatus"); + break; + } + div.append(status); + return div; + } + + userupdate(json: userjson): void{ + if(json.avatar !== this.avatar){ + this.changepfp(json.avatar); + } + } + + bind(html: HTMLElement, guild: Guild | null = null, error = true): void{ + if(guild && guild.id !== "@me"){ + Member.resolveMember(this, guild) + .then(member=>{ + User.contextmenu.bindContextmenu(html, this, member); + if(member === undefined && error){ + const errorSpan = document.createElement("span"); + errorSpan.textContent = "!"; + errorSpan.classList.add("membererror"); + html.after(errorSpan); + return; } + if(member){ + member.bind(html); + } + }) + .catch(err=>{ + console.log(err); + }); + } + if(guild){ + this.profileclick(html, guild); + }else{ + this.profileclick(html); + } + } - async getUserProfile(): Promise { - return await fetch( - `${this.info.api}/users/${this.id.replace( - "#clone", - "" - )}/profile?with_mutual_guilds=true&with_mutual_friends=true`, - { - headers: this.localuser.headers, - } - ).then((res) => res.json()); - } + static async resolve(id: string, localuser: Localuser): Promise{ + const json = await fetch( + localuser.info.api.toString() + "/users/" + id + "/profile", + { headers: localuser.headers } + ).then(res=>res.json()); + return new User(json, localuser); + } - async getBadge(id: string): Promise { - if (this.localuser.badges.has(id)) { - return this.localuser.badges.get(id); - } else { - if (this.resolving) { - await this.resolving; - return this.localuser.badges.get(id); - } + changepfp(update: string | null): void{ + this.avatar = update; + this.hypotheticalpfp = false; + const src = this.getpfpsrc(); + Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( + element=>{ + (element as HTMLImageElement).src = src; + } + ); + } - const prom = await this.getUserProfile(); - this.resolving = prom; - const badges = prom.badges; - this.resolving = false; - for (const badge of badges) { - this.localuser.badges.set(badge.id, badge); - } - return this.localuser.badges.get(id); - } - } + block(): void{ + fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "PUT", + headers: this.owner.headers, + body: JSON.stringify({ + type: 2, + }), + }); + this.relationshipType = 2; + const channel = this.localuser.channelfocus; + if(channel){ + for(const message of channel.messages){ + message[1].generateMessage(); + } + } + } - buildpfp(): HTMLImageElement { - const pfp = document.createElement("img"); - pfp.loading = "lazy"; - pfp.src = this.getpfpsrc(); - pfp.classList.add("pfp"); - pfp.classList.add("userid:" + this.id); - return pfp; - } + unblock(): void{ + fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "DELETE", + headers: this.owner.headers, + }); + this.relationshipType = 0; + const channel = this.localuser.channelfocus; + if(channel){ + for(const message of channel.messages){ + message[1].generateMessage(); + } + } + } - async buildstatuspfp(): Promise { - const div = document.createElement("div"); - div.style.position = "relative"; - const pfp = this.buildpfp(); - div.append(pfp); - const status = document.createElement("div"); - status.classList.add("statusDiv"); - switch (await this.getStatus()) { - case "offline": - status.classList.add("offlinestatus"); - break; - case "online": - default: - status.classList.add("onlinestatus"); - break; - } - div.append(status); - return div; - } + getpfpsrc(): string{ + if(this.hypotheticalpfp && this.avatar){ + return this.avatar; + } + if(this.avatar !== null){ + return`${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ + this.avatar + }.png`; + }else{ + const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); + return`${this.info.cdn}/embed/avatars/${int}.png`; + } + } - userupdate(json: userjson): void { - if (json.avatar !== this.avatar) { - this.changepfp(json.avatar); - } - } + async buildprofile( + x: number, + y: number, + guild: Guild | null = null + ): Promise{ + if(Contextmenu.currentmenu != ""){ + Contextmenu.currentmenu.remove(); + } - bind(html: HTMLElement, guild: Guild | null = null, error = true): void { - if (guild && guild.id !== "@me") { - Member.resolveMember(this, guild) - .then((member) => { - User.contextmenu.bindContextmenu(html, this, member); - if (member === undefined && error) { - const errorSpan = document.createElement("span"); - errorSpan.textContent = "!"; - errorSpan.classList.add("membererror"); - html.after(errorSpan); - return; - } - if (member) { - member.bind(html); - } - }) - .catch((err) => { - console.log(err); - }); - } - if (guild) { - this.profileclick(html, guild); - } else { - this.profileclick(html); - } - } + const div = document.createElement("div"); - static async resolve(id: string, localuser: Localuser): Promise { - const json = await fetch( - localuser.info.api.toString() + "/users/" + id + "/profile", - { headers: localuser.headers } - ).then((res) => res.json()); - return new User(json, localuser); - } + if(this.accent_color){ + div.style.setProperty( + "--accent_color", + `#${this.accent_color.toString(16).padStart(6, "0")}` + ); + }else{ + div.style.setProperty("--accent_color", "transparent"); + } + if(this.banner){ + const banner = document.createElement("img"); + let src: string; + if(!this.hypotheticalbanner){ + src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ + this.banner + }.png`; + }else{ + src = this.banner; + } + banner.src = src; + banner.classList.add("banner"); + div.append(banner); + } + if(x !== -1){ + div.style.left = `${x}px`; + div.style.top = `${y}px`; + div.classList.add("profile", "flexttb"); + }else{ + this.setstatus("online"); + div.classList.add("hypoprofile", "flexttb"); + } + const badgediv = document.createElement("div"); + badgediv.classList.add("badges"); + (async ()=>{ + if(!this.badge_ids)return; + for(const id of this.badge_ids){ + const badgejson = await this.getBadge(id); + if(badgejson){ + const badge = document.createElement(badgejson.link ? "a" : "div"); + badge.classList.add("badge"); + const img = document.createElement("img"); + img.src = badgejson.icon; + badge.append(img); + const span = document.createElement("span"); + span.textContent = badgejson.description; + badge.append(span); + if(badge instanceof HTMLAnchorElement){ + badge.href = badgejson.link; + } + badgediv.append(badge); + } + } + })(); + const pfp = await this.buildstatuspfp(); + div.appendChild(pfp); + const userbody = document.createElement("div"); + userbody.classList.add("infosection"); + div.appendChild(userbody); + const usernamehtml = document.createElement("h2"); + usernamehtml.textContent = this.username; + userbody.appendChild(usernamehtml); + userbody.appendChild(badgediv); + const discrimatorhtml = document.createElement("h3"); + discrimatorhtml.classList.add("tag"); + discrimatorhtml.textContent = `${this.username}#${this.discriminator}`; + userbody.appendChild(discrimatorhtml); - changepfp(update: string | null): void { - this.avatar = update; - this.hypotheticalpfp = false; - const src = this.getpfpsrc(); - Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( - (element) => { - (element as HTMLImageElement).src = src; - } - ); - } + const pronounshtml = document.createElement("p"); + pronounshtml.textContent = this.pronouns; + pronounshtml.classList.add("pronouns"); + userbody.appendChild(pronounshtml); - block(): void { - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "PUT", - headers: this.owner.headers, - body: JSON.stringify({ - type: 2, - }), - }); - this.relationshipType = 2; - const channel = this.localuser.channelfocus; - if (channel) { - for (const message of channel.messages) { - message[1].generateMessage(); - } - } - } + const rule = document.createElement("hr"); + userbody.appendChild(rule); + const biohtml = this.bio.makeHTML(); + userbody.appendChild(biohtml); + if(guild){ + Member.resolveMember(this, guild).then(member=>{ + if(!member)return; + const roles = document.createElement("div"); + roles.classList.add("rolesbox"); + for(const role of member.roles){ + const roleDiv = document.createElement("div"); + roleDiv.classList.add("rolediv"); + const color = document.createElement("div"); + roleDiv.append(color); + color.style.setProperty( + "--role-color", + `#${role.color.toString(16).padStart(6, "0")}` + ); + color.classList.add("colorrolediv"); + const span = document.createElement("span"); + roleDiv.append(span); + span.textContent = role.name; + roles.append(roleDiv); + } + userbody.append(roles); + }); + } + if(x !== -1){ + Contextmenu.currentmenu = div; + document.body.appendChild(div); + Contextmenu.keepOnScreen(div); + } + return div; + } - unblock(): void { - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "DELETE", - headers: this.owner.headers, - }); - this.relationshipType = 0; - const channel = this.localuser.channelfocus; - if (channel) { - for (const message of channel.messages) { - message[1].generateMessage(); - } - } - } + profileclick(obj: HTMLElement, guild?: Guild): void{ + obj.onclick = (e: MouseEvent)=>{ + this.buildprofile(e.clientX, e.clientY, guild); + e.stopPropagation(); + }; + } +} - getpfpsrc(): string { - if (this.hypotheticalpfp && this.avatar) { - return this.avatar; - } - if (this.avatar !== null) { - return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ - this.avatar - }.png`; - } else { - const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); - return `${this.info.cdn}/embed/avatars/${int}.png`; - } - } - - async buildprofile( - x: number, - y: number, - guild: Guild | null = null - ): Promise { - if (Contextmenu.currentmenu != "") { - Contextmenu.currentmenu.remove(); - } - - const div = document.createElement("div"); - - if (this.accent_color) { - div.style.setProperty( - "--accent_color", - `#${this.accent_color.toString(16).padStart(6, "0")}` - ); - } else { - div.style.setProperty("--accent_color", "transparent"); - } - if (this.banner) { - const banner = document.createElement("img"); - let src: string; - if (!this.hypotheticalbanner) { - src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ - this.banner - }.png`; - } else { - src = this.banner; - } - banner.src = src; - banner.classList.add("banner"); - div.append(banner); - } - if (x !== -1) { - div.style.left = `${x}px`; - div.style.top = `${y}px`; - div.classList.add("profile", "flexttb"); - } else { - this.setstatus("online"); - div.classList.add("hypoprofile", "flexttb"); - } - const badgediv = document.createElement("div"); - badgediv.classList.add("badges"); - (async () => { - if (!this.badge_ids) return; - for (const id of this.badge_ids) { - const badgejson = await this.getBadge(id); - if (badgejson) { - const badge = document.createElement(badgejson.link ? "a" : "div"); - badge.classList.add("badge"); - const img = document.createElement("img"); - img.src = badgejson.icon; - badge.append(img); - const span = document.createElement("span"); - span.textContent = badgejson.description; - badge.append(span); - if (badge instanceof HTMLAnchorElement) { - badge.href = badgejson.link; - } - badgediv.append(badge); - } - } - })(); - const pfp = await this.buildstatuspfp(); - div.appendChild(pfp); - const userbody = document.createElement("div"); - userbody.classList.add("infosection"); - div.appendChild(userbody); - const usernamehtml = document.createElement("h2"); - usernamehtml.textContent = this.username; - userbody.appendChild(usernamehtml); - userbody.appendChild(badgediv); - const discrimatorhtml = document.createElement("h3"); - discrimatorhtml.classList.add("tag"); - discrimatorhtml.textContent = `${this.username}#${this.discriminator}`; - userbody.appendChild(discrimatorhtml); - - const pronounshtml = document.createElement("p"); - pronounshtml.textContent = this.pronouns; - pronounshtml.classList.add("pronouns"); - userbody.appendChild(pronounshtml); - - const rule = document.createElement("hr"); - userbody.appendChild(rule); - const biohtml = this.bio.makeHTML(); - userbody.appendChild(biohtml); - if (guild) { - Member.resolveMember(this, guild).then((member) => { - if (!member) return; - const roles = document.createElement("div"); - roles.classList.add("rolesbox"); - for (const role of member.roles) { - const roleDiv = document.createElement("div"); - roleDiv.classList.add("rolediv"); - const color = document.createElement("div"); - roleDiv.append(color); - color.style.setProperty( - "--role-color", - `#${role.color.toString(16).padStart(6, "0")}` - ); - color.classList.add("colorrolediv"); - const span = document.createElement("span"); - roleDiv.append(span); - span.textContent = role.name; - roles.append(roleDiv); - } - userbody.append(roles); - }); - } - if (x !== -1) { - Contextmenu.currentmenu = div; - document.body.appendChild(div); - Contextmenu.keepOnScreen(div); - } - return div; - } - - profileclick(obj: HTMLElement, guild?: Guild): void { - obj.onclick = (e: MouseEvent) => { - this.buildprofile(e.clientX, e.clientY, guild); - e.stopPropagation(); - }; - } - } - - User.setUpContextMenu(); - export { User }; +User.setUpContextMenu(); +export{ User };