formatting updates

This commit is contained in:
MathMan05 2024-12-20 19:28:08 -06:00
parent ffe21e6d6c
commit d2d0f45c81
50 changed files with 7783 additions and 7432 deletions

View file

@ -19,6 +19,12 @@
"prettier": "^3.4.2", "prettier": "^3.4.2",
"rimraf": "^6.0.1" "rimraf": "^6.0.1"
}, },
"prettier":{
"useTabs":true,
"printWidth":100,
"semi":true,
"bracketSpacing":false
},
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.10.0", "@eslint/js": "^9.10.0",
"@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/eslint-plugin": "^0.25.0",

View file

@ -1,176 +1,180 @@
#!/usr/bin/env node #!/usr/bin/env node
import compression from"compression"; import compression from "compression";
import express, { Request, Response }from"express"; import express, {Request, Response} from "express";
import fs from"node:fs/promises"; import fs from "node:fs/promises";
import path from"node:path"; import path from "node:path";
import{ observe, uptime }from"./stats.js"; import {observe, uptime} from "./stats.js";
import{ getApiUrls, inviteResponse }from"./utils.js"; import {getApiUrls, inviteResponse} from "./utils.js";
import{ fileURLToPath }from"node:url"; import {fileURLToPath} from "node:url";
import {readFileSync} from "fs"; import {readFileSync} from "fs";
import process from"node:process"; import process from "node:process";
const devmode = (process.env.NODE_ENV || "development") === "development"; const devmode = (process.env.NODE_ENV || "development") === "development";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
interface Instance { interface Instance {
name: string; name: string;
[key: string]: any; [key: string]: any;
} }
const app = express(); const app = express();
type instace={ type instace = {
name:string, name: string;
description?:string, description?: string;
descriptionLong?:string, descriptionLong?: string;
image?:string, image?: string;
url?:string, url?: string;
language:string, language: string;
country:string, country: string;
display:boolean, display: boolean;
urls?:{ urls?: {
wellknown:string, wellknown: string;
api:string, api: string;
cdn:string, cdn: string;
gateway:string, gateway: string;
login?:string login?: string;
}, };
contactInfo?:{ contactInfo?: {
discord?:string, discord?: string;
github?:string, github?: string;
email?:string, email?: string;
spacebar?:string, spacebar?: string;
matrix?:string, matrix?: string;
mastodon?:string mastodon?: string;
} };
} };
const instances=JSON.parse(readFileSync(process.env.JANK_INSTANCES_PATH||(__dirname+"/webpage/instances.json")).toString()) as instace[]; const instances = JSON.parse(
readFileSync(process.env.JANK_INSTANCES_PATH || __dirname + "/webpage/instances.json").toString(),
) as instace[];
const instanceNames = new Map<string, Instance>(); const instanceNames = new Map<string, Instance>();
for(const instance of instances){ for (const instance of instances) {
instanceNames.set(instance.name, instance); instanceNames.set(instance.name, instance);
} }
app.use(compression()); app.use(compression());
async function updateInstances(): Promise<void>{ async function updateInstances(): Promise<void> {
try{ try {
const response = await fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"); const response = await fetch(
"https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json",
);
const json = (await response.json()) as Instance[]; const json = (await response.json()) as Instance[];
for(const instance of json){ for (const instance of json) {
if(instanceNames.has(instance.name)){ if (instanceNames.has(instance.name)) {
const existingInstance = instanceNames.get(instance.name); const existingInstance = instanceNames.get(instance.name);
if(existingInstance){ if (existingInstance) {
for(const key of Object.keys(instance)){ for (const key of Object.keys(instance)) {
if(!existingInstance[key]){ if (!existingInstance[key]) {
existingInstance[key] = instance[key]; existingInstance[key] = instance[key];
} }
} }
} }
}else{ } else {
instances.push(instance as any); instances.push(instance as any);
} }
} }
observe(instances); observe(instances);
}catch(error){ } catch (error) {
console.error("Error updating instances:", error); console.error("Error updating instances:", error);
} }
} }
updateInstances(); updateInstances();
app.use("/getupdates", async (_req: Request, res: Response)=>{ app.use("/getupdates", async (_req: Request, res: Response) => {
try{ try {
const stats = await fs.stat(path.join(__dirname, "webpage")); const stats = await fs.stat(path.join(__dirname, "webpage"));
res.send(stats.mtimeMs.toString()); res.send(stats.mtimeMs.toString());
}catch(error){ } catch (error) {
console.error("Error getting updates:", error); console.error("Error getting updates:", error);
res.status(500).send("Error getting updates"); res.status(500).send("Error getting updates");
} }
}); });
app.use("/services/oembed", (req: Request, res: Response)=>{ app.use("/services/oembed", (req: Request, res: Response) => {
inviteResponse(req, res); inviteResponse(req, res);
}); });
app.use("/uptime", (req: Request, res: Response)=>{ app.use("/uptime", (req: Request, res: Response) => {
const instanceUptime = uptime.get(req.query.name as string); const instanceUptime = uptime.get(req.query.name as string);
res.send(instanceUptime); res.send(instanceUptime);
}); });
app.use("/", async (req: Request, res: Response)=>{ app.use("/", async (req: Request, res: Response) => {
const scheme = req.secure ? "https" : "http"; const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`; const host = `${scheme}://${req.get("Host")}`;
const ref = host + req.originalUrl; const ref = host + req.originalUrl;
if(host && ref){ if (host && ref) {
const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`;
res.set( res.set(
"Link", "Link",
`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` `<${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")); res.sendFile(path.join(__dirname, "webpage", "home.html"));
return; return;
} }
if(req.path.startsWith("/instances.json")){ if (req.path.startsWith("/instances.json")) {
res.json(instances); res.json(instances);
return; return;
} }
if(req.path.startsWith("/invite/")){ if (req.path.startsWith("/invite/")) {
res.sendFile(path.join(__dirname, "webpage", "invite.html")); res.sendFile(path.join(__dirname, "webpage", "invite.html"));
return; return;
} }
const filePath = path.join(__dirname, "webpage", req.path); const filePath = path.join(__dirname, "webpage", req.path);
try{ try {
await fs.access(filePath); await fs.access(filePath);
if(devmode){ if (devmode) {
const filePath2 = path.join(__dirname, "../src/webpage", req.path); const filePath2 = path.join(__dirname, "../src/webpage", req.path);
try{ try {
await fs.access(filePath2); await fs.access(filePath2);
res.sendFile(filePath2); res.sendFile(filePath2);
return; return;
}catch{} } catch {}
} }
res.sendFile(filePath); res.sendFile(filePath);
}catch{ } catch {
try{ try {
await fs.access(`${filePath}.html`); await fs.access(`${filePath}.html`);
if(devmode){ if (devmode) {
const filePath2 = path.join(__dirname, "../src/webpage", req.path); const filePath2 = path.join(__dirname, "../src/webpage", req.path);
try{ try {
await fs.access(filePath2 + ".html"); await fs.access(filePath2 + ".html");
res.sendFile(filePath2 + ".html"); res.sendFile(filePath2 + ".html");
return; return;
}catch{} } catch {}
} }
res.sendFile(`${filePath}.html`); res.sendFile(`${filePath}.html`);
}catch{ } catch {
if(req.path.startsWith("/src/webpage")){ if (req.path.startsWith("/src/webpage")) {
const filePath2 = path.join(__dirname, "..", req.path); const filePath2 = path.join(__dirname, "..", req.path);
try{ try {
await fs.access(filePath2); await fs.access(filePath2);
res.sendFile(filePath2); res.sendFile(filePath2);
return; return;
}catch{} } catch {}
} }
res.sendFile(path.join(__dirname, "webpage", "index.html")); res.sendFile(path.join(__dirname, "webpage", "index.html"));
} }
} }
}); });
app.set('trust proxy', (ip:string) => ip.startsWith("127.")); app.set("trust proxy", (ip: string) => ip.startsWith("127."));
const PORT = process.env.PORT || Number(process.argv[2]) || 8080; const PORT = process.env.PORT || Number(process.argv[2]) || 8080;
app.listen(PORT, ()=>{ app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`); console.log(`Server running on port ${PORT}`);
}); });
export{ getApiUrls }; export {getApiUrls};

View file

@ -1,39 +1,39 @@
import fs from"node:fs"; import fs from "node:fs";
import path from"node:path"; import path from "node:path";
import{ getApiUrls }from"./utils.js"; import {getApiUrls} from "./utils.js";
import{ fileURLToPath }from"node:url"; import {fileURLToPath} from "node:url";
import{ setTimeout, clearTimeout }from"node:timers"; import {setTimeout, clearTimeout} from "node:timers";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
interface UptimeEntry { interface UptimeEntry {
time: number; time: number;
online: boolean; online: boolean;
} }
interface Instance { interface Instance {
name: string; name: string;
urls?: { api: string }; urls?: {api: string};
url?: string; url?: string;
online?: boolean; online?: boolean;
uptime?: { uptime?: {
daytime: number; daytime: number;
weektime: number; weektime: number;
alltime: number; alltime: number;
}; };
} }
const uptimeObject: Map<string, UptimeEntry[]> = loadUptimeObject(); const uptimeObject: Map<string, UptimeEntry[]> = loadUptimeObject();
export{ uptimeObject as uptime }; export {uptimeObject as uptime};
function loadUptimeObject(): Map<string, UptimeEntry[]>{ function loadUptimeObject(): Map<string, UptimeEntry[]> {
const filePath = process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"); const filePath = process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json");
if(fs.existsSync(filePath)){ if (fs.existsSync(filePath)) {
try{ try {
const data = JSON.parse(fs.readFileSync(filePath, "utf8")); const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
return new Map(Object.entries(data)); return new Map(Object.entries(data));
}catch(error){ } catch (error) {
console.error("Error reading uptime.json:", error); console.error("Error reading uptime.json:", error);
return new Map(); return new Map();
} }
@ -43,26 +43,26 @@ function loadUptimeObject(): Map<string, UptimeEntry[]>{
let saveTimeout: ReturnType<typeof setTimeout> | null = null; let saveTimeout: ReturnType<typeof setTimeout> | null = null;
function saveUptimeObject(): void{ function saveUptimeObject(): void {
if(saveTimeout){ if (saveTimeout) {
clearTimeout(saveTimeout); clearTimeout(saveTimeout);
} }
saveTimeout = setTimeout(()=>{ saveTimeout = setTimeout(() => {
const data = Object.fromEntries(uptimeObject); const data = Object.fromEntries(uptimeObject);
fs.writeFile( fs.writeFile(
process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"), process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json"),
JSON.stringify(data), JSON.stringify(data),
error=>{ (error) => {
if(error){ if (error) {
console.error("Error saving uptime.json:", error); console.error("Error saving uptime.json:", error);
} }
} },
); );
}, 5000); // Batch updates every 5 seconds }, 5000); // Batch updates every 5 seconds
} }
function removeUndefinedKey(): void{ function removeUndefinedKey(): void {
if(uptimeObject.has("undefined")){ if (uptimeObject.has("undefined")) {
uptimeObject.delete("undefined"); uptimeObject.delete("undefined");
saveUptimeObject(); saveUptimeObject();
} }
@ -70,101 +70,89 @@ function removeUndefinedKey(): void{
removeUndefinedKey(); removeUndefinedKey();
export async function observe(instances: Instance[]): Promise<void>{ export async function observe(instances: Instance[]): Promise<void> {
const activeInstances = new Set<string>(); const activeInstances = new Set<string>();
const instancePromises = instances.map(instance=>resolveInstance(instance, activeInstances) const instancePromises = instances.map((instance) => resolveInstance(instance, activeInstances));
);
await Promise.allSettled(instancePromises); await Promise.allSettled(instancePromises);
updateInactiveInstances(activeInstances); updateInactiveInstances(activeInstances);
} }
async function resolveInstance( async function resolveInstance(instance: Instance, activeInstances: Set<string>): Promise<void> {
instance: Instance, try {
activeInstances: Set<string>
): Promise<void>{
try{
calcStats(instance); calcStats(instance);
const api = await getApiUrl(instance); const api = await getApiUrl(instance);
if(!api){ if (!api) {
handleUnresolvedApi(instance); handleUnresolvedApi(instance);
return; return;
} }
activeInstances.add(instance.name); activeInstances.add(instance.name);
await checkHealth(instance, api); await checkHealth(instance, api);
scheduleHealthCheck(instance, api); scheduleHealthCheck(instance, api);
}catch(error){ } catch (error) {
console.error("Error resolving instance:", error); console.error("Error resolving instance:", error);
} }
} }
async function getApiUrl(instance: Instance): Promise<string | null>{ async function getApiUrl(instance: Instance): Promise<string | null> {
if(instance.urls){ if (instance.urls) {
return instance.urls.api; return instance.urls.api;
} }
if(instance.url){ if (instance.url) {
const urls = await getApiUrls(instance.url); const urls = await getApiUrls(instance.url);
return urls ? urls.api : null; return urls ? urls.api : null;
} }
return null; return null;
} }
function handleUnresolvedApi(instance: Instance): void{ function handleUnresolvedApi(instance: Instance): void {
setStatus(instance, false); setStatus(instance, false);
console.warn(`${instance.name} does not resolve api URL`, instance); console.warn(`${instance.name} does not resolve api URL`, instance);
setTimeout(()=>resolveInstance(instance, new Set()), 1000 * 60 * 30); setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30);
} }
function scheduleHealthCheck(instance: Instance, api: string): void{ function scheduleHealthCheck(instance: Instance, api: string): void {
const checkInterval = 1000 * 60 * 30; const checkInterval = 1000 * 60 * 30;
const initialDelay = Math.random() * 1000 * 60 * 10; const initialDelay = Math.random() * 1000 * 60 * 10;
setTimeout(()=>{ setTimeout(() => {
checkHealth(instance, api); checkHealth(instance, api);
setInterval(()=>checkHealth(instance, api), checkInterval); setInterval(() => checkHealth(instance, api), checkInterval);
}, initialDelay); }, initialDelay);
} }
async function checkHealth( async function checkHealth(instance: Instance, api: string, tries = 0): Promise<void> {
instance: Instance, try {
api: string, const response = await fetch(`${api}/ping`, {method: "HEAD"});
tries = 0
): Promise<void>{
try{
const response = await fetch(`${api}/ping`, { method: "HEAD" });
console.log(`Checking health for ${instance.name}: ${response.status}`); console.log(`Checking health for ${instance.name}: ${response.status}`);
if(response.ok || tries > 3){ if (response.ok || tries > 3) {
setStatus(instance, response.ok); setStatus(instance, response.ok);
}else{ } else {
retryHealthCheck(instance, api, tries); retryHealthCheck(instance, api, tries);
} }
}catch(error){ } catch (error) {
console.error(`Error checking health for ${instance.name}:`, error); console.error(`Error checking health for ${instance.name}:`, error);
if(tries > 3){ if (tries > 3) {
setStatus(instance, false); setStatus(instance, false);
}else{ } else {
retryHealthCheck(instance, api, tries); retryHealthCheck(instance, api, tries);
} }
} }
} }
function retryHealthCheck( function retryHealthCheck(instance: Instance, api: string, tries: number): void {
instance: Instance, setTimeout(() => checkHealth(instance, api, tries + 1), 30000);
api: string,
tries: number
): void{
setTimeout(()=>checkHealth(instance, api, tries + 1), 30000);
} }
function updateInactiveInstances(activeInstances: Set<string>): void{ function updateInactiveInstances(activeInstances: Set<string>): void {
for(const key of uptimeObject.keys()){ for (const key of uptimeObject.keys()) {
if(!activeInstances.has(key)){ if (!activeInstances.has(key)) {
setStatus(key, false); setStatus(key, false);
} }
} }
} }
function calcStats(instance: Instance): void{ function calcStats(instance: Instance): void {
const obj = uptimeObject.get(instance.name); const obj = uptimeObject.get(instance.name);
if(!obj)return; if (!obj) return;
const now = Date.now(); const now = Date.now();
const day = now - 1000 * 60 * 60 * 24; const day = now - 1000 * 60 * 60 * 24;
@ -176,7 +164,7 @@ function calcStats(instance: Instance): void{
let weektime = 0; let weektime = 0;
let online = false; let online = false;
for(let i = 0; i < obj.length; i++){ for (let i = 0; i < obj.length; i++) {
const entry = obj[i]; const entry = obj[i];
online = entry.online; online = entry.online;
const stamp = entry.time; const stamp = entry.time;
@ -186,11 +174,11 @@ function calcStats(instance: Instance): void{
totalTimePassed += timePassed; totalTimePassed += timePassed;
alltime += Number(online) * timePassed; alltime += Number(online) * timePassed;
if(stamp + timePassed > week){ if (stamp + timePassed > week) {
const weekTimePassed = Math.min(timePassed, nextStamp - week); const weekTimePassed = Math.min(timePassed, nextStamp - week);
weektime += Number(online) * weekTimePassed; weektime += Number(online) * weekTimePassed;
if(stamp + timePassed > day){ if (stamp + timePassed > day) {
const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); const dayTimePassed = Math.min(weekTimePassed, nextStamp - day);
daytime += Number(online) * dayTimePassed; daytime += Number(online) * dayTimePassed;
} }
@ -198,13 +186,7 @@ function calcStats(instance: Instance): void{
} }
instance.online = online; instance.online = online;
instance.uptime = calculateUptimeStats( instance.uptime = calculateUptimeStats(totalTimePassed, alltime, daytime, weektime, online);
totalTimePassed,
alltime,
daytime,
weektime,
online
);
} }
function calculateUptimeStats( function calculateUptimeStats(
@ -212,46 +194,46 @@ function calculateUptimeStats(
alltime: number, alltime: number,
daytime: number, daytime: number,
weektime: number, weektime: number,
online: boolean online: boolean,
): { daytime: number; weektime: number; alltime: number }{ ): {daytime: number; weektime: number; alltime: number} {
const dayInMs = 1000 * 60 * 60 * 24; const dayInMs = 1000 * 60 * 60 * 24;
const weekInMs = dayInMs * 7; const weekInMs = dayInMs * 7;
alltime /= totalTimePassed; alltime /= totalTimePassed;
if(totalTimePassed > dayInMs){ if (totalTimePassed > dayInMs) {
daytime = daytime || (online ? dayInMs : 0); daytime = daytime || (online ? dayInMs : 0);
daytime /= dayInMs; daytime /= dayInMs;
if(totalTimePassed > weekInMs){ if (totalTimePassed > weekInMs) {
weektime = weektime || (online ? weekInMs : 0); weektime = weektime || (online ? weekInMs : 0);
weektime /= weekInMs; weektime /= weekInMs;
}else{ } else {
weektime = alltime; weektime = alltime;
} }
}else{ } else {
weektime = alltime; weektime = alltime;
daytime = alltime; daytime = alltime;
} }
return{ daytime, weektime, alltime }; return {daytime, weektime, alltime};
} }
function setStatus(instance: string | Instance, status: boolean): void{ function setStatus(instance: string | Instance, status: boolean): void {
const name = typeof instance === "string" ? instance : instance.name; const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject.get(name); let obj = uptimeObject.get(name);
if(!obj){ if (!obj) {
obj = []; obj = [];
uptimeObject.set(name, obj); uptimeObject.set(name, obj);
} }
const lastEntry = obj.at(-1); const lastEntry = obj.at(-1);
if(!lastEntry || lastEntry.online !== status){ if (!lastEntry || lastEntry.online !== status) {
obj.push({ time: Date.now(), online: status }); obj.push({time: Date.now(), online: status});
saveUptimeObject(); saveUptimeObject();
if(typeof instance !== "string"){ if (typeof instance !== "string") {
calcStats(instance); calcStats(instance);
} }
} }

View file

@ -1,74 +1,76 @@
import{ Request, Response }from"express"; import {Request, Response} from "express";
interface ApiUrls { interface ApiUrls {
api: string; api: string;
gateway: string; gateway: string;
cdn: string; cdn: string;
wellknown: string; wellknown: string;
} }
interface Invite { interface Invite {
guild: { guild: {
name: string; name: string;
description?: string; description?: string;
icon?: string; icon?: string;
id: string; id: string;
}; };
inviter?: { inviter?: {
username: string; username: string;
}; };
} }
export async function getApiUrls(url: string): Promise<ApiUrls | null>{ export async function getApiUrls(url: string): Promise<ApiUrls | null> {
if(!url.endsWith("/")){ if (!url.endsWith("/")) {
url += "/"; url += "/";
} }
try{ try {
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json()); const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then((res) => res.json());
const api = info.api; const api = info.api;
const apiUrl = new URL(api); const apiUrl = new URL(api);
const policies: any = await fetch( const policies: any = await fetch(
`${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains` `${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
).then(res=>res.json()); ).then((res) => res.json());
return{ return {
api: policies.apiEndpoint, api: policies.apiEndpoint,
gateway: policies.gateway, gateway: policies.gateway,
cdn: policies.cdn, cdn: policies.cdn,
wellknown: url, wellknown: url,
}; };
}catch(error){ } catch (error) {
console.error("Error fetching API URLs:", error); console.error("Error fetching API URLs:", error);
return null; return null;
} }
} }
export async function inviteResponse(req: Request, res: Response): Promise<void>{ export async function inviteResponse(req: Request, res: Response): Promise<void> {
let url: URL; let url: URL;
try{ try {
url = new URL(req.query.url as string); url = new URL(req.query.url as string);
}catch{ } catch {
const scheme = req.secure ? "https" : "http"; const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`; const host = `${scheme}://${req.get("Host")}`;
url = new URL(host); url = new URL(host);
} }
try{ try {
if(url.pathname.startsWith("invite")){ if (url.pathname.startsWith("invite")) {
throw new Error("Invalid invite URL"); throw new Error("Invalid invite URL");
} }
const code = url.pathname.split("/")[2]; const code = url.pathname.split("/")[2];
const instance = url.searchParams.get("instance"); const instance = url.searchParams.get("instance");
if(!instance){ if (!instance) {
throw new Error("Instance not specified"); throw new Error("Instance not specified");
} }
const urls = await getApiUrls(instance); const urls = await getApiUrls(instance);
if(!urls){ if (!urls) {
throw new Error("Failed to get API URLs"); throw new Error("Failed to get API URLs");
} }
const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise<Invite>); const invite = await fetch(`${urls.api}/invites/${code}`).then(
(json) => json.json() as Promise<Invite>,
);
const title = invite.guild.name; const title = invite.guild.name;
const description = invite.inviter const description = invite.inviter
? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}` ? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`
@ -84,7 +86,7 @@ export async function inviteResponse(req: Request, res: Response): Promise<void>
thumbnail, thumbnail,
description, description,
}); });
}catch(error){ } catch (error) {
console.error("Error processing invite response:", error); console.error("Error processing invite response:", error);
res.json({ res.json({
type: "link", type: "link",

View file

@ -1,34 +1,34 @@
import { BinRead } from "../utils/binaryUtils.js"; import {BinRead} from "../utils/binaryUtils.js";
import { Track } from "./track.js"; import {Track} from "./track.js";
export class Audio{ export class Audio {
name:string; name: string;
tracks:(Track|number)[]; tracks: (Track | number)[];
constructor(name:string,tracks:(Track|number)[]){ constructor(name: string, tracks: (Track | number)[]) {
this.tracks=tracks; this.tracks = tracks;
this.name=name; this.name = name;
} }
static parse(read:BinRead,trackarr:Track[]):Audio{ static parse(read: BinRead, trackarr: Track[]): Audio {
const name=read.readString8(); const name = read.readString8();
const length=read.read16(); const length = read.read16();
const tracks:(Track|number)[]=[] const tracks: (Track | number)[] = [];
for(let i=0;i<length;i++){ for (let i = 0; i < length; i++) {
let index=read.read16(); let index = read.read16();
if(index===0){ if (index === 0) {
tracks.push(read.readFloat32()); tracks.push(read.readFloat32());
}else{ } else {
tracks.push(trackarr[index-1]); tracks.push(trackarr[index - 1]);
} }
} }
return new Audio(name,tracks) return new Audio(name, tracks);
} }
async play(){ async play() {
for(const thing of this.tracks){ for (const thing of this.tracks) {
if(thing instanceof Track){ if (thing instanceof Track) {
thing.play(); thing.play();
}else{ } else {
await new Promise((res)=>setTimeout(res,thing)); await new Promise((res) => setTimeout(res, thing));
} }
} }
} }
} }

View file

@ -1,26 +1,39 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Audio</title> <title>Jank Audio</title>
<meta content="Jank Sound" property="og:title"> <meta content="Jank Sound" property="og:title" />
<meta content="A sound editor for jank clients sound format .jasf" property="og:description"> <meta content="A sound editor for jank clients sound format .jasf" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme" style="overflow-y: scroll;"> <body class="no-theme" style="overflow-y: scroll">
<h1>This will eventually be something</h1> <h1>This will eventually be something</h1>
<p>I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience.</p> <p>
I want to let the sound system of jank not be so hard coded, but I still need to work on
everything a bit before that can happen. Thanks for your patience.
</p>
<h3>why does this tool need to exist?</h3> <h3>why does this tool need to exist?</h3>
<p>For size reasons jank does not use normal sound files, so I need to make this whole format to be more adaptable</p> <p>
For size reasons jank does not use normal sound files, so I need to make this whole format to
be more adaptable
</p>
<button id="download">Download the sounds</button> <button id="download">Download the sounds</button>
</body> </body>
<script src="/audio/page.js" type="module"></script> <script src="/audio/page.js" type="module"></script>
</html> </html>

View file

@ -1,9 +1,9 @@
import { BinWrite } from "../utils/binaryUtils.js"; import {BinWrite} from "../utils/binaryUtils.js";
import { setTheme } from "../utils/utils.js"; import {setTheme} from "../utils/utils.js";
import { Play } from "./play.js"; import {Play} from "./play.js";
setTheme(); setTheme();
const w=new BinWrite(2**12); const w = new BinWrite(2 ** 12);
w.writeStringNo("jasf"); w.writeStringNo("jasf");
w.write8(4); w.write8(4);
@ -18,100 +18,103 @@ w.writeString8("custom");
w.write32Float(150); w.write32Float(150);
//return Math.sin(((t + 2) ** Math.cos(t * 4)) * Math.PI * 2 * freq); //return Math.sin(((t + 2) ** Math.cos(t * 4)) * Math.PI * 2 * freq);
//Math.sin((((t+2)**Math.cos((t*4)))*((Math.PI*2)*f))) //Math.sin((((t+2)**Math.cos((t*4)))*((Math.PI*2)*f)))
w.write8(4);//sin w.write8(4); //sin
w.write8(5)//times w.write8(5); //times
{ {
w.write8(9);//Power w.write8(9); //Power
{ {
w.write8(6);//adding w.write8(6); //adding
w.write8(1);//t w.write8(1); //t
w.write8(0);w.write32Float(2);//2 w.write8(0);
} w.write32Float(2); //2
w.write8(13);//cos }
w.write8(5);// times w.write8(13); //cos
w.write8(1);//t w.write8(5); // times
w.write8(0);w.write32Float(4);//4 w.write8(1); //t
w.write8(0);
w.write32Float(4); //4
} }
{ {
w.write8(5)//times w.write8(5); //times
w.write8(5)//times w.write8(5); //times
w.write8(3);//PI w.write8(3); //PI
w.write8(0);w.write32Float(2);//2 w.write8(0);
w.write8(2);//freq w.write32Float(2); //2
w.write8(2); //freq
} }
w.write16(4);//3 tracks w.write16(4); //3 tracks
w.write16(1);//zip w.write16(1); //zip
w.write8(4); w.write8(4);
w.write32Float(1) w.write32Float(1);
w.write32Float(700) w.write32Float(700);
w.write16(3);//beep w.write16(3); //beep
{ {
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(700); w.write32Float(700);
w.write32Float(50); w.write32Float(50);
w.write8(0); w.write8(0);
w.write32Float(100); w.write32Float(100);
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(700); w.write32Float(700);
w.write32Float(50); w.write32Float(50);
} }
w.write16(5);//three w.write16(5); //three
{ {
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(800); w.write32Float(800);
w.write32Float(50); w.write32Float(50);
w.write8(0); w.write8(0);
w.write32Float(50); w.write32Float(50);
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(1000); w.write32Float(1000);
w.write32Float(50); w.write32Float(50);
w.write8(0); w.write8(0);
w.write32Float(50); w.write32Float(50);
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(1300); w.write32Float(1300);
w.write32Float(50); w.write32Float(50);
} }
w.write16(5);//square w.write16(5); //square
{ {
w.write8(3); w.write8(3);
w.write32Float(1) w.write32Float(1);
w.write32Float(600); w.write32Float(600);
w.write32Float(50); w.write32Float(50);
w.write8(0); w.write8(0);
w.write32Float(50); w.write32Float(50);
w.write8(3); w.write8(3);
w.write32Float(1) w.write32Float(1);
w.write32Float(800); w.write32Float(800);
w.write32Float(50); w.write32Float(50);
w.write8(0); w.write8(0);
w.write32Float(50); w.write32Float(50);
w.write8(3); w.write8(3);
w.write32Float(1) w.write32Float(1);
w.write32Float(1000); w.write32Float(1000);
w.write32Float(50); w.write32Float(50);
} }
w.write16(4);//2 audio w.write16(4); //2 audio
w.writeString8("zip"); w.writeString8("zip");
w.write16(1); w.write16(1);
@ -128,8 +131,8 @@ w.write16(3);
w.writeString8("square"); w.writeString8("square");
w.write16(1); w.write16(1);
w.write16(4); w.write16(4);
const buff=w.getBuffer(); const buff = w.getBuffer();
const play=Play.parseBin(buff); const play = Play.parseBin(buff);
/* /*
const zip=play.audios.get("square"); const zip=play.audios.get("square");
if(zip){ if(zip){
@ -140,18 +143,18 @@ if(zip){
console.log(play.voices[3][0].info.wave) console.log(play.voices[3][0].info.wave)
}; };
*/ */
console.log(play,buff); console.log(play, buff);
const download=document.getElementById("download"); const download = document.getElementById("download");
if(download){ if (download) {
download.onclick=()=>{ download.onclick = () => {
const blob = new Blob([buff], { type: "binary" }); const blob = new Blob([buff], {type: "binary"});
const downloadUrl = URL.createObjectURL(blob); const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement("a");
a.href = downloadUrl; a.href = downloadUrl;
a.download = "sounds.jasf"; a.download = "sounds.jasf";
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
URL.revokeObjectURL(downloadUrl); URL.revokeObjectURL(downloadUrl);
} };
} }

View file

@ -1,48 +1,48 @@
import { BinRead } from "../utils/binaryUtils.js"; import {BinRead} from "../utils/binaryUtils.js";
import { Track } from "./track.js"; import {Track} from "./track.js";
import { AVoice } from "./voice.js"; import {AVoice} from "./voice.js";
import { Audio } from "./audio.js"; import {Audio} from "./audio.js";
export class Play{ export class Play {
voices:[AVoice,string][] voices: [AVoice, string][];
tracks:Track[] tracks: Track[];
audios:Map<string,Audio>; audios: Map<string, Audio>;
constructor(voices:[AVoice,string][],tracks:Track[],audios:Map<string,Audio>){ constructor(voices: [AVoice, string][], tracks: Track[], audios: Map<string, Audio>) {
this.voices=voices; this.voices = voices;
this.tracks=tracks; this.tracks = tracks;
this.audios=audios; this.audios = audios;
} }
static parseBin(buffer:ArrayBuffer){ static parseBin(buffer: ArrayBuffer) {
const read=new BinRead(buffer); const read = new BinRead(buffer);
if(read.readStringNo(4)!=="jasf") throw new Error("this is not a jasf file"); if (read.readStringNo(4) !== "jasf") throw new Error("this is not a jasf file");
let voices=read.read8(); let voices = read.read8();
let six=false; let six = false;
if(voices===255){ if (voices === 255) {
voices=read.read16(); voices = read.read16();
six=true; six = true;
} }
const voiceArr:[AVoice,string][]=[]; const voiceArr: [AVoice, string][] = [];
for(let i=0;i<voices;i++){ for (let i = 0; i < voices; i++) {
voiceArr.push(AVoice.getVoice(read)); voiceArr.push(AVoice.getVoice(read));
} }
const tracks=read.read16(); const tracks = read.read16();
const trackArr:Track[]=[]; const trackArr: Track[] = [];
for(let i=0;i<tracks;i++){ for (let i = 0; i < tracks; i++) {
trackArr.push(Track.parse(read,voiceArr,six)); trackArr.push(Track.parse(read, voiceArr, six));
} }
const audios=read.read16(); const audios = read.read16();
const audioArr=new Map<string,Audio>(); const audioArr = new Map<string, Audio>();
for(let i=0;i<audios;i++){ for (let i = 0; i < audios; i++) {
const a=Audio.parse(read,trackArr); const a = Audio.parse(read, trackArr);
audioArr.set(a.name,a) audioArr.set(a.name, a);
} }
return new Play(voiceArr,trackArr,audioArr); return new Play(voiceArr, trackArr, audioArr);
} }
static async playURL(url:string){ static async playURL(url: string) {
const res=await fetch(url); const res = await fetch(url);
const arr=await res.arrayBuffer(); const arr = await res.arrayBuffer();
return this.parseBin(arr); return this.parseBin(arr);
} }
} }

View file

@ -1,46 +1,45 @@
import { BinRead } from "../utils/binaryUtils.js"; import {BinRead} from "../utils/binaryUtils.js";
import { AVoice } from "./voice.js"; import {AVoice} from "./voice.js";
export class Track{ export class Track {
seq:(AVoice|number)[]; seq: (AVoice | number)[];
constructor(playing:(AVoice|number)[]){ constructor(playing: (AVoice | number)[]) {
this.seq=playing; this.seq = playing;
} }
static parse(read:BinRead,play:[AVoice,string][],six:boolean):Track{ static parse(read: BinRead, play: [AVoice, string][], six: boolean): Track {
const length=read.read16(); const length = read.read16();
const play2:(AVoice|number)[]=[]; const play2: (AVoice | number)[] = [];
for(let i=0;i<length;i++){ for (let i = 0; i < length; i++) {
let index:number; let index: number;
if(six){ if (six) {
index=read.read16(); index = read.read16();
}else{ } else {
index=read.read8(); index = read.read8();
} }
if(index===0){ if (index === 0) {
play2.push(read.readFloat32()); play2.push(read.readFloat32());
continue; continue;
} }
index--; index--;
if(!play[index]) throw new Error("voice not found"); if (!play[index]) throw new Error("voice not found");
const [voice]=play[index]; const [voice] = play[index];
let temp:AVoice; let temp: AVoice;
if((voice.info.wave instanceof Function)){ if (voice.info.wave instanceof Function) {
temp=voice.clone(read.readFloat32(),read.readFloat32()); temp = voice.clone(read.readFloat32(), read.readFloat32());
}else{ } else {
temp=voice.clone(read.readFloat32(),read.readFloat32(),read.readFloat32()); temp = voice.clone(read.readFloat32(), read.readFloat32(), read.readFloat32());
} }
play2.push(temp); play2.push(temp);
}
} return new Track(play2);
return new Track(play2); }
} async play() {
async play(){ for (const thing of this.seq) {
for(const thing of this.seq){ if (thing instanceof AVoice) {
if(thing instanceof AVoice){ thing.playL();
thing.playL(); } else {
}else{ await new Promise((res) => setTimeout(res, thing));
await new Promise((res)=>setTimeout(res,thing)); }
} }
} }
}
} }

View file

@ -1,23 +1,23 @@
import { BinRead } from "../utils/binaryUtils.js"; import {BinRead} from "../utils/binaryUtils.js";
class AVoice{ class AVoice {
audioCtx: AudioContext; audioCtx: AudioContext;
info: { wave: string | Function; freq: number }; info: {wave: string | Function; freq: number};
playing: boolean; playing: boolean;
myArrayBuffer: AudioBuffer; myArrayBuffer: AudioBuffer;
gainNode: GainNode; gainNode: GainNode;
buffer: Float32Array; buffer: Float32Array;
source: AudioBufferSourceNode; source: AudioBufferSourceNode;
length=1; length = 1;
constructor(wave: string | Function, freq: number, volume = 1,length=1000){ constructor(wave: string | Function, freq: number, volume = 1, length = 1000) {
this.length=length; this.length = length;
this.audioCtx = new window.AudioContext(); this.audioCtx = new window.AudioContext();
this.info = { wave, freq }; this.info = {wave, freq};
this.playing = false; this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer( this.myArrayBuffer = this.audioCtx.createBuffer(
1, 1,
this.audioCtx.sampleRate*length/1000, (this.audioCtx.sampleRate * length) / 1000,
this.audioCtx.sampleRate this.audioCtx.sampleRate,
); );
this.gainNode = this.audioCtx.createGain(); this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume; this.gainNode.gain.value = volume;
@ -29,193 +29,193 @@ class AVoice{
this.source.start(); this.source.start();
this.updateWave(); this.updateWave();
} }
clone(volume:number,freq:number,length=this.length){ clone(volume: number, freq: number, length = this.length) {
return new AVoice(this.wave,freq,volume,length); return new AVoice(this.wave, freq, volume, length);
} }
get wave(): string | Function{ get wave(): string | Function {
return this.info.wave; return this.info.wave;
} }
get freq(): number{ get freq(): number {
return this.info.freq; return this.info.freq;
} }
set wave(wave: string | Function){ set wave(wave: string | Function) {
this.info.wave = wave; this.info.wave = wave;
this.updateWave(); this.updateWave();
} }
set freq(freq: number){ set freq(freq: number) {
this.info.freq = freq; this.info.freq = freq;
this.updateWave(); this.updateWave();
} }
updateWave(): void{ updateWave(): void {
const func = this.waveFunction(); const func = this.waveFunction();
for(let i = 0; i < this.buffer.length; i++){ for (let i = 0; i < this.buffer.length; i++) {
this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq);
} }
} }
waveFunction(): Function{ waveFunction(): Function {
if(typeof this.wave === "function"){ if (typeof this.wave === "function") {
return this.wave; return this.wave;
} }
switch(this.wave){ switch (this.wave) {
case"sin": case "sin":
return(t: number, freq: number)=>{ return (t: number, freq: number) => {
return Math.sin(t * Math.PI * 2 * freq); return Math.sin(t * Math.PI * 2 * freq);
}; };
case"triangle": case "triangle":
return(t: number, freq: number)=>{ return (t: number, freq: number) => {
return Math.abs(((4 * t * freq) % 4) - 2) - 1; return Math.abs(((4 * t * freq) % 4) - 2) - 1;
}; };
case"sawtooth": case "sawtooth":
return(t: number, freq: number)=>{ return (t: number, freq: number) => {
return((t * freq) % 1) * 2 - 1; return ((t * freq) % 1) * 2 - 1;
}; };
case"square": case "square":
return(t: number, freq: number)=>{ return (t: number, freq: number) => {
return(t * freq) % 2 < 1 ? 1 : -1; return (t * freq) % 2 < 1 ? 1 : -1;
}; };
case"white": case "white":
return(_t: number, _freq: number)=>{ return (_t: number, _freq: number) => {
return Math.random() * 2 - 1; return Math.random() * 2 - 1;
}; };
} }
return new Function(); return new Function();
} }
play(): void{ play(): void {
if(this.playing){ if (this.playing) {
return; return;
} }
this.source.connect(this.gainNode); this.source.connect(this.gainNode);
this.playing = true; this.playing = true;
} }
playL(){ playL() {
this.play(); this.play();
setTimeout(()=>{ setTimeout(() => {
this.stop(); this.stop();
},this.length); }, this.length);
} }
stop(): void{ stop(): void {
if(this.playing){ if (this.playing) {
this.source.disconnect(); this.source.disconnect();
this.playing = false; this.playing = false;
} }
} }
static noises(noise: string): void{ static noises(noise: string): void {
switch(noise){ switch (noise) {
case"three": { case "three": {
const voicy = new AVoice("sin", 800); const voicy = new AVoice("sin", 800);
voicy.play();
setTimeout(_=>{
voicy.freq = 1000;
}, 50);
setTimeout(_=>{
voicy.freq = 1300;
}, 100);
setTimeout(_=>{
voicy.stop();
}, 150);
break;
}
case"zip": {
const voicy = new AVoice((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 AVoice("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 AVoice("sin", 800);
voicy.play();
setTimeout(_=>{
voicy.stop();
}, 50);
setTimeout(_=>{
voicy.play(); voicy.play();
}, 100); setTimeout((_) => {
setTimeout(_=>{ voicy.freq = 1000;
voicy.stop(); }, 50);
}, 150); setTimeout((_) => {
break; voicy.freq = 1300;
} }, 100);
case "join":{ setTimeout((_) => {
const voicy = new AVoice("triangle", 600,.1); voicy.stop();
voicy.play(); }, 150);
setTimeout(_=>{ break;
voicy.freq=800; }
}, 75); case "zip": {
setTimeout(_=>{ const voicy = new AVoice((t: number, freq: number) => {
voicy.freq=1000; return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
}, 150); }, 700);
setTimeout(_=>{
voicy.stop();
}, 200);
break;
}
case "leave":{
const voicy = new AVoice("triangle", 850,.5);
voicy.play();
setTimeout(_=>{
voicy.freq=700;
}, 100);
setTimeout(_=>{
voicy.stop();
voicy.freq=400;
}, 180);
setTimeout(_=>{
voicy.play(); voicy.play();
}, 200); setTimeout((_) => {
setTimeout(_=>{ voicy.stop();
voicy.stop(); }, 150);
}, 250); break;
break; }
} case "square": {
const voicy = new AVoice("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 AVoice("sin", 800);
voicy.play();
setTimeout((_) => {
voicy.stop();
}, 50);
setTimeout((_) => {
voicy.play();
}, 100);
setTimeout((_) => {
voicy.stop();
}, 150);
break;
}
case "join": {
const voicy = new AVoice("triangle", 600, 0.1);
voicy.play();
setTimeout((_) => {
voicy.freq = 800;
}, 75);
setTimeout((_) => {
voicy.freq = 1000;
}, 150);
setTimeout((_) => {
voicy.stop();
}, 200);
break;
}
case "leave": {
const voicy = new AVoice("triangle", 850, 0.5);
voicy.play();
setTimeout((_) => {
voicy.freq = 700;
}, 100);
setTimeout((_) => {
voicy.stop();
voicy.freq = 400;
}, 180);
setTimeout((_) => {
voicy.play();
}, 200);
setTimeout((_) => {
voicy.stop();
}, 250);
break;
}
} }
} }
static get sounds(){ static get sounds() {
return["three", "zip", "square", "beep"]; return ["three", "zip", "square", "beep"];
} }
static getVoice(read:BinRead):[AVoice,string]{ static getVoice(read: BinRead): [AVoice, string] {
const name = read.readString8(); const name = read.readString8();
let length=read.readFloat32(); let length = read.readFloat32();
let special:Function|string let special: Function | string;
if(length!==0){ if (length !== 0) {
special=this.parseExpression(read); special = this.parseExpression(read);
}else{ } else {
special=name; special = name;
length=1; length = 1;
} }
return [new AVoice(special,0,0,length),name] return [new AVoice(special, 0, 0, length), name];
} }
static parseExpression(read:BinRead):Function{ static parseExpression(read: BinRead): Function {
return new Function("t","f",`return ${this.PEHelper(read)};`); return new Function("t", "f", `return ${this.PEHelper(read)};`);
} }
static PEHelper(read:BinRead):string{ static PEHelper(read: BinRead): string {
let state=read.read8(); let state = read.read8();
switch(state){ switch (state) {
case 0: case 0:
return ""+read.readFloat32(); return "" + read.readFloat32();
case 1: case 1:
return "t"; return "t";
case 2: case 2:
return "f"; return "f";
case 3: case 3:
return `Math.PI` return `Math.PI`;
case 4: case 4:
return `Math.sin(${this.PEHelper(read)})`; return `Math.sin(${this.PEHelper(read)})`;
case 5: case 5:
@ -238,9 +238,8 @@ class AVoice{
return `Math.cos(${this.PEHelper(read)})`; return `Math.cos(${this.PEHelper(read)})`;
default: default:
throw new Error("unexpected case found!"); throw new Error("unexpected case found!");
} }
} }
} }
export{ AVoice as AVoice }; export {AVoice as AVoice};

File diff suppressed because it is too large Load diff

View file

@ -1,82 +1,82 @@
import{ iOS }from"./utils/utils.js"; import {iOS} from "./utils/utils.js";
class Contextmenu<x, y>{ class Contextmenu<x, y> {
static currentmenu: HTMLElement | ""; static currentmenu: HTMLElement | "";
name: string; name: string;
buttons: [ buttons: [
string|(()=>string), string | (() => string),
(this: x, arg: y, e: MouseEvent) => void, (this: x, arg: y, e: MouseEvent) => void,
string | null, string | null,
(this: x, arg: y) => boolean, (this: x, arg: y) => boolean,
(this: x, arg: y) => boolean, (this: x, arg: y) => boolean,
string string,
][]; ][];
div!: HTMLDivElement; div!: HTMLDivElement;
static setup(){ static setup() {
Contextmenu.currentmenu = ""; Contextmenu.currentmenu = "";
document.addEventListener("click", event=>{ document.addEventListener("click", (event) => {
if(Contextmenu.currentmenu === ""){ if (Contextmenu.currentmenu === "") {
return; return;
} }
if(!Contextmenu.currentmenu.contains(event.target as Node)){ if (!Contextmenu.currentmenu.contains(event.target as Node)) {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
Contextmenu.currentmenu = ""; Contextmenu.currentmenu = "";
} }
}); });
} }
constructor(name: string){ constructor(name: string) {
this.name = name; this.name = name;
this.buttons = []; this.buttons = [];
} }
addbutton( addbutton(
text: string|(()=>string), text: string | (() => string),
onclick: (this: x, arg: y, e: MouseEvent) => void, onclick: (this: x, arg: y, e: MouseEvent) => void,
img: null | string = null, img: null | string = null,
shown: (this: x, arg: y) => boolean = _=>true, shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = _=>true enabled: (this: x, arg: y) => boolean = (_) => true,
){ ) {
this.buttons.push([text, onclick, img, shown, enabled, "button"]); this.buttons.push([text, onclick, img, shown, enabled, "button"]);
return{}; return {};
} }
addsubmenu( addsubmenu(
text: string|(()=>string), text: string | (() => string),
onclick: (this: x, arg: y, e: MouseEvent) => void, onclick: (this: x, arg: y, e: MouseEvent) => void,
img = null, img = null,
shown: (this: x, arg: y) => boolean = _=>true, shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = _=>true enabled: (this: x, arg: y) => boolean = (_) => true,
){ ) {
this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); this.buttons.push([text, onclick, img, shown, enabled, "submenu"]);
return{}; return {};
} }
private makemenu(x: number, y: number, addinfo: x, other: y){ private makemenu(x: number, y: number, addinfo: x, other: y) {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("contextmenu", "flexttb"); div.classList.add("contextmenu", "flexttb");
let visibleButtons = 0; let visibleButtons = 0;
for(const thing of this.buttons){ for (const thing of this.buttons) {
if(!thing[3].call(addinfo, other))continue; if (!thing[3].call(addinfo, other)) continue;
visibleButtons++; visibleButtons++;
const intext = document.createElement("button"); const intext = document.createElement("button");
intext.disabled = !thing[4].call(addinfo, other); intext.disabled = !thing[4].call(addinfo, other);
intext.classList.add("contextbutton"); intext.classList.add("contextbutton");
if(thing[0] instanceof Function){ if (thing[0] instanceof Function) {
intext.textContent = thing[0](); intext.textContent = thing[0]();
}else{ } else {
intext.textContent = thing[0]; intext.textContent = thing[0];
} }
console.log(thing); console.log(thing);
if(thing[5] === "button" || thing[5] === "submenu"){ if (thing[5] === "button" || thing[5] === "submenu") {
intext.onclick = (e)=>{ intext.onclick = (e) => {
div.remove(); div.remove();
thing[1].call(addinfo, other,e) thing[1].call(addinfo, other, e);
}; };
} }
div.appendChild(intext); div.appendChild(intext);
} }
if(visibleButtons == 0)return; if (visibleButtons == 0) return;
if(Contextmenu.currentmenu != ""){ if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
div.style.top = y + "px"; div.style.top = y + "px";
@ -87,64 +87,74 @@ class Contextmenu<x, y>{
Contextmenu.currentmenu = div; Contextmenu.currentmenu = div;
return this.div; return this.div;
} }
bindContextmenu(obj: HTMLElement, addinfo: x, other: y,touchDrag:(x:number,y:number)=>unknown=()=>{},touchEnd:(x:number,y:number)=>unknown=()=>{}){ bindContextmenu(
const func = (event: MouseEvent)=>{ obj: HTMLElement,
addinfo: x,
other: y,
touchDrag: (x: number, y: number) => unknown = () => {},
touchEnd: (x: number, y: number) => unknown = () => {},
) {
const func = (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.makemenu(event.clientX, event.clientY, addinfo, other); this.makemenu(event.clientX, event.clientY, addinfo, other);
}; };
obj.addEventListener("contextmenu", func); obj.addEventListener("contextmenu", func);
if(iOS){ if (iOS) {
let hold:NodeJS.Timeout|undefined; let hold: NodeJS.Timeout | undefined;
let x!:number; let x!: number;
let y!:number; let y!: number;
obj.addEventListener("touchstart",(event: TouchEvent)=>{ obj.addEventListener(
x=event.touches[0].pageX; "touchstart",
y=event.touches[0].pageY; (event: TouchEvent) => {
if(event.touches.length > 1){ x = event.touches[0].pageX;
event.preventDefault(); y = event.touches[0].pageY;
event.stopImmediatePropagation(); if (event.touches.length > 1) {
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); event.preventDefault();
}else{ event.stopImmediatePropagation();
//
event.stopImmediatePropagation();
hold=setTimeout(()=>{
if(lastx**2+lasty**2>10**2) return;
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
console.log(obj); } else {
},500) //
} event.stopImmediatePropagation();
},{passive: false}); hold = setTimeout(() => {
let lastx=0; if (lastx ** 2 + lasty ** 2 > 10 ** 2) return;
let lasty=0; this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
obj.addEventListener("touchend",()=>{ console.log(obj);
if(hold){ }, 500);
}
},
{passive: false},
);
let lastx = 0;
let lasty = 0;
obj.addEventListener("touchend", () => {
if (hold) {
clearTimeout(hold); clearTimeout(hold);
} }
touchEnd(lastx,lasty); touchEnd(lastx, lasty);
}); });
obj.addEventListener("touchmove",(event)=>{ obj.addEventListener("touchmove", (event) => {
lastx=event.touches[0].pageX-x; lastx = event.touches[0].pageX - x;
lasty=event.touches[0].pageY-y; lasty = event.touches[0].pageY - y;
touchDrag(lastx,lasty); touchDrag(lastx, lasty);
}); });
} }
return func; return func;
} }
static keepOnScreen(obj: HTMLElement){ static keepOnScreen(obj: HTMLElement) {
const html = document.documentElement.getBoundingClientRect(); const html = document.documentElement.getBoundingClientRect();
const docheight = html.height; const docheight = html.height;
const docwidth = html.width; const docwidth = html.width;
const box = obj.getBoundingClientRect(); const box = obj.getBoundingClientRect();
console.log(box, docheight, docwidth); console.log(box, docheight, docwidth);
if(box.right > docwidth){ if (box.right > docwidth) {
console.log("test"); console.log("test");
obj.style.left = docwidth - box.width + "px"; obj.style.left = docwidth - box.width + "px";
} }
if(box.bottom > docheight){ if (box.bottom > docheight) {
obj.style.top = docheight - box.height + "px"; obj.style.top = docheight - box.height + "px";
} }
} }
} }
Contextmenu.setup(); Contextmenu.setup();
export{ Contextmenu }; export {Contextmenu};

View file

@ -1,22 +1,22 @@
import{ Guild }from"./guild.js"; import {Guild} from "./guild.js";
import{ Channel }from"./channel.js"; import {Channel} from "./channel.js";
import{ Message }from"./message.js"; import {Message} from "./message.js";
import{ Localuser }from"./localuser.js"; import {Localuser} from "./localuser.js";
import{ User }from"./user.js"; import {User} from "./user.js";
import{channeljson,dirrectjson,memberjson,messagejson}from"./jsontypes.js"; import {channeljson, dirrectjson, memberjson, messagejson} from "./jsontypes.js";
import{ Permissions }from"./permissions.js"; import {Permissions} from "./permissions.js";
import{ SnowFlake }from"./snowflake.js"; import {SnowFlake} from "./snowflake.js";
import{ Contextmenu }from"./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import { Float, FormError } from "./settings.js"; import {Float, FormError} from "./settings.js";
class Direct extends Guild{ class Direct extends Guild {
declare channelids: { [key: string]: Group }; declare channelids: {[key: string]: Group};
channels: Group[]; channels: Group[];
getUnixTime(): number{ getUnixTime(): number {
throw new Error("Do not call this for Direct, it does not make sense"); throw new Error("Do not call this for Direct, it does not make sense");
} }
constructor(json: dirrectjson[], owner: Localuser){ constructor(json: dirrectjson[], owner: Localuser) {
super(-1, owner, null); super(-1, owner, null);
this.message_notifications = 0; this.message_notifications = 0;
this.owner = owner; this.owner = owner;
@ -29,7 +29,7 @@ class Direct extends Guild{
this.roleids = new Map(); this.roleids = new Map();
this.prevchannel = undefined; this.prevchannel = undefined;
this.properties.name = I18n.getTranslation("DMs.name"); this.properties.name = I18n.getTranslation("DMs.name");
for(const thing of json){ for (const thing of json) {
const temp = new Group(thing, this); const temp = new Group(thing, this);
this.channels.push(temp); this.channels.push(temp);
this.channelids[temp.id] = temp; this.channelids[temp.id] = temp;
@ -37,7 +37,7 @@ class Direct extends Guild{
} }
this.headchannels = this.channels; this.headchannels = this.channels;
} }
createChannelpac(json: any){ createChannelpac(json: any) {
const thischannel = new Group(json, this); const thischannel = new Group(json, this);
this.channelids[thischannel.id] = thischannel; this.channelids[thischannel.id] = thischannel;
this.channels.push(thischannel); this.channels.push(thischannel);
@ -46,266 +46,270 @@ class Direct extends Guild{
this.printServers(); this.printServers();
return thischannel; return thischannel;
} }
delChannel(json: channeljson){ delChannel(json: channeljson) {
const channel = this.channelids[json.id]; const channel = this.channelids[json.id];
super.delChannel(json); super.delChannel(json);
if(channel){ if (channel) {
channel.del(); channel.del();
} }
} }
getHTML(){ getHTML() {
const ddiv=document.createElement("div"); const ddiv = document.createElement("div");
const build=super.getHTML(); const build = super.getHTML();
const freindDiv=document.createElement("div"); const freindDiv = document.createElement("div");
freindDiv.classList.add("liststyle","flexltr","friendsbutton"); freindDiv.classList.add("liststyle", "flexltr", "friendsbutton");
const icon=document.createElement("span"); const icon = document.createElement("span");
icon.classList.add("svgicon","svg-friends","space"); icon.classList.add("svgicon", "svg-friends", "space");
freindDiv.append(icon); freindDiv.append(icon);
freindDiv.append(I18n.getTranslation("friends.friends")); freindDiv.append(I18n.getTranslation("friends.friends"));
ddiv.append(freindDiv); ddiv.append(freindDiv);
freindDiv.onclick=()=>{ freindDiv.onclick = () => {
this.loadChannel(null); this.loadChannel(null);
} };
ddiv.append(build); ddiv.append(build);
return ddiv; return ddiv;
} }
noChannel(addstate:boolean){ noChannel(addstate: boolean) {
if(addstate){ if (addstate) {
history.pushState([this.id,undefined], "", "/channels/" + this.id); history.pushState([this.id, undefined], "", "/channels/" + this.id);
} }
this.localuser.pageTitle(I18n.getTranslation("friends.friendlist")); this.localuser.pageTitle(I18n.getTranslation("friends.friendlist"));
const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement;
channelTopic.removeAttribute("hidden"); channelTopic.removeAttribute("hidden");
channelTopic.textContent=""; channelTopic.textContent = "";
const loading = document.getElementById("loadingdiv") as HTMLDivElement; const loading = document.getElementById("loadingdiv") as HTMLDivElement;
loading.classList.remove("loading"); loading.classList.remove("loading");
this.localuser.getSidePannel(); this.localuser.getSidePannel();
const messages = document.getElementById("channelw") as HTMLDivElement; const messages = document.getElementById("channelw") as HTMLDivElement;
for(const thing of Array.from(messages.getElementsByClassName("messagecontainer"))){ for (const thing of Array.from(messages.getElementsByClassName("messagecontainer"))) {
thing.remove(); thing.remove();
} }
const container=document.createElement("div"); const container = document.createElement("div");
container.classList.add("messagecontainer","flexttb","friendcontainer") container.classList.add("messagecontainer", "flexttb", "friendcontainer");
messages.append(container); messages.append(container);
const checkVoid=()=>{ const checkVoid = () => {
if(this.localuser.channelfocus!==undefined||this.localuser.lookingguild!==this){ if (this.localuser.channelfocus !== undefined || this.localuser.lookingguild !== this) {
this.localuser.relationshipsUpdate=()=>{}; this.localuser.relationshipsUpdate = () => {};
} }
} };
function genuserstrip(user:User,icons:HTMLElement):HTMLElement{ function genuserstrip(user: User, icons: HTMLElement): HTMLElement {
const div=document.createElement("div"); const div = document.createElement("div");
div.classList.add("flexltr","liststyle"); div.classList.add("flexltr", "liststyle");
user.bind(div); user.bind(div);
div.append(user.buildpfp()); div.append(user.buildpfp());
const userinfos=document.createElement("div"); const userinfos = document.createElement("div");
userinfos.classList.add("flexttb"); userinfos.classList.add("flexttb");
const username=document.createElement("span"); const username = document.createElement("span");
username.textContent=user.name; username.textContent = user.name;
userinfos.append(username,user.getStatus()); userinfos.append(username, user.getStatus());
div.append(userinfos); div.append(userinfos);
User.contextmenu.bindContextmenu(div,user,undefined); User.contextmenu.bindContextmenu(div, user, undefined);
userinfos.style.flexGrow="1"; userinfos.style.flexGrow = "1";
div.append(icons); div.append(icons);
return div; return div;
} }
{ {
//TODO update on users coming online //TODO update on users coming online
const online=document.createElement("button"); const online = document.createElement("button");
online.textContent=I18n.getTranslation("friends.online"); online.textContent = I18n.getTranslation("friends.online");
channelTopic.append(online); channelTopic.append(online);
const genOnline=()=>{ const genOnline = () => {
this.localuser.relationshipsUpdate=genOnline; this.localuser.relationshipsUpdate = genOnline;
checkVoid(); checkVoid();
container.innerHTML=""; container.innerHTML = "";
container.append(I18n.getTranslation("friends.online:")); container.append(I18n.getTranslation("friends.online:"));
for(const user of this.localuser.inrelation){ for (const user of this.localuser.inrelation) {
if(user.relationshipType===1&&user.online){ if (user.relationshipType === 1 && user.online) {
const buttonc=document.createElement("div"); const buttonc = document.createElement("div");
const button1=document.createElement("span"); const button1 = document.createElement("span");
button1.classList.add("svg-frmessage","svgicon"); button1.classList.add("svg-frmessage", "svgicon");
buttonc.append(button1); buttonc.append(button1);
buttonc.classList.add("friendlyButton"); buttonc.classList.add("friendlyButton");
buttonc.onclick=(e)=>{ buttonc.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.opendm(); user.opendm();
} };
container.append(genuserstrip(user,buttonc)); container.append(genuserstrip(user, buttonc));
} }
} }
} };
online.onclick=genOnline; online.onclick = genOnline;
genOnline(); genOnline();
} }
{ {
const all=document.createElement("button"); const all = document.createElement("button");
all.textContent=I18n.getTranslation("friends.all"); all.textContent = I18n.getTranslation("friends.all");
const genAll=()=>{ const genAll = () => {
this.localuser.relationshipsUpdate=genAll; this.localuser.relationshipsUpdate = genAll;
checkVoid(); checkVoid();
container.innerHTML=""; container.innerHTML = "";
container.append(I18n.getTranslation("friends.all:")); container.append(I18n.getTranslation("friends.all:"));
for(const user of this.localuser.inrelation){ for (const user of this.localuser.inrelation) {
if(user.relationshipType===1){ if (user.relationshipType === 1) {
const buttonc=document.createElement("div"); const buttonc = document.createElement("div");
const button1=document.createElement("span"); const button1 = document.createElement("span");
button1.classList.add("svg-frmessage","svgicon"); button1.classList.add("svg-frmessage", "svgicon");
buttonc.append(button1); buttonc.append(button1);
buttonc.classList.add("friendlyButton"); buttonc.classList.add("friendlyButton");
buttonc.onclick=(e)=>{ buttonc.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.opendm(); user.opendm();
} };
container.append(genuserstrip(user,buttonc)); container.append(genuserstrip(user, buttonc));
} }
} }
} };
all.onclick=genAll; all.onclick = genAll;
channelTopic.append(all); channelTopic.append(all);
} }
{ {
const pending=document.createElement("button"); const pending = document.createElement("button");
pending.textContent=I18n.getTranslation("friends.pending"); pending.textContent = I18n.getTranslation("friends.pending");
const genPending=()=>{ const genPending = () => {
this.localuser.relationshipsUpdate=genPending; this.localuser.relationshipsUpdate = genPending;
checkVoid(); checkVoid();
container.innerHTML=""; container.innerHTML = "";
container.append(I18n.getTranslation("friends.pending:")); container.append(I18n.getTranslation("friends.pending:"));
for(const user of this.localuser.inrelation){ for (const user of this.localuser.inrelation) {
if(user.relationshipType===3||user.relationshipType===4){ if (user.relationshipType === 3 || user.relationshipType === 4) {
const buttons=document.createElement("div"); const buttons = document.createElement("div");
buttons.classList.add("flexltr"); buttons.classList.add("flexltr");
const buttonc=document.createElement("div"); const buttonc = document.createElement("div");
const button1=document.createElement("span"); const button1 = document.createElement("span");
button1.classList.add("svgicon","svg-x"); button1.classList.add("svgicon", "svg-x");
if(user.relationshipType===3){ if (user.relationshipType === 3) {
const buttonc=document.createElement("div"); const buttonc = document.createElement("div");
const button2=document.createElement("span"); const button2 = document.createElement("span");
button2.classList.add("svgicon","svg-x"); button2.classList.add("svgicon", "svg-x");
button2.classList.add("svg-addfriend"); button2.classList.add("svg-addfriend");
buttonc.append(button2); buttonc.append(button2);
buttonc.classList.add("friendlyButton"); buttonc.classList.add("friendlyButton");
buttonc.append(button2); buttonc.append(button2);
buttons.append(buttonc); buttons.append(buttonc);
buttonc.onclick=(e)=>{ buttonc.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.changeRelationship(1); user.changeRelationship(1);
outerDiv.remove(); outerDiv.remove();
} };
} }
buttonc.append(button1); buttonc.append(button1);
buttonc.classList.add("friendlyButton"); buttonc.classList.add("friendlyButton");
buttonc.onclick=(e)=>{ buttonc.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.changeRelationship(0); user.changeRelationship(0);
outerDiv.remove(); outerDiv.remove();
} };
buttons.append(buttonc); buttons.append(buttonc);
const outerDiv=genuserstrip(user,buttons); const outerDiv = genuserstrip(user, buttons);
container.append(outerDiv); container.append(outerDiv);
} }
} }
} };
pending.onclick=genPending; pending.onclick = genPending;
channelTopic.append(pending); channelTopic.append(pending);
} }
{ {
const blocked=document.createElement("button"); const blocked = document.createElement("button");
blocked.textContent=I18n.getTranslation("friends.blocked"); blocked.textContent = I18n.getTranslation("friends.blocked");
const genBlocked=()=>{ const genBlocked = () => {
this.localuser.relationshipsUpdate=genBlocked; this.localuser.relationshipsUpdate = genBlocked;
checkVoid(); checkVoid();
container.innerHTML=""; container.innerHTML = "";
container.append(I18n.getTranslation("friends.blockedusers")); container.append(I18n.getTranslation("friends.blockedusers"));
for(const user of this.localuser.inrelation){ for (const user of this.localuser.inrelation) {
if(user.relationshipType===2){ if (user.relationshipType === 2) {
const buttonc=document.createElement("div"); const buttonc = document.createElement("div");
const button1=document.createElement("span"); const button1 = document.createElement("span");
button1.classList.add("svg-x","svgicon"); button1.classList.add("svg-x", "svgicon");
buttonc.append(button1); buttonc.append(button1);
buttonc.classList.add("friendlyButton"); buttonc.classList.add("friendlyButton");
buttonc.onclick=(e)=>{ buttonc.onclick = (e) => {
user.changeRelationship(0); user.changeRelationship(0);
e.stopImmediatePropagation(); e.stopImmediatePropagation();
outerDiv.remove(); outerDiv.remove();
} };
const outerDiv=genuserstrip(user,buttonc); const outerDiv = genuserstrip(user, buttonc);
container.append(outerDiv); container.append(outerDiv);
} }
} }
} };
blocked.onclick=genBlocked; blocked.onclick = genBlocked;
channelTopic.append(blocked); channelTopic.append(blocked);
} }
{ {
const add=document.createElement("button"); const add = document.createElement("button");
add.textContent=I18n.getTranslation("friends.addfriend"); add.textContent = I18n.getTranslation("friends.addfriend");
add.onclick=()=>{ add.onclick = () => {
this.localuser.relationshipsUpdate=()=>{}; this.localuser.relationshipsUpdate = () => {};
container.innerHTML=""; container.innerHTML = "";
const float=new Float(""); const float = new Float("");
const options=float.options; const options = float.options;
const form=options.addForm("",(e:any)=>{ const form = options.addForm(
console.log(e); "",
if(e.code===404){ (e: any) => {
throw new FormError(text,I18n.getTranslation("friends.notfound")); console.log(e);
}else if(e.code===400){ if (e.code === 404) {
throw new FormError(text,e.message.split("Error: ")[1]); throw new FormError(text, I18n.getTranslation("friends.notfound"));
}else{ } else if (e.code === 400) {
const box=text.input.deref(); throw new FormError(text, e.message.split("Error: ")[1]);
if(!box)return; } else {
box.value=""; const box = text.input.deref();
} if (!box) return;
},{ box.value = "";
method:"POST", }
fetchURL:this.info.api+"/users/@me/relationships", },
headers:this.headers {
}); method: "POST",
const text=form.addTextInput(I18n.getTranslation("friends.addfriendpromt"),"username"); fetchURL: this.info.api + "/users/@me/relationships",
form.addPreprocessor((obj:any)=>{ headers: this.headers,
const [username,discriminator]=obj.username.split("#"); },
obj.username=username; );
obj.discriminator=discriminator; const text = form.addTextInput(I18n.getTranslation("friends.addfriendpromt"), "username");
if(!discriminator){ form.addPreprocessor((obj: any) => {
throw new FormError(text,I18n.getTranslation("friends.discnotfound")); const [username, discriminator] = obj.username.split("#");
obj.username = username;
obj.discriminator = discriminator;
if (!discriminator) {
throw new FormError(text, I18n.getTranslation("friends.discnotfound"));
} }
}); });
container.append(float.generateHTML()); container.append(float.generateHTML());
} };
channelTopic.append(add); channelTopic.append(add);
} }
} }
get mentions(){ get mentions() {
let mentions=0; let mentions = 0;
for(const thing of this.localuser.inrelation){ for (const thing of this.localuser.inrelation) {
if(thing.relationshipType===3){ if (thing.relationshipType === 3) {
mentions+=1; mentions += 1;
} }
} }
return mentions; return mentions;
} }
giveMember(_member: memberjson){ giveMember(_member: memberjson) {
throw new Error("not a real guild, can't give member object"); throw new Error("not a real guild, can't give member object");
} }
getRole(/* ID: string */){ getRole(/* ID: string */) {
return null; return null;
} }
hasRole(/* r: string */){ hasRole(/* r: string */) {
return false; return false;
} }
isAdmin(){ isAdmin() {
return false; return false;
} }
unreaddms(){ unreaddms() {
for(const thing of this.channels){ for (const thing of this.channels) {
(thing as Group).unreads(); (thing as Group).unreads();
} }
} }
@ -335,34 +339,46 @@ dmPermissions.setPermission("STREAM", 1);
dmPermissions.setPermission("USE_VAD", 1); dmPermissions.setPermission("USE_VAD", 1);
// @ts-ignore I need to look into this lol // @ts-ignore I need to look into this lol
class Group extends Channel{ class Group extends Channel {
user: User; user: User;
static contextmenu = new Contextmenu<Group, undefined>("channel menu"); static contextmenu = new Contextmenu<Group, undefined>("channel menu");
static setupcontextmenu(){ static setupcontextmenu() {
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.copyId"), function(this: Group){ this.contextmenu.addbutton(
navigator.clipboard.writeText(this.id); () => I18n.getTranslation("DMs.copyId"),
}); function (this: Group) {
navigator.clipboard.writeText(this.id);
},
);
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.markRead"), function(this: Group){ this.contextmenu.addbutton(
this.readbottom(); () => I18n.getTranslation("DMs.markRead"),
}); function (this: Group) {
this.readbottom();
},
);
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.close"), function(this: Group){ this.contextmenu.addbutton(
this.deleteChannel(); () => I18n.getTranslation("DMs.close"),
}); function (this: Group) {
this.deleteChannel();
},
);
this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(){ this.contextmenu.addbutton(
navigator.clipboard.writeText(this.user.id); () => I18n.getTranslation("user.copyId"),
}); function () {
navigator.clipboard.writeText(this.user.id);
},
);
} }
constructor(json: dirrectjson, owner: Direct){ constructor(json: dirrectjson, owner: Direct) {
super(-1, owner, json.id); super(-1, owner, json.id);
this.owner = owner; this.owner = owner;
this.headers = this.guild.headers; this.headers = this.guild.headers;
this.name = json.recipients[0]?.username; this.name = json.recipients[0]?.username;
if(json.recipients[0]){ if (json.recipients[0]) {
this.user = new User(json.recipients[0], this.localuser); this.user = new User(json.recipients[0], this.localuser);
}else{ } else {
this.user = this.localuser.user; this.user = this.localuser.user;
} }
this.name ??= this.localuser.user.username; this.name ??= this.localuser.user.username;
@ -376,26 +392,26 @@ class Group extends Channel{
this.setUpInfiniteScroller(); this.setUpInfiniteScroller();
this.updatePosition(); this.updatePosition();
} }
updatePosition(){ updatePosition() {
if(this.lastmessageid){ if (this.lastmessageid) {
this.position = SnowFlake.stringToUnixTime(this.lastmessageid); this.position = SnowFlake.stringToUnixTime(this.lastmessageid);
}else{ } else {
this.position = 0; this.position = 0;
} }
this.position = -Math.max(this.position, this.getUnixTime()); this.position = -Math.max(this.position, this.getUnixTime());
} }
createguildHTML(){ createguildHTML() {
const div = document.createElement("div"); const div = document.createElement("div");
Group.contextmenu.bindContextmenu(div, this,undefined); Group.contextmenu.bindContextmenu(div, this, undefined);
this.html = new WeakRef(div); this.html = new WeakRef(div);
div.classList.add("flexltr","liststyle"); div.classList.add("flexltr", "liststyle");
const myhtml = document.createElement("span"); const myhtml = document.createElement("span");
myhtml.classList.add("ellipsis"); myhtml.classList.add("ellipsis");
myhtml.textContent = this.name; myhtml.textContent = this.name;
div.appendChild(this.user.buildpfp()); div.appendChild(this.user.buildpfp());
div.appendChild(myhtml); div.appendChild(myhtml);
(div as any).myinfo = this; (div as any).myinfo = this;
div.onclick = _=>{ div.onclick = (_) => {
this.getHTML(); this.getHTML();
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
toggle.checked = true; toggle.checked = true;
@ -403,56 +419,55 @@ class Group extends Channel{
return div; return div;
} }
async getHTML(addstate=true){ async getHTML(addstate = true) {
const id = ++Channel.genid; const id = ++Channel.genid;
if(this.localuser.channelfocus){ if (this.localuser.channelfocus) {
this.localuser.channelfocus.infinite.delete(); this.localuser.channelfocus.infinite.delete();
} }
if(this.guild !== this.localuser.lookingguild){ if (this.guild !== this.localuser.lookingguild) {
this.guild.loadGuild(); this.guild.loadGuild();
} }
this.guild.prevchannel = this; this.guild.prevchannel = this;
this.localuser.channelfocus = this; this.localuser.channelfocus = this;
const prom = this.infinite.delete(); const prom = this.infinite.delete();
if(addstate){ if (addstate) {
history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id); history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id);
} }
this.localuser.pageTitle("@" + this.name); this.localuser.pageTitle("@" + this.name);
(document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden", "");
const loading = document.getElementById("loadingdiv") as HTMLDivElement; const loading = document.getElementById("loadingdiv") as HTMLDivElement;
Channel.regenLoadingMessages(); Channel.regenLoadingMessages();
loading.classList.add("loading"); loading.classList.add("loading");
this.rendertyping(); this.rendertyping();
(document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true; (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + true;
(document.getElementById("upload") as HTMLElement).style.visibility="visible"; (document.getElementById("upload") as HTMLElement).style.visibility = "visible";
(document.getElementById("typediv") as HTMLElement).style.visibility="visible"; (document.getElementById("typediv") as HTMLElement).style.visibility = "visible";
(document.getElementById("typebox") as HTMLDivElement).focus(); (document.getElementById("typebox") as HTMLDivElement).focus();
await this.putmessages(); await this.putmessages();
await prom; await prom;
this.localuser.getSidePannel(); this.localuser.getSidePannel();
if(id !== Channel.genid){ if (id !== Channel.genid) {
return; return;
} }
this.buildmessages(); this.buildmessages();
} }
messageCreate(messagep: { d: messagejson }){ messageCreate(messagep: {d: messagejson}) {
this.mentions++; this.mentions++;
const messagez = new Message(messagep.d, this); const messagez = new Message(messagep.d, this);
if(this.lastmessageid){ if (this.lastmessageid) {
this.idToNext.set(this.lastmessageid, messagez.id); this.idToNext.set(this.lastmessageid, messagez.id);
this.idToPrev.set(messagez.id, this.lastmessageid); this.idToPrev.set(messagez.id, this.lastmessageid);
} }
this.lastmessageid = messagez.id; this.lastmessageid = messagez.id;
if(messagez.author === this.localuser.user){ if (messagez.author === this.localuser.user) {
this.lastreadmessageid = messagez.id; this.lastreadmessageid = messagez.id;
if(this.myhtml){ if (this.myhtml) {
this.myhtml.classList.remove("cunread"); this.myhtml.classList.remove("cunread");
} }
}else{ } else {
if(this.myhtml){ if (this.myhtml) {
this.myhtml.classList.add("cunread"); this.myhtml.classList.add("cunread");
} }
} }
@ -460,55 +475,55 @@ class Group extends Channel{
this.updatePosition(); this.updatePosition();
this.infinite.addedBottom(); this.infinite.addedBottom();
this.guild.sortchannels(); this.guild.sortchannels();
if(this.myhtml){ if (this.myhtml) {
const parrent = this.myhtml.parentElement as HTMLElement; const parrent = this.myhtml.parentElement as HTMLElement;
parrent.prepend(this.myhtml); parrent.prepend(this.myhtml);
} }
if(this === this.localuser.channelfocus){ if (this === this.localuser.channelfocus) {
if(!this.infinitefocus){ if (!this.infinitefocus) {
this.tryfocusinfinate(); this.tryfocusinfinate();
} }
this.infinite.addedBottom(); this.infinite.addedBottom();
} }
this.unreads(); this.unreads();
if(messagez.author === this.localuser.user){ if (messagez.author === this.localuser.user) {
this.mentions=0; this.mentions = 0;
return; return;
} }
if(this.localuser.lookingguild?.prevchannel === this && document.hasFocus()){ if (this.localuser.lookingguild?.prevchannel === this && document.hasFocus()) {
return; return;
} }
if(this.notification === "all"){ if (this.notification === "all") {
this.notify(messagez); this.notify(messagez);
}else if(this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)){ } else if (this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)) {
this.notify(messagez); this.notify(messagez);
} }
} }
notititle(message: Message){ notititle(message: Message) {
return message.author.username; return message.author.username;
} }
readbottom(){ readbottom() {
super.readbottom(); super.readbottom();
this.unreads(); this.unreads();
} }
all: WeakRef<HTMLElement> = new WeakRef(document.createElement("div")); all: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div")); noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
del(){ del() {
const all = this.all.deref(); const all = this.all.deref();
if(all){ if (all) {
all.remove(); all.remove();
} }
if(this.myhtml){ if (this.myhtml) {
this.myhtml.remove(); this.myhtml.remove();
} }
} }
unreads(){ unreads() {
const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime
const current = this.all.deref(); const current = this.all.deref();
if(this.hasunreads){ if (this.hasunreads) {
{ {
const noti = this.noti.deref(); const noti = this.noti.deref();
if(noti){ if (noti) {
noti.textContent = this.mentions + ""; noti.textContent = this.mentions + "";
return; return;
} }
@ -525,24 +540,24 @@ class Group extends Channel{
buildpfp.classList.add("mentioned"); buildpfp.classList.add("mentioned");
div.append(buildpfp); div.append(buildpfp);
sentdms.append(div); sentdms.append(div);
div.onclick = _=>{ div.onclick = (_) => {
this.guild.loadGuild(); this.guild.loadGuild();
this.getHTML(); this.getHTML();
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
toggle.checked = true; toggle.checked = true;
}; };
}else if(current){ } else if (current) {
current.remove(); current.remove();
}else{ } else {
} }
} }
isAdmin(): boolean{ isAdmin(): boolean {
return false; return false;
} }
hasPermission(name: string): boolean{ hasPermission(name: string): boolean {
return dmPermissions.hasPermission(name); return dmPermissions.hasPermission(name);
} }
} }
export{ Direct, Group }; export {Direct, Group};
Group.setupcontextmenu() Group.setupcontextmenu();

View file

@ -1,37 +1,37 @@
class ImagesDisplay{ class ImagesDisplay {
images:string[]; images: string[];
index=0; index = 0;
constructor(srcs:string[],index=0){ constructor(srcs: string[], index = 0) {
this.images=srcs; this.images = srcs;
this.index=index; this.index = index;
} }
weakbg=new WeakRef<HTMLElement>(document.createElement("div")); weakbg = new WeakRef<HTMLElement>(document.createElement("div"));
get background():HTMLElement|undefined{ get background(): HTMLElement | undefined {
return this.weakbg.deref(); return this.weakbg.deref();
} }
set background(e:HTMLElement){ set background(e: HTMLElement) {
this.weakbg=new WeakRef(e); this.weakbg = new WeakRef(e);
} }
makeHTML():HTMLElement{ makeHTML(): HTMLElement {
//TODO this should be able to display more than one image at a time lol //TODO this should be able to display more than one image at a time lol
const image= document.createElement("img"); const image = document.createElement("img");
image.src=this.images[this.index]; image.src = this.images[this.index];
image.classList.add("imgfit","centeritem"); image.classList.add("imgfit", "centeritem");
return image; return image;
} }
show(){ show() {
this.background = document.createElement("div"); this.background = document.createElement("div");
this.background.classList.add("background"); this.background.classList.add("background");
this.background.appendChild(this.makeHTML()); this.background.appendChild(this.makeHTML());
this.background.onclick = _=>{ this.background.onclick = (_) => {
this.hide(); this.hide();
}; };
document.body.append(this.background); document.body.append(this.background);
} }
hide(){ hide() {
if(this.background){ if (this.background) {
this.background.remove(); this.background.remove();
} }
} }
} }
export{ImagesDisplay} export {ImagesDisplay};

View file

@ -1,94 +1,85 @@
import{ Message }from"./message.js"; import {Message} from "./message.js";
import{ MarkDown }from"./markdown.js"; import {MarkDown} from "./markdown.js";
import{ embedjson, invitejson }from"./jsontypes.js"; import {embedjson, invitejson} from "./jsontypes.js";
import{ getapiurls, getInstances }from"./utils/utils.js"; import {getapiurls, getInstances} from "./utils/utils.js";
import{ Guild }from"./guild.js"; import {Guild} from "./guild.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import { ImagesDisplay } from "./disimg.js"; import {ImagesDisplay} from "./disimg.js";
class Embed{ class Embed {
type: string; type: string;
owner: Message; owner: Message;
json: embedjson; json: embedjson;
constructor(json: embedjson, owner: Message){ constructor(json: embedjson, owner: Message) {
this.type = this.getType(json); this.type = this.getType(json);
this.owner = owner; this.owner = owner;
this.json = json; this.json = json;
} }
getType(json: embedjson){ getType(json: embedjson) {
const instances = getInstances(); const instances = getInstances();
if( if (instances && json.type === "link" && json.url && URL.canParse(json.url)) {
instances &&
json.type === "link" &&
json.url &&
URL.canParse(json.url)
){
const Url = new URL(json.url); const Url = new URL(json.url);
for(const instance of instances){ for (const instance of instances) {
if(instance.url && URL.canParse(instance.url)){ if (instance.url && URL.canParse(instance.url)) {
const IUrl = new URL(instance.url); const IUrl = new URL(instance.url);
const params = new URLSearchParams(Url.search); const params = new URLSearchParams(Url.search);
let host: string; let host: string;
if(params.has("instance")){ if (params.has("instance")) {
const url = params.get("instance") as string; const url = params.get("instance") as string;
if(URL.canParse(url)){ if (URL.canParse(url)) {
host = new URL(url).host; host = new URL(url).host;
}else{ } else {
host = Url.host; host = Url.host;
} }
}else{ } else {
host = Url.host; host = Url.host;
} }
if(IUrl.host === host){ if (IUrl.host === host) {
const code = const code = Url.pathname.split("/")[Url.pathname.split("/").length - 1];
Url.pathname.split("/")[Url.pathname.split("/").length - 1];
json.invite = { json.invite = {
url: instance.url, url: instance.url,
code, code,
}; };
return"invite"; return "invite";
} }
} }
} }
} }
return json.type || "rich"; return json.type || "rich";
} }
generateHTML(){ generateHTML() {
switch(this.type){ switch (this.type) {
case"rich": case "rich":
return this.generateRich(); return this.generateRich();
case"image": case "image":
return this.generateImage(); return this.generateImage();
case"invite": case "invite":
return this.generateInvite(); return this.generateInvite();
case"link": case "link":
return this.generateLink(); return this.generateLink();
case"video": case "video":
case"article": case "article":
return this.generateArticle(); return this.generateArticle();
default: default:
console.warn( console.warn(`unsupported embed type ${this.type}, please add support dev :3`, this.json);
`unsupported embed type ${this.type}, please add support dev :3`, return document.createElement("div"); //prevent errors by giving blank div
this.json
);
return document.createElement("div"); //prevent errors by giving blank div
} }
} }
get message(){ get message() {
return this.owner; return this.owner;
} }
get channel(){ get channel() {
return this.message.channel; return this.message.channel;
} }
get guild(){ get guild() {
return this.channel.guild; return this.channel.guild;
} }
get localuser(){ get localuser() {
return this.guild.localuser; return this.guild.localuser;
} }
generateRich(){ generateRich() {
const div = document.createElement("div"); const div = document.createElement("div");
if(this.json.color){ if (this.json.color) {
div.style.backgroundColor = "#" + this.json.color.toString(16); div.style.backgroundColor = "#" + this.json.color.toString(16);
} }
div.classList.add("embed-color"); div.classList.add("embed-color");
@ -97,9 +88,9 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
embed.classList.add("embed"); embed.classList.add("embed");
div.append(embed); div.append(embed);
if(this.json.author){ if (this.json.author) {
const authorline = document.createElement("div"); const authorline = document.createElement("div");
if(this.json.author.icon_url){ if (this.json.author.icon_url) {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("embedimg"); img.classList.add("embedimg");
img.src = this.json.author.icon_url; img.src = this.json.author.icon_url;
@ -107,31 +98,31 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
} }
const a = document.createElement("a"); const a = document.createElement("a");
a.textContent = this.json.author.name as string; a.textContent = this.json.author.name as string;
if(this.json.author.url){ if (this.json.author.url) {
MarkDown.safeLink(a, this.json.author.url); MarkDown.safeLink(a, this.json.author.url);
} }
a.classList.add("username"); a.classList.add("username");
authorline.append(a); authorline.append(a);
embed.append(authorline); embed.append(authorline);
} }
if(this.json.title){ if (this.json.title) {
const title = document.createElement("a"); const title = document.createElement("a");
title.append(new MarkDown(this.json.title, this.channel).makeHTML()); title.append(new MarkDown(this.json.title, this.channel).makeHTML());
if(this.json.url){ if (this.json.url) {
MarkDown.safeLink(title, this.json.url); MarkDown.safeLink(title, this.json.url);
} }
title.classList.add("embedtitle"); title.classList.add("embedtitle");
embed.append(title); embed.append(title);
} }
if(this.json.description){ if (this.json.description) {
const p = document.createElement("p"); const p = document.createElement("p");
p.append(new MarkDown(this.json.description, this.channel).makeHTML()); p.append(new MarkDown(this.json.description, this.channel).makeHTML());
embed.append(p); embed.append(p);
} }
embed.append(document.createElement("br")); embed.append(document.createElement("br"));
if(this.json.fields){ if (this.json.fields) {
for(const thing of this.json.fields){ for (const thing of this.json.fields) {
const div = document.createElement("div"); const div = document.createElement("div");
const b = document.createElement("b"); const b = document.createElement("b");
b.textContent = thing.name; b.textContent = thing.name;
@ -141,31 +132,31 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
p.classList.add("embedp"); p.classList.add("embedp");
div.append(p); div.append(p);
if(thing.inline){ if (thing.inline) {
div.classList.add("inline"); div.classList.add("inline");
} }
embed.append(div); embed.append(div);
} }
} }
if(this.json.footer || this.json.timestamp){ if (this.json.footer || this.json.timestamp) {
const footer = document.createElement("div"); const footer = document.createElement("div");
if(this.json?.footer?.icon_url){ if (this.json?.footer?.icon_url) {
const img = document.createElement("img"); const img = document.createElement("img");
img.src = this.json.footer.icon_url; img.src = this.json.footer.icon_url;
img.classList.add("embedicon"); img.classList.add("embedicon");
footer.append(img); footer.append(img);
} }
if(this.json?.footer?.text){ if (this.json?.footer?.text) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = this.json.footer.text; span.textContent = this.json.footer.text;
footer.append(span); footer.append(span);
} }
if(this.json?.footer && this.json?.timestamp){ if (this.json?.footer && this.json?.timestamp) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = " • "; span.textContent = " • ";
footer.append(span); footer.append(span);
} }
if(this.json?.timestamp){ if (this.json?.timestamp) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = new Date(this.json.timestamp).toLocaleString(); span.textContent = new Date(this.json.timestamp).toLocaleString();
footer.append(span); footer.append(span);
@ -174,15 +165,15 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
} }
return div; return div;
} }
generateImage(){ generateImage() {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("messageimg"); img.classList.add("messageimg");
img.onclick = function(){ img.onclick = function () {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([img.src]);
full.show(); full.show();
}; };
img.src = this.json.thumbnail.proxy_url; img.src = this.json.thumbnail.proxy_url;
if(this.json.thumbnail.width){ if (this.json.thumbnail.width) {
let scale = 1; let scale = 1;
const max = 96 * 3; const max = 96 * 3;
scale = Math.max(scale, this.json.thumbnail.width / max); scale = Math.max(scale, this.json.thumbnail.width / max);
@ -195,12 +186,12 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
console.log(this.json, "Image fix"); console.log(this.json, "Image fix");
return img; return img;
} }
generateLink(){ generateLink() {
const table = document.createElement("table"); const table = document.createElement("table");
table.classList.add("embed", "linkembed"); table.classList.add("embed", "linkembed");
const trtop = document.createElement("tr"); const trtop = document.createElement("tr");
table.append(trtop); table.append(trtop);
if(this.json.url && this.json.title){ if (this.json.url && this.json.title) {
const td = document.createElement("td"); const td = document.createElement("td");
const a = document.createElement("a"); const a = document.createElement("a");
MarkDown.safeLink(a, this.json.url); MarkDown.safeLink(a, this.json.url);
@ -211,9 +202,9 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
{ {
const td = document.createElement("td"); const td = document.createElement("td");
const img = document.createElement("img"); const img = document.createElement("img");
if(this.json.thumbnail){ if (this.json.thumbnail) {
img.classList.add("embedimg"); img.classList.add("embedimg");
img.onclick = function(){ img.onclick = function () {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([img.src]);
full.show(); full.show();
}; };
@ -224,7 +215,7 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
} }
const bottomtr = document.createElement("tr"); const bottomtr = document.createElement("tr");
const td = document.createElement("td"); const td = document.createElement("td");
if(this.json.description){ if (this.json.description) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = this.json.description; span.textContent = this.json.description;
td.append(span); td.append(span);
@ -233,59 +224,62 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
table.append(bottomtr); table.append(bottomtr);
return table; return table;
} }
invcache: [invitejson, { cdn: string; api: string }] | undefined; invcache: [invitejson, {cdn: string; api: string}] | undefined;
generateInvite(){ generateInvite() {
if(this.invcache && (!this.json.invite || !this.localuser)){ if (this.invcache && (!this.json.invite || !this.localuser)) {
return this.generateLink(); return this.generateLink();
} }
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("embed", "inviteEmbed", "flexttb"); div.classList.add("embed", "inviteEmbed", "flexttb");
const json1 = this.json.invite; const json1 = this.json.invite;
(async ()=>{ (async () => {
let json: invitejson; let json: invitejson;
let info: { cdn: string; api: string }; let info: {cdn: string; api: string};
if(!this.invcache){ if (!this.invcache) {
if(!json1){ if (!json1) {
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
const tempinfo = await getapiurls(json1.url); const tempinfo = await getapiurls(json1.url);
if(!tempinfo){ if (!tempinfo) {
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
info = tempinfo; info = tempinfo;
const res = await fetch(info.api + "/invites/" + json1.code); const res = await fetch(info.api + "/invites/" + json1.code);
if(!res.ok){ if (!res.ok) {
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
json = (await res.json()) as invitejson; json = (await res.json()) as invitejson;
this.invcache = [json, info]; this.invcache = [json, info];
}else{ } else {
[json, info] = this.invcache; [json, info] = this.invcache;
} }
if(!json){ if (!json) {
div.append(this.generateLink()); div.append(this.generateLink());
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
return; return;
} }
if(json.guild.banner){ if (json.guild.banner) {
const banner = document.createElement("img"); const banner = document.createElement("img");
banner.src = this.localuser.info.cdn + "/icons/" + json.guild.id + "/" + json.guild.banner + ".png?size=256"; banner.src =
this.localuser.info.cdn +
"/icons/" +
json.guild.id +
"/" +
json.guild.banner +
".png?size=256";
banner.classList.add("banner"); banner.classList.add("banner");
div.append(banner); div.append(banner);
} }
const guild: invitejson["guild"] & { info?: { cdn: string } } = const guild: invitejson["guild"] & {info?: {cdn: string}} = json.guild;
json.guild;
guild.info = info; guild.info = info;
const icon = Guild.generateGuildIcon( const icon = Guild.generateGuildIcon(guild as invitejson["guild"] & {info: {cdn: string}});
guild as invitejson["guild"] & { info: { cdn: string } }
);
const iconrow = document.createElement("div"); const iconrow = document.createElement("div");
iconrow.classList.add("flexltr"); iconrow.classList.add("flexltr");
iconrow.append(icon); iconrow.append(icon);
@ -305,30 +299,30 @@ guild as invitejson["guild"] & { info: { cdn: string } }
div.append(iconrow); div.append(iconrow);
const h2 = document.createElement("h2"); const h2 = document.createElement("h2");
h2.textContent = I18n.getTranslation("invite.invitedBy",json.inviter.username); h2.textContent = I18n.getTranslation("invite.invitedBy", json.inviter.username);
div.append(h2); div.append(h2);
const button = document.createElement("button"); const button = document.createElement("button");
button.textContent = I18n.getTranslation("invite.accept"); button.textContent = I18n.getTranslation("invite.accept");
if(this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)){ if (this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)) {
button.textContent = I18n.getTranslation("invite.alreadyJoined"); button.textContent = I18n.getTranslation("invite.alreadyJoined");
button.disabled = true; button.disabled = true;
} }
button.classList.add("acceptinvbutton"); button.classList.add("acceptinvbutton");
div.append(button); div.append(button);
button.onclick = _=>{ button.onclick = (_) => {
if(this.localuser.info.api.startsWith(info.api)){ if (this.localuser.info.api.startsWith(info.api)) {
fetch(this.localuser.info.api + "/invites/" + json.code, { fetch(this.localuser.info.api + "/invites/" + json.code, {
method: "POST", method: "POST",
headers: this.localuser.headers, headers: this.localuser.headers,
}) })
.then(r=>r.json()) .then((r) => r.json())
.then(_=>{ .then((_) => {
if(_.message){ if (_.message) {
alert(_.message); alert(_.message);
} }
}); });
}else{ } else {
if(this.json.invite){ if (this.json.invite) {
const params = new URLSearchParams(""); const params = new URLSearchParams("");
params.set("instance", this.json.invite.url); params.set("instance", this.json.invite.url);
const encoded = params.toString(); const encoded = params.toString();
@ -340,33 +334,33 @@ guild as invitejson["guild"] & { info: { cdn: string } }
})(); })();
return div; return div;
} }
generateArticle(){ generateArticle() {
const colordiv = document.createElement("div"); const colordiv = document.createElement("div");
colordiv.style.backgroundColor = "#000000"; colordiv.style.backgroundColor = "#000000";
colordiv.classList.add("embed-color"); colordiv.classList.add("embed-color");
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("embed"); div.classList.add("embed");
if(this.json.provider){ if (this.json.provider) {
const provider = document.createElement("p"); const provider = document.createElement("p");
provider.classList.add("provider"); provider.classList.add("provider");
provider.textContent = this.json.provider.name; provider.textContent = this.json.provider.name;
div.append(provider); div.append(provider);
} }
const a = document.createElement("a"); const a = document.createElement("a");
if(this.json.url && this.json.url){ if (this.json.url && this.json.url) {
MarkDown.safeLink(a, this.json.url); MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.url; a.textContent = this.json.url;
div.append(a); div.append(a);
} }
if(this.json.description){ if (this.json.description) {
const description = document.createElement("p"); const description = document.createElement("p");
description.textContent = this.json.description; description.textContent = this.json.description;
div.append(description); div.append(description);
} }
if(this.json.thumbnail){ if (this.json.thumbnail) {
const img = document.createElement("img"); const img = document.createElement("img");
if(this.json.thumbnail.width && this.json.thumbnail.width){ if (this.json.thumbnail.width && this.json.thumbnail.width) {
let scale = 1; let scale = 1;
const inch = 96; const inch = 96;
scale = Math.max(scale, this.json.thumbnail.width / inch / 4); scale = Math.max(scale, this.json.thumbnail.width / inch / 4);
@ -377,21 +371,21 @@ guild as invitejson["guild"] & { info: { cdn: string } }
img.style.height = this.json.thumbnail.height + "px"; img.style.height = this.json.thumbnail.height + "px";
} }
img.classList.add("bigembedimg"); img.classList.add("bigembedimg");
if(this.json.video){ if (this.json.video) {
img.onclick = async ()=>{ img.onclick = async () => {
if(this.json.video){ if (this.json.video) {
img.remove(); img.remove();
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.src = this.json.video.url + "?autoplay=1"; iframe.src = this.json.video.url + "?autoplay=1";
if(this.json.thumbnail.width && this.json.thumbnail.width){ if (this.json.thumbnail.width && this.json.thumbnail.width) {
iframe.style.width = this.json.thumbnail.width + "px"; iframe.style.width = this.json.thumbnail.width + "px";
iframe.style.height = this.json.thumbnail.height + "px"; iframe.style.height = this.json.thumbnail.height + "px";
} }
div.append(iframe); div.append(iframe);
} }
}; };
}else{ } else {
img.onclick = async ()=>{ img.onclick = async () => {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([img.src]);
full.show(); full.show();
}; };
@ -403,4 +397,4 @@ guild as invitejson["guild"] & { info: { cdn: string } }
return colordiv; return colordiv;
} }
} }
export{ Embed }; export {Embed};

View file

@ -1,75 +1,73 @@
import{ Contextmenu }from"./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
import{ Guild }from"./guild.js"; import {Guild} from "./guild.js";
import { emojijson } from "./jsontypes.js"; import {emojijson} from "./jsontypes.js";
import{ Localuser }from"./localuser.js"; import {Localuser} from "./localuser.js";
import { BinRead } from "./utils/binaryUtils.js"; import {BinRead} from "./utils/binaryUtils.js";
//I need to recompile the emoji format for translation //I need to recompile the emoji format for translation
class Emoji{ class Emoji {
static emojis: { static emojis: {
name: string; name: string;
emojis: { emojis: {
name: string; name: string;
emoji: string; emoji: string;
}[]; }[];
}[]; }[];
name: string; name: string;
id?: string; id?: string;
emoji?:string; emoji?: string;
animated: boolean; animated: boolean;
owner: Guild | Localuser; owner: Guild | Localuser;
get guild(){ get guild() {
if(this.owner instanceof Guild){ if (this.owner instanceof Guild) {
return this.owner; return this.owner;
} }
return null; return null;
} }
get localuser(){ get localuser() {
if(this.owner instanceof Guild){ if (this.owner instanceof Guild) {
return this.owner.localuser; return this.owner.localuser;
}else{ } else {
return this.owner; return this.owner;
} }
} }
get info(){ get info() {
return this.owner.info; return this.owner.info;
} }
constructor( constructor(json: emojijson, owner: Guild | Localuser) {
json: emojijson,
owner: Guild | Localuser
){
this.name = json.name; this.name = json.name;
this.id = json.id; this.id = json.id;
this.animated = json.animated||false; this.animated = json.animated || false;
this.owner = owner; this.owner = owner;
this.emoji=json.emoji; this.emoji = json.emoji;
} }
getHTML(bigemoji: boolean = false){ getHTML(bigemoji: boolean = false) {
if(this.id){ if (this.id) {
const emojiElem = document.createElement("img"); const emojiElem = document.createElement("img");
emojiElem.classList.add("md-emoji"); emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
emojiElem.crossOrigin = "anonymous"; emojiElem.crossOrigin = "anonymous";
emojiElem.src =this.info.cdn+"/emojis/"+this.id+"."+(this.animated ? "gif" : "png")+"?size=32"; emojiElem.src =
this.info.cdn + "/emojis/" + this.id + "." + (this.animated ? "gif" : "png") + "?size=32";
emojiElem.alt = this.name; emojiElem.alt = this.name;
emojiElem.loading = "lazy"; emojiElem.loading = "lazy";
return emojiElem; return emojiElem;
}else if(this.emoji){ } else if (this.emoji) {
const emojiElem = document.createElement("span"); const emojiElem = document.createElement("span");
emojiElem.classList.add("md-emoji"); emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
emojiElem.textContent=this.emoji; emojiElem.textContent = this.emoji;
return emojiElem; return emojiElem;
}else{ } else {
throw new Error("This path should *never* be gone down, this means a malformed emoji"); throw new Error("This path should *never* be gone down, this means a malformed emoji");
} }
} }
static decodeEmojiList(buffer: ArrayBuffer){ static decodeEmojiList(buffer: ArrayBuffer) {
const reader=new BinRead(buffer) const reader = new BinRead(buffer);
const build: { name: string; emojis: { name: string; emoji: string }[] }[] = []; const build: {name: string; emojis: {name: string; emoji: string}[]}[] = [];
let cats = reader.read16(); let cats = reader.read16();
for(; cats !== 0; cats--){ for (; cats !== 0; cats--) {
const name = reader.readString16(); const name = reader.readString16();
const emojis: { const emojis: {
name: string; name: string;
@ -77,7 +75,7 @@ class Emoji{
emoji: string; emoji: string;
}[] = []; }[] = [];
let emojinumber = reader.read16(); let emojinumber = reader.read16();
for(; emojinumber !== 0; emojinumber--){ for (; emojinumber !== 0; emojinumber--) {
//console.log(emojis); //console.log(emojis);
const name = reader.readString8(); const name = reader.readString8();
const len = reader.read8(); const len = reader.read8();
@ -96,22 +94,18 @@ class Emoji{
} }
this.emojis = build; this.emojis = build;
} }
static grabEmoji(){ static grabEmoji() {
fetch("/emoji.bin") fetch("/emoji.bin")
.then(e=>{ .then((e) => {
return e.arrayBuffer(); return e.arrayBuffer();
}) })
.then(e=>{ .then((e) => {
Emoji.decodeEmojiList(e); Emoji.decodeEmojiList(e);
}); });
} }
static async emojiPicker( static async emojiPicker(x: number, y: number, localuser: Localuser): Promise<Emoji | string> {
x: number,
y: number,
localuser: Localuser
): Promise<Emoji | string>{
let res: (r: Emoji | string) => void; let res: (r: Emoji | string) => void;
const promise: Promise<Emoji | string> = new Promise(r=>{ const promise: Promise<Emoji | string> = new Promise((r) => {
res = r; res = r;
}); });
const menu = document.createElement("div"); const menu = document.createElement("div");
@ -130,33 +124,39 @@ class Emoji{
let isFirst = true; let isFirst = true;
localuser.guilds localuser.guilds
.filter(guild=>guild.id != "@me" && guild.emojis.length > 0) .filter((guild) => guild.id != "@me" && guild.emojis.length > 0)
.forEach(guild=>{ .forEach((guild) => {
const select = document.createElement("div"); const select = document.createElement("div");
select.classList.add("emojiSelect"); select.classList.add("emojiSelect");
if(guild.properties.icon){ if (guild.properties.icon) {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("pfp", "servericon", "emoji-server"); img.classList.add("pfp", "servericon", "emoji-server");
img.crossOrigin = "anonymous"; img.crossOrigin = "anonymous";
img.src = localuser.info.cdn+"/icons/"+guild.properties.id+"/"+guild.properties.icon+".png?size=48"; img.src =
localuser.info.cdn +
"/icons/" +
guild.properties.id +
"/" +
guild.properties.icon +
".png?size=48";
img.alt = "Server: " + guild.properties.name; img.alt = "Server: " + guild.properties.name;
select.appendChild(img); select.appendChild(img);
}else{ } else {
const div = document.createElement("span"); const div = document.createElement("span");
div.textContent = guild.properties.name div.textContent = guild.properties.name
.replace(/'s /g, " ") .replace(/'s /g, " ")
.replace(/\w+/g, word=>word[0]) .replace(/\w+/g, (word) => word[0])
.replace(/\s/g, ""); .replace(/\s/g, "");
select.append(div); select.append(div);
} }
selection.append(select); selection.append(select);
const clickEvent = ()=>{ const clickEvent = () => {
title.textContent = guild.properties.name; title.textContent = guild.properties.name;
body.innerHTML = ""; body.innerHTML = "";
for(const emojit of guild.emojis){ for (const emojit of guild.emojis) {
const emojiElem = document.createElement("div"); const emojiElem = document.createElement("div");
emojiElem.classList.add("emojiSelect"); emojiElem.classList.add("emojiSelect");
@ -166,14 +166,14 @@ class Emoji{
name: emojit.name, name: emojit.name,
animated: emojit.animated as boolean, animated: emojit.animated as boolean,
}, },
localuser localuser,
); );
emojiElem.append(emojiClass.getHTML()); emojiElem.append(emojiClass.getHTML());
body.append(emojiElem); body.append(emojiElem);
emojiElem.addEventListener("click", ()=>{ emojiElem.addEventListener("click", () => {
res(emojiClass); res(emojiClass);
if(Contextmenu.currentmenu !== ""){ if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
}); });
@ -181,14 +181,14 @@ class Emoji{
}; };
select.addEventListener("click", clickEvent); select.addEventListener("click", clickEvent);
if(isFirst){ if (isFirst) {
clickEvent(); clickEvent();
isFirst = false; isFirst = false;
} }
}); });
setTimeout(()=>{ setTimeout(() => {
if(Contextmenu.currentmenu != ""){ if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
document.body.append(menu); document.body.append(menu);
@ -197,29 +197,29 @@ class Emoji{
}, 10); }, 10);
let i = 0; let i = 0;
for(const thing of Emoji.emojis){ for (const thing of Emoji.emojis) {
const select = document.createElement("div"); const select = document.createElement("div");
select.textContent = thing.emojis[0].emoji; select.textContent = thing.emojis[0].emoji;
select.classList.add("emojiSelect"); select.classList.add("emojiSelect");
selection.append(select); selection.append(select);
const clickEvent = ()=>{ const clickEvent = () => {
title.textContent = thing.name; title.textContent = thing.name;
body.innerHTML = ""; body.innerHTML = "";
for(const emojit of thing.emojis){ for (const emojit of thing.emojis) {
const emoji = document.createElement("div"); const emoji = document.createElement("div");
emoji.classList.add("emojiSelect"); emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji; emoji.textContent = emojit.emoji;
body.append(emoji); body.append(emoji);
emoji.onclick = _=>{ emoji.onclick = (_) => {
res(emojit.emoji); res(emojit.emoji);
if(Contextmenu.currentmenu !== ""){ if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
}; };
} }
}; };
select.onclick = clickEvent; select.onclick = clickEvent;
if(i === 0){ if (i === 0) {
clickEvent(); clickEvent();
} }
i++; i++;
@ -228,40 +228,39 @@ class Emoji{
menu.append(body); menu.append(body);
return promise; return promise;
} }
static searchEmoji(search:string,localuser:Localuser,results=50):[Emoji,number][]{ static searchEmoji(search: string, localuser: Localuser, results = 50): [Emoji, number][] {
const ranked:[emojijson,number][]=[]; const ranked: [emojijson, number][] = [];
function similar(json:emojijson){ function similar(json: emojijson) {
if(json.name.includes(search)){ if (json.name.includes(search)) {
ranked.push([json,search.length/json.name.length]); ranked.push([json, search.length / json.name.length]);
return true; return true;
}else if(json.name.toLowerCase().includes(search.toLowerCase())){ } else if (json.name.toLowerCase().includes(search.toLowerCase())) {
ranked.push([json,search.length/json.name.length/1.4]); ranked.push([json, search.length / json.name.length / 1.4]);
return true; return true;
}else{ } else {
return false; return false;
} }
} }
for(const group of this.emojis){ for (const group of this.emojis) {
for(const emoji of group.emojis){ for (const emoji of group.emojis) {
similar(emoji) similar(emoji);
} }
} }
const weakGuild=new WeakMap<emojijson,Guild>(); const weakGuild = new WeakMap<emojijson, Guild>();
for(const guild of localuser.guilds){ for (const guild of localuser.guilds) {
if(guild.id!=="@me"&&guild.emojis.length!==0){ if (guild.id !== "@me" && guild.emojis.length !== 0) {
for(const emoji of guild.emojis){ for (const emoji of guild.emojis) {
if(similar(emoji)){ if (similar(emoji)) {
weakGuild.set(emoji,guild); weakGuild.set(emoji, guild);
}; }
} }
} }
} }
ranked.sort((a,b)=>b[1]-a[1]); ranked.sort((a, b) => b[1] - a[1]);
return ranked.splice(0,results).map(a=>{ return ranked.splice(0, results).map((a) => {
return [new Emoji(a[0],weakGuild.get(a[0])||localuser),a[1]]; return [new Emoji(a[0], weakGuild.get(a[0]) || localuser), a[1]];
});
})
} }
} }
Emoji.grabEmoji(); Emoji.grabEmoji();
export{ Emoji }; export {Emoji};

View file

@ -1,8 +1,8 @@
import{ Message }from"./message.js"; import {Message} from "./message.js";
import{ filejson }from"./jsontypes.js"; import {filejson} from "./jsontypes.js";
import { ImagesDisplay } from "./disimg.js"; import {ImagesDisplay} from "./disimg.js";
class File{ class File {
owner: Message | null; owner: Message | null;
id: string; id: string;
filename: string; filename: string;
@ -12,7 +12,7 @@ class File{
proxy_url: string | undefined; proxy_url: string | undefined;
url: string; url: string;
size: number; size: number;
constructor(fileJSON: filejson, owner: Message | null){ constructor(fileJSON: filejson, owner: Message | null) {
this.owner = owner; this.owner = owner;
this.id = fileJSON.id; this.id = fileJSON.id;
this.filename = fileJSON.filename; this.filename = fileJSON.filename;
@ -24,9 +24,9 @@ class File{
this.content_type = fileJSON.content_type; this.content_type = fileJSON.content_type;
this.size = fileJSON.size; this.size = fileJSON.size;
} }
getHTML(temp: boolean = false): HTMLElement{ getHTML(temp: boolean = false): HTMLElement {
const src = this.proxy_url || this.url; const src = this.proxy_url || this.url;
if(this.width && this.height){ if (this.width && this.height) {
let scale = 1; let scale = 1;
const max = 96 * 3; const max = 96 * 3;
scale = Math.max(scale, this.width / max); scale = Math.max(scale, this.width / max);
@ -34,35 +34,35 @@ class File{
this.width /= scale; this.width /= scale;
this.height /= scale; this.height /= scale;
} }
if(this.content_type.startsWith("image/")){ if (this.content_type.startsWith("image/")) {
const div = document.createElement("div"); const div = document.createElement("div");
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("messageimg"); img.classList.add("messageimg");
div.classList.add("messageimgdiv"); div.classList.add("messageimgdiv");
img.onclick = function(){ img.onclick = function () {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([img.src]);
full.show(); full.show();
}; };
img.src = src; img.src = src;
div.append(img); div.append(img);
if(this.width){ if (this.width) {
div.style.width = this.width + "px"; div.style.width = this.width + "px";
div.style.height = this.height + "px"; div.style.height = this.height + "px";
} }
return div; return div;
}else if(this.content_type.startsWith("video/")){ } else if (this.content_type.startsWith("video/")) {
const video = document.createElement("video"); const video = document.createElement("video");
const source = document.createElement("source"); const source = document.createElement("source");
source.src = src; source.src = src;
video.append(source); video.append(source);
source.type = this.content_type; source.type = this.content_type;
video.controls = !temp; video.controls = !temp;
if(this.width && this.height){ if (this.width && this.height) {
video.width = this.width; video.width = this.width;
video.height = this.height; video.height = this.height;
} }
return video; return video;
}else if(this.content_type.startsWith("audio/")){ } else if (this.content_type.startsWith("audio/")) {
const audio = document.createElement("audio"); const audio = document.createElement("audio");
const source = document.createElement("source"); const source = document.createElement("source");
source.src = src; source.src = src;
@ -70,11 +70,11 @@ class File{
source.type = this.content_type; source.type = this.content_type;
audio.controls = !temp; audio.controls = !temp;
return audio; return audio;
}else{ } else {
return this.createunknown(); return this.createunknown();
} }
} }
upHTML(files: Blob[], file: globalThis.File): HTMLElement{ upHTML(files: Blob[], file: globalThis.File): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
const contained = this.getHTML(true); const contained = this.getHTML(true);
div.classList.add("containedFile"); div.classList.add("containedFile");
@ -82,9 +82,9 @@ class File{
const controls = document.createElement("div"); const controls = document.createElement("div");
const garbage = document.createElement("button"); const garbage = document.createElement("button");
const icon = document.createElement("span"); const icon = document.createElement("span");
icon.classList.add("svgicon","svg-delete"); icon.classList.add("svgicon", "svg-delete");
garbage.append(icon); garbage.append(icon);
garbage.onclick = _=>{ garbage.onclick = (_) => {
div.remove(); div.remove();
files.splice(files.indexOf(file), 1); files.splice(files.indexOf(file), 1);
}; };
@ -93,7 +93,7 @@ class File{
controls.append(garbage); controls.append(garbage);
return div; return div;
} }
static initFromBlob(file: globalThis.File){ static initFromBlob(file: globalThis.File) {
return new File( return new File(
{ {
filename: file.name, filename: file.name,
@ -105,10 +105,10 @@ class File{
url: URL.createObjectURL(file), url: URL.createObjectURL(file),
proxy_url: undefined, proxy_url: undefined,
}, },
null null,
); );
} }
createunknown(): HTMLElement{ createunknown(): HTMLElement {
console.log("🗎"); console.log("🗎");
const src = this.proxy_url || this.url; const src = this.proxy_url || this.url;
const div = document.createElement("table"); const div = document.createElement("table");
@ -121,12 +121,12 @@ class File{
fileicon.classList.add("fileicon"); fileicon.classList.add("fileicon");
fileicon.rowSpan = 2; fileicon.rowSpan = 2;
const nametd = document.createElement("td"); const nametd = document.createElement("td");
if(src){ if (src) {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = src; a.href = src;
a.textContent = this.filename; a.textContent = this.filename;
nametd.append(a); nametd.append(a);
}else{ } else {
nametd.textContent = this.filename; nametd.textContent = this.filename;
} }
@ -140,11 +140,13 @@ class File{
div.appendChild(sizetr); div.appendChild(sizetr);
return div; return div;
} }
static filesizehuman(fsize: number){ static filesizehuman(fsize: number) {
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
return( return (
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 +
" " +
["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong
); );
} }
} }
export{ File }; export {File};

File diff suppressed because it is too large Load diff

View file

@ -1,39 +1,44 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description"> <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme" style="overflow-y: scroll;"> <body class="no-theme" style="overflow-y: scroll">
<div id="titleDiv"> <div id="titleDiv">
<img src="/logo.svg" width="40"> <img src="/logo.svg" width="40" />
<h1 id="pageTitle">Jank Client</h1> <h1 id="pageTitle">Jank Client</h1>
<a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" <a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" class="TitleButtons">
class="TitleButtons">
Spacebar Guild Spacebar Guild
</a> </a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> <a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
Github <a href="/channels/@me" class="TitleButtons" id="openClient"> Open Client </a>
</a>
<a href="/channels/@me" class="TitleButtons" id="openClient">
Open Client
</a>
</div> </div>
<div id="homePage"> <div id="homePage">
<h1 class="pagehead" id="welcomeJank">Welcome to Jank Client</h1> <h1 class="pagehead" id="welcomeJank">Welcome to Jank Client</h1>
<div class="pagebox"> <div class="pagebox">
<p id="box1title">Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:</p> <p id="box1title">
Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many
features including:
</p>
<ul id="box1Items"> <ul id="box1Items">
<li>Direct Messaging</li> <li>Direct Messaging</li>
<li>Reactions support</li> <li>Reactions support</li>
@ -47,18 +52,18 @@
</div> </div>
<div class="pagebox"> <div class="pagebox">
<h2 id="compatableInstances">Spacebar-Compatible Instances:</h2> <h2 id="compatableInstances">Spacebar-Compatible Instances:</h2>
<div id="instancebox"> <div id="instancebox"></div>
</div>
</div> </div>
<div class="pagebox"> <div class="pagebox">
<h2 id="box3title">Contribute to Jank Client</h2> <h2 id="box3title">Contribute to Jank Client</h2>
<p id="box3description">We always appreciate some help, whether that be in the form of bug reports, code, help translate, or even just pointing out some typos.</p><br> <p id="box3description">
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> We always appreciate some help, whether that be in the form of bug reports, code, help
Github translate, or even just pointing out some typos.
</a> </p>
<br />
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
</div> </div>
</div> </div>
</body> </body>
<script src="/home.js" type="module"></script> <script src="/home.js" type="module"></script>
</html> </html>

View file

@ -1,40 +1,56 @@
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import{ mobile }from"./utils/utils.js"; import {mobile} from "./utils/utils.js";
console.log(mobile); console.log(mobile);
const serverbox = document.getElementById("instancebox") as HTMLDivElement; const serverbox = document.getElementById("instancebox") as HTMLDivElement;
(async ()=>{ (async () => {
await I18n.done; await I18n.done;
const openClient=document.getElementById("openClient") const openClient = document.getElementById("openClient");
const welcomeJank=document.getElementById("welcomeJank") const welcomeJank = document.getElementById("welcomeJank");
const box1title=document.getElementById("box1title") const box1title = document.getElementById("box1title");
const box1Items=document.getElementById("box1Items") const box1Items = document.getElementById("box1Items");
const compatableInstances=document.getElementById("compatableInstances") const compatableInstances = document.getElementById("compatableInstances");
const box3title=document.getElementById("box3title") const box3title = document.getElementById("box3title");
const box3description=document.getElementById("box3description") const box3description = document.getElementById("box3description");
if(openClient&&welcomeJank&&compatableInstances&&box3title&&box3description&&box1title&&box1Items){ if (
openClient.textContent=I18n.getTranslation("htmlPages.openClient"); openClient &&
welcomeJank.textContent=I18n.getTranslation("htmlPages.welcomeJank"); welcomeJank &&
box1title.textContent=I18n.getTranslation("htmlPages.box1title"); compatableInstances &&
box3title &&
box3description &&
box1title &&
box1Items
) {
openClient.textContent = I18n.getTranslation("htmlPages.openClient");
welcomeJank.textContent = I18n.getTranslation("htmlPages.welcomeJank");
box1title.textContent = I18n.getTranslation("htmlPages.box1title");
compatableInstances.textContent=I18n.getTranslation("htmlPages.compatableInstances"); compatableInstances.textContent = I18n.getTranslation("htmlPages.compatableInstances");
box3title.textContent=I18n.getTranslation("htmlPages.box3title"); box3title.textContent = I18n.getTranslation("htmlPages.box3title");
box3description.textContent=I18n.getTranslation("htmlPages.box3description"); box3description.textContent = I18n.getTranslation("htmlPages.box3description");
const items=I18n.getTranslation("htmlPages.box1Items").split("|"); const items = I18n.getTranslation("htmlPages.box1Items").split("|");
let i=0; let i = 0;
//@ts-ignore ts is being dumb here //@ts-ignore ts is being dumb here
for(const item of box1Items.children){ for (const item of box1Items.children) {
(item as HTMLElement).textContent=items[i]; (item as HTMLElement).textContent = items[i];
i++; i++;
} }
}else{ } else {
console.error(openClient,welcomeJank,compatableInstances,box3title,box3description,box1title,box1Items) console.error(
openClient,
welcomeJank,
compatableInstances,
box3title,
box3description,
box1title,
box1Items,
);
} }
})() })();
fetch("/instances.json") fetch("/instances.json")
.then(_=>_.json()) .then((_) => _.json())
.then( .then(
async ( async (
json: { json: {
@ -45,76 +61,77 @@ fetch("/instances.json")
url?: string; url?: string;
display?: boolean; display?: boolean;
online?: boolean; online?: boolean;
uptime: { alltime: number; daytime: number; weektime: number }; uptime: {alltime: number; daytime: number; weektime: number};
urls: { urls: {
wellknown: string; wellknown: string;
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
login?: string; login?: string;
}; };
}[] }[],
)=>{ ) => {
await I18n.done; await I18n.done;
console.warn(json); console.warn(json);
for(const instance of json){ for (const instance of json) {
if(instance.display === false){ if (instance.display === false) {
continue; continue;
} }
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("flexltr", "instance"); div.classList.add("flexltr", "instance");
if(instance.image){ if (instance.image) {
const img = document.createElement("img"); const img = document.createElement("img");
img.src = instance.image; img.src = instance.image;
div.append(img); div.append(img);
} }
const statbox = document.createElement("div"); const statbox = document.createElement("div");
statbox.classList.add("flexttb","flexgrow"); statbox.classList.add("flexttb", "flexgrow");
{ {
const textbox = document.createElement("div"); const textbox = document.createElement("div");
textbox.classList.add("flexttb", "instancetextbox"); textbox.classList.add("flexttb", "instancetextbox");
const title = document.createElement("h2"); const title = document.createElement("h2");
title.innerText = instance.name; title.innerText = instance.name;
if(instance.online !== undefined){ if (instance.online !== undefined) {
const status = document.createElement("span"); const status = document.createElement("span");
status.innerText = instance.online ? "Online" : "Offline"; status.innerText = instance.online ? "Online" : "Offline";
status.classList.add("instanceStatus"); status.classList.add("instanceStatus");
title.append(status); title.append(status);
} }
textbox.append(title); textbox.append(title);
if(instance.description || instance.descriptionLong){ if (instance.description || instance.descriptionLong) {
const p = document.createElement("p"); const p = document.createElement("p");
if(instance.descriptionLong){ if (instance.descriptionLong) {
p.innerText = instance.descriptionLong; p.innerText = instance.descriptionLong;
}else if(instance.description){ } else if (instance.description) {
p.innerText = instance.description; p.innerText = instance.description;
} }
textbox.append(p); textbox.append(p);
} }
statbox.append(textbox); statbox.append(textbox);
} }
if(instance.uptime){ if (instance.uptime) {
const stats = document.createElement("div"); const stats = document.createElement("div");
stats.classList.add("flexltr"); stats.classList.add("flexltr");
const span = document.createElement("span"); const span = document.createElement("span");
span.innerText = I18n.getTranslation("home.uptimeStats",Math.round( span.innerText = I18n.getTranslation(
instance.uptime.alltime * 100 "home.uptimeStats",
)+"",Math.round( Math.round(instance.uptime.alltime * 100) + "",
instance.uptime.weektime * 100 Math.round(instance.uptime.weektime * 100) + "",
)+"",Math.round(instance.uptime.daytime * 100)+"") Math.round(instance.uptime.daytime * 100) + "",
);
stats.append(span); stats.append(span);
statbox.append(stats); statbox.append(stats);
} }
div.append(statbox); div.append(statbox);
div.onclick = _=>{ div.onclick = (_) => {
if(instance.online){ if (instance.online) {
window.location.href = "/register.html?instance=" + encodeURI(instance.name); window.location.href = "/register.html?instance=" + encodeURI(instance.name);
}else{ } else {
alert(I18n.getTranslation("home.warnOffiline")); alert(I18n.getTranslation("home.warnOffiline"));
} }
}; };
serverbox.append(div); serverbox.append(div);
} }
} },
); );

View file

@ -1,57 +1,58 @@
import { Contextmenu } from "./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
import { MarkDown } from "./markdown.js"; import {MarkDown} from "./markdown.js";
class Hover{ class Hover {
str:string|MarkDown|(()=>Promise<MarkDown|string>|MarkDown|string); str: string | MarkDown | (() => Promise<MarkDown | string> | MarkDown | string);
constructor(txt:string|MarkDown|(()=>Promise<MarkDown|string>|MarkDown|string)){ constructor(txt: string | MarkDown | (() => Promise<MarkDown | string> | MarkDown | string)) {
this.str=txt; this.str = txt;
} }
addEvent(elm:HTMLElement){ addEvent(elm: HTMLElement) {
let timeOut=setTimeout(()=>{},0); let timeOut = setTimeout(() => {}, 0);
let elm2=document.createElement("div"); let elm2 = document.createElement("div");
elm.addEventListener("mouseover",()=>{ elm.addEventListener("mouseover", () => {
timeOut=setTimeout(async ()=>{ timeOut = setTimeout(async () => {
elm2=await this.makeHover(elm); elm2 = await this.makeHover(elm);
},750) }, 750);
}); });
elm.addEventListener("mouseout",()=>{ elm.addEventListener("mouseout", () => {
clearTimeout(timeOut); clearTimeout(timeOut);
elm2.remove(); elm2.remove();
}); });
new MutationObserver(function (e) { new MutationObserver(function (e) {
if (e[0].removedNodes) { if (e[0].removedNodes) {
clearTimeout(timeOut); clearTimeout(timeOut);
elm2.remove(); elm2.remove();
}; }
}).observe(elm,{ childList: true }); }).observe(elm, {childList: true});
} }
async makeHover(elm:HTMLElement){ async makeHover(elm: HTMLElement) {
if(!document.contains(elm)) return document.createDocumentFragment() as unknown as HTMLDivElement; if (!document.contains(elm))
const div=document.createElement("div"); return document.createDocumentFragment() as unknown as HTMLDivElement;
if(this.str instanceof MarkDown){ const div = document.createElement("div");
div.append(this.str.makeHTML()) if (this.str instanceof MarkDown) {
}else if(this.str instanceof Function){ div.append(this.str.makeHTML());
const hover=await this.str(); } else if (this.str instanceof Function) {
if(hover instanceof MarkDown){ const hover = await this.str();
div.append(hover.makeHTML()); if (hover instanceof MarkDown) {
}else{ div.append(hover.makeHTML());
div.innerText=hover; } else {
} div.innerText = hover;
}else{ }
div.innerText=this.str; } else {
} div.innerText = this.str;
const box=elm.getBoundingClientRect(); }
div.style.top=(box.bottom+4)+"px"; const box = elm.getBoundingClientRect();
div.style.left=Math.floor(box.left+box.width/2)+"px"; div.style.top = box.bottom + 4 + "px";
div.classList.add("hoverthing"); div.style.left = Math.floor(box.left + box.width / 2) + "px";
div.style.opacity="0"; div.classList.add("hoverthing");
setTimeout(()=>{ div.style.opacity = "0";
div.style.opacity="1"; setTimeout(() => {
},10) div.style.opacity = "1";
document.body.append(div); }, 10);
Contextmenu.keepOnScreen(div); document.body.append(div);
console.log(div,elm); Contextmenu.keepOnScreen(div);
return div; console.log(div, elm);
} return div;
}
} }
export{Hover} export {Hover};

View file

@ -1,122 +1,118 @@
//@ts-ignore //@ts-ignore
import {langs} from "./translations/langs.js"; import {langs} from "./translations/langs.js";
const langmap=new Map<string,string>(); const langmap = new Map<string, string>();
for(const lang of Object.keys(langs) as string[]){ for (const lang of Object.keys(langs) as string[]) {
langmap.set(lang,langs[lang]); langmap.set(lang, langs[lang]);
} }
console.log(langs); console.log(langs);
type translation={ type translation = {
[key:string]:string|translation [key: string]: string | translation;
}; };
let res:()=>unknown=()=>{}; let res: () => unknown = () => {};
class I18n{ class I18n {
static lang:string; static lang: string;
static translations:translation[]=[]; static translations: translation[] = [];
static done=new Promise<void>((res2,_reject)=>{ static done = new Promise<void>((res2, _reject) => {
res=res2; res = res2;
}); });
static async create(lang:string){ static async create(lang: string) {
const json = (await (await fetch("/translations/" + lang + ".json")).json()) as translation;
const translations: translation[] = [];
translations.push(json);
if (lang !== "en") {
translations.push((await (await fetch("/translations/en.json")).json()) as translation);
}
this.lang = lang;
this.translations = translations;
const json=await (await fetch("/translations/"+lang+".json")).json() as translation; res();
const translations:translation[]=[]; }
translations.push(json); static getTranslation(msg: string, ...params: string[]): string {
if(lang!=="en"){ let str: string | undefined;
translations.push(await (await fetch("/translations/en.json")).json() as translation); const path = msg.split(".");
} for (const json of this.translations) {
this.lang=lang; let jsont: string | translation = json;
this.translations=translations; for (const thing of path) {
if (typeof jsont !== "string" && jsont !== undefined) {
jsont = jsont[thing];
} else {
jsont = json;
break;
}
}
res(); if (typeof jsont === "string") {
} str = jsont;
static getTranslation(msg:string,...params:string[]):string{ break;
let str:string|undefined; }
const path=msg.split("."); }
for(const json of this.translations){ if (str) {
let jsont:string|translation=json; return this.fillInBlanks(str, params);
for(const thing of path){ } else {
if(typeof jsont !== "string" && jsont!==undefined){ throw new Error(msg + " not found");
jsont=jsont[thing]; }
}
static fillInBlanks(msg: string, params: string[]): string {
//thanks to geotale for the regex
msg = msg.replace(/\$\d+/g, (match) => {
const number = Number(match.slice(1));
if (params[number - 1]) {
return params[number - 1];
} else {
return match;
}
});
msg = msg.replace(/{{(.+?)}}/g, (str, match: string) => {
const [op, strsSplit] = this.fillInBlanks(match, params).split(":");
const [first, ...strs] = strsSplit.split("|");
switch (op.toUpperCase()) {
case "PLURAL": {
const numb = Number(first);
if (numb === 0) {
return strs[strs.length - 1];
}
return strs[Math.min(strs.length - 1, numb - 1)];
}
case "GENDER": {
if (first === "male") {
return strs[0];
} else if (first === "female") {
return strs[1];
} else if (first === "neutral") {
if (strs[2]) {
return strs[2];
} else {
return strs[0];
}
}
}
}
return str;
});
}else{ return msg;
jsont=json; }
break; static options() {
} return [...langmap.keys()].map((e) => e.replace(".json", ""));
} }
static setLanguage(lang: string) {
if(typeof jsont === "string"){ if (this.options().indexOf(userLocale) !== -1) {
str=jsont; localStorage.setItem("lang", lang);
break; I18n.create(lang);
} }
} }
if(str){
return this.fillInBlanks(str,params);
}else{
throw new Error(msg+" not found")
}
}
static fillInBlanks(msg:string,params:string[]):string{
//thanks to geotale for the regex
msg=msg.replace(/\$\d+/g,(match) => {
const number=Number(match.slice(1));
if(params[number-1]){
return params[number-1];
}else{
return match;
}
});
msg=msg.replace(/{{(.+?)}}/g,
(str, match:string) => {
const [op,strsSplit]=this.fillInBlanks(match,params).split(":");
const [first,...strs]=strsSplit.split("|");
switch(op.toUpperCase()){
case "PLURAL":{
const numb=Number(first);
if(numb===0){
return strs[strs.length-1];
}
return strs[Math.min(strs.length-1,numb-1)];
}
case "GENDER":{
if(first==="male"){
return strs[0];
}else if(first==="female"){
return strs[1];
}else if(first==="neutral"){
if(strs[2]){
return strs[2];
}else{
return strs[0];
}
}
}
}
return str;
}
);
return msg;
}
static options(){
return [...langmap.keys()].map(e=>e.replace(".json",""));
}
static setLanguage(lang:string){
if(this.options().indexOf(userLocale)!==-1){
localStorage.setItem("lang",lang);
I18n.create(lang);
}
}
} }
console.log(langmap); console.log(langmap);
let userLocale = navigator.language.slice(0,2) || "en"; let userLocale = navigator.language.slice(0, 2) || "en";
if(I18n.options().indexOf(userLocale)===-1){ if (I18n.options().indexOf(userLocale) === -1) {
userLocale="en"; userLocale = "en";
} }
const storage=localStorage.getItem("lang"); const storage = localStorage.getItem("lang");
if(storage){ if (storage) {
userLocale=storage; userLocale = storage;
}else{ } else {
localStorage.setItem("lang",userLocale) localStorage.setItem("lang", userLocale);
} }
I18n.create(userLocale); I18n.create(userLocale);
export{I18n,langmap}; export {I18n, langmap};

View file

@ -1,23 +1,37 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description"> <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>body.no-theme,#loading{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme,#loading{background:#9397bd;}}</style> <style>
<link rel="manifest" href="/manifest.json"> body.no-theme,
#loading {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme,
#loading {
background: #9397bd;
}
}
</style>
<link rel="manifest" href="/manifest.json" />
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div id="loading" class="loading"> <div id="loading" class="loading">
<div class="centeritem"> <div class="centeritem">
<img src="/logo.svg" style="width:3in;height:3in;"> <img src="/logo.svg" style="width: 3in; height: 3in" />
<h1 id="loadingText">Jank Client is loading</h1> <h1 id="loadingText">Jank Client is loading</h1>
<h2 id="load-desc">This shouldn't take long</h2> <h2 id="load-desc">This shouldn't take long</h2>
<h1 id="switchaccounts">Switch Accounts</h1> <h1 id="switchaccounts">Switch Accounts</h1>
@ -36,7 +50,7 @@
</div> </div>
<div class="flexltr" id="userdock"> <div class="flexltr" id="userdock">
<div class="flexltr" id="userinfo"> <div class="flexltr" id="userinfo">
<img id="userpfp" class="pfp"> <img id="userpfp" class="pfp" />
<div class="flexttb userflex"> <div class="flexttb userflex">
<p id="username">USERNAME</p> <p id="username">USERNAME</p>
@ -55,8 +69,8 @@
<label for="maintoggle" id="maintoggleicon"> <label for="maintoggle" id="maintoggleicon">
<span class="svgicon svg-category"></span> <span class="svgicon svg-category"></span>
</label> </label>
<input type="checkbox" id="maintoggle"> <input type="checkbox" id="maintoggle" />
<span class="flexltr" style="align-items: center;"> <span class="flexltr" style="align-items: center">
<span id="channelname">Channel name</span> <span id="channelname">Channel name</span>
<span id="channelTopic" class="ellipsis" hidden>Channel topic</span> <span id="channelTopic" class="ellipsis" hidden>Channel topic</span>
</span> </span>
@ -64,15 +78,14 @@
<label for="memberlisttoggle" id="memberlisttoggleicon"> <label for="memberlisttoggle" id="memberlisttoggleicon">
<span class="svgicon svg-friends"></span> <span class="svgicon svg-friends"></span>
</label> </label>
<input type="checkbox" id="memberlisttoggle" checked> <input type="checkbox" id="memberlisttoggle" checked />
</div> </div>
<div class="flexltr flexgrow"> <div class="flexltr flexgrow">
<div class="flexttb flexgrow"> <div class="flexttb flexgrow">
<div id="channelw" class="flexltr"> <div id="channelw" class="flexltr">
<div id="loadingdiv"> <div id="loadingdiv"></div>
</div>
</div> </div>
<div style="position: relative;"> <div style="position: relative">
<div id="searchOptions" class="flexttb searchOptions"></div> <div id="searchOptions" class="flexttb searchOptions"></div>
</div> </div>
<div id="pasteimage" class="flexltr"></div> <div id="pasteimage" class="flexltr"></div>
@ -81,7 +94,7 @@
<div id="realbox"> <div id="realbox">
<div class="outerTypeBox"> <div class="outerTypeBox">
<span class="svg-upload svgicon" id="upload"></span> <span class="svg-upload svgicon" id="upload"></span>
<div id="typebox" contentEditable="true"></div> <div id="typebox" contenteditable="true"></div>
</div> </div>
</div> </div>
<div id="typing" class="hidden flexltr"> <div id="typing" class="hidden flexltr">

View file

@ -1,36 +1,36 @@
import{ Localuser }from"./localuser.js"; import {Localuser} from "./localuser.js";
import{ Contextmenu }from"./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
import{ mobile }from"./utils/utils.js"; import {mobile} from "./utils/utils.js";
import { getBulkUsers, setTheme, Specialuser } from "./utils/utils.js"; import {getBulkUsers, setTheme, Specialuser} from "./utils/utils.js";
import{ MarkDown }from"./markdown.js"; import {MarkDown} from "./markdown.js";
import{ Message }from"./message.js"; import {Message} from "./message.js";
import{File}from"./file.js"; import {File} from "./file.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
(async ()=>{ (async () => {
await I18n.done await I18n.done;
const users = getBulkUsers(); const users = getBulkUsers();
if(!users.currentuser){ if (!users.currentuser) {
window.location.href = "/login.html"; window.location.href = "/login.html";
return; return;
} }
{ {
const loadingText=document.getElementById("loadingText"); const loadingText = document.getElementById("loadingText");
const loaddesc=document.getElementById("load-desc"); const loaddesc = document.getElementById("load-desc");
const switchaccounts=document.getElementById("switchaccounts"); const switchaccounts = document.getElementById("switchaccounts");
const filedroptext=document.getElementById("filedroptext"); const filedroptext = document.getElementById("filedroptext");
if(loadingText&&loaddesc&&switchaccounts&&filedroptext){ if (loadingText && loaddesc && switchaccounts && filedroptext) {
loadingText.textContent=I18n.getTranslation("htmlPages.loadingText"); loadingText.textContent = I18n.getTranslation("htmlPages.loadingText");
loaddesc.textContent=I18n.getTranslation("htmlPages.loaddesc"); loaddesc.textContent = I18n.getTranslation("htmlPages.loaddesc");
switchaccounts.textContent=I18n.getTranslation("htmlPages.switchaccounts"); switchaccounts.textContent = I18n.getTranslation("htmlPages.switchaccounts");
filedroptext.textContent=I18n.getTranslation("uploadFilesText"); filedroptext.textContent = I18n.getTranslation("uploadFilesText");
} }
} }
I18n I18n;
function showAccountSwitcher(): void{ function showAccountSwitcher(): void {
const table = document.createElement("div"); const table = document.createElement("div");
table.classList.add("flexttb","accountSwitcher"); table.classList.add("flexttb", "accountSwitcher");
for(const user of Object.values(users.users)){ for (const user of Object.values(users.users)) {
const specialUser = user as Specialuser; const specialUser = user as Specialuser;
const userInfo = document.createElement("div"); const userInfo = document.createElement("div");
userInfo.classList.add("flexltr", "switchtable"); userInfo.classList.add("flexltr", "switchtable");
@ -55,7 +55,7 @@ import { I18n } from "./i18n.js";
userInfo.append(userDiv); userInfo.append(userDiv);
table.append(userInfo); table.append(userInfo);
userInfo.addEventListener("click", ()=>{ userInfo.addEventListener("click", () => {
thisUser.unload(); thisUser.unload();
thisUser.swapped = true; thisUser.swapped = true;
const loading = document.getElementById("loading") as HTMLDivElement; const loading = document.getElementById("loading") as HTMLDivElement;
@ -66,7 +66,7 @@ import { I18n } from "./i18n.js";
users.currentuser = specialUser.uid; users.currentuser = specialUser.uid;
localStorage.setItem("userinfos", JSON.stringify(users)); localStorage.setItem("userinfos", JSON.stringify(users));
thisUser.initwebsocket().then(()=>{ thisUser.initwebsocket().then(() => {
thisUser.loaduser(); thisUser.loaduser();
thisUser.init(); thisUser.init();
loading.classList.add("doneloading"); loading.classList.add("doneloading");
@ -81,12 +81,12 @@ import { I18n } from "./i18n.js";
const switchAccountDiv = document.createElement("div"); const switchAccountDiv = document.createElement("div");
switchAccountDiv.classList.add("switchtable"); switchAccountDiv.classList.add("switchtable");
switchAccountDiv.textContent = I18n.getTranslation("switchAccounts"); switchAccountDiv.textContent = I18n.getTranslation("switchAccounts");
switchAccountDiv.addEventListener("click", ()=>{ switchAccountDiv.addEventListener("click", () => {
window.location.href = "/login.html"; window.location.href = "/login.html";
}); });
table.append(switchAccountDiv); table.append(switchAccountDiv);
if(Contextmenu.currentmenu){ if (Contextmenu.currentmenu) {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
Contextmenu.currentmenu = table; Contextmenu.currentmenu = table;
@ -94,22 +94,22 @@ import { I18n } from "./i18n.js";
} }
const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; const userInfoElement = document.getElementById("userinfo") as HTMLDivElement;
userInfoElement.addEventListener("click", event=>{ userInfoElement.addEventListener("click", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
const switchAccountsElement = document.getElementById("switchaccounts") as HTMLDivElement; const switchAccountsElement = document.getElementById("switchaccounts") as HTMLDivElement;
switchAccountsElement.addEventListener("click", event=>{ switchAccountsElement.addEventListener("click", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
let thisUser: Localuser; let thisUser: Localuser;
try{ try {
console.log(users.users, users.currentuser); console.log(users.users, users.currentuser);
thisUser = new Localuser(users.users[users.currentuser]); thisUser = new Localuser(users.users[users.currentuser]);
thisUser.initwebsocket().then(()=>{ thisUser.initwebsocket().then(() => {
thisUser.loaduser(); thisUser.loaduser();
thisUser.init(); thisUser.init();
const loading = document.getElementById("loading") as HTMLDivElement; const loading = document.getElementById("loading") as HTMLDivElement;
@ -117,62 +117,65 @@ import { I18n } from "./i18n.js";
loading.classList.remove("loading"); loading.classList.remove("loading");
console.log("done loading"); console.log("done loading");
}); });
}catch(e){ } catch (e) {
console.error(e); console.error(e);
(document.getElementById("load-desc") as HTMLSpanElement).textContent = I18n.getTranslation("accountNotStart"); (document.getElementById("load-desc") as HTMLSpanElement).textContent =
I18n.getTranslation("accountNotStart");
thisUser = new Localuser(-1); thisUser = new Localuser(-1);
} }
const menu = new Contextmenu<void,void>("create rightclick"); const menu = new Contextmenu<void, void>("create rightclick");
menu.addbutton( menu.addbutton(
I18n.getTranslation("channel.createChannel"), I18n.getTranslation("channel.createChannel"),
()=>{ () => {
if(thisUser.lookingguild){ if (thisUser.lookingguild) {
thisUser.lookingguild.createchannels(); thisUser.lookingguild.createchannels();
} }
}, },
null, null,
()=>thisUser.isAdmin() () => thisUser.isAdmin(),
); );
menu.addbutton( menu.addbutton(
I18n.getTranslation("channel.createCatagory"), I18n.getTranslation("channel.createCatagory"),
()=>{ () => {
if(thisUser.lookingguild){ if (thisUser.lookingguild) {
thisUser.lookingguild.createcategory(); thisUser.lookingguild.createcategory();
} }
}, },
null, null,
()=>thisUser.isAdmin() () => thisUser.isAdmin(),
); );
menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement); menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement);
const pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement; const pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement;
let replyingTo: Message | null = null; let replyingTo: Message | null = null;
window.addEventListener("popstate",(e)=>{ window.addEventListener("popstate", (e) => {
if(e.state instanceof Object){ if (e.state instanceof Object) {
thisUser.goToChannel(e.state[1],false); thisUser.goToChannel(e.state[1], false);
} }
//console.log(e.state,"state:3") //console.log(e.state,"state:3")
}) });
async function handleEnter(event: KeyboardEvent): Promise<void>{ async function handleEnter(event: KeyboardEvent): Promise<void> {
if(thisUser.keyup(event)){return} if (thisUser.keyup(event)) {
return;
}
const channel = thisUser.channelfocus; const channel = thisUser.channelfocus;
if(!channel)return; if (!channel) return;
if(markdown.rawString===""&&event.key==="ArrowUp"){ if (markdown.rawString === "" && event.key === "ArrowUp") {
channel.editLast(); channel.editLast();
return; return;
} }
channel.typingstart(); channel.typingstart();
if(event.key === "Enter" && !event.shiftKey){ if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault(); event.preventDefault();
replyingTo = thisUser.channelfocus? thisUser.channelfocus.replyingto: null; replyingTo = thisUser.channelfocus ? thisUser.channelfocus.replyingto : null;
if(replyingTo?.div){ if (replyingTo?.div) {
replyingTo.div.classList.remove("replying"); replyingTo.div.classList.remove("replying");
} }
if(thisUser.channelfocus){ if (thisUser.channelfocus) {
thisUser.channelfocus.replyingto = null; thisUser.channelfocus.replyingto = null;
} }
channel.sendMessage(markdown.rawString, { channel.sendMessage(markdown.rawString, {
@ -181,10 +184,10 @@ import { I18n } from "./i18n.js";
embeds: [], // Add an empty array for the embeds property embeds: [], // Add an empty array for the embeds property
replyingto: replyingTo, replyingto: replyingTo,
}); });
if(thisUser.channelfocus){ if (thisUser.channelfocus) {
thisUser.channelfocus.makereplybox(); thisUser.channelfocus.makereplybox();
} }
while(images.length){ while (images.length) {
images.pop(); images.pop();
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);
} }
@ -193,15 +196,17 @@ import { I18n } from "./i18n.js";
} }
} }
interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown;
}
const typebox = document.getElementById("typebox") as CustomHTMLDivElement; const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
typebox.markdown = markdown; typebox.markdown = markdown;
typebox.addEventListener("keyup", handleEnter); typebox.addEventListener("keyup", handleEnter);
typebox.addEventListener("keydown", event=>{ typebox.addEventListener("keydown", (event) => {
thisUser.keydown(event) thisUser.keydown(event);
if(event.key === "Enter" && !event.shiftKey) event.preventDefault(); if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
}); });
markdown.giveBox(typebox); markdown.giveBox(typebox);
{ {
@ -209,30 +214,27 @@ import { I18n } from "./i18n.js";
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
searchBox.markdown = markdown; searchBox.markdown = markdown;
searchBox.addEventListener("keydown", event=>{ searchBox.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
if(event.key === "Enter") {
event.preventDefault(); event.preventDefault();
thisUser.mSearch(markdown.rawString) thisUser.mSearch(markdown.rawString);
}; }
}); });
markdown.giveBox(searchBox); markdown.giveBox(searchBox);
markdown.setCustomBox((e)=>{ markdown.setCustomBox((e) => {
const span=document.createElement("span"); const span = document.createElement("span");
span.textContent=e.replace("\n",""); span.textContent = e.replace("\n", "");
return span; return span;
}); });
} }
const images: Blob[] = []; const images: Blob[] = [];
const imagesHtml: HTMLElement[] = []; const imagesHtml: HTMLElement[] = [];
document.addEventListener("paste", async (e: ClipboardEvent)=>{ document.addEventListener("paste", async (e: ClipboardEvent) => {
if(!e.clipboardData)return; if (!e.clipboardData) return;
for(const file of Array.from(e.clipboardData.files)){ for (const file of Array.from(e.clipboardData.files)) {
const fileInstance = File.initFromBlob(file); const fileInstance = File.initFromBlob(file);
e.preventDefault(); e.preventDefault();
const html = fileInstance.upHTML(images, file); const html = fileInstance.upHTML(images, file);
@ -244,60 +246,59 @@ import { I18n } from "./i18n.js";
setTheme(); setTheme();
function userSettings(): void{ function userSettings(): void {
thisUser.showusersettings(); thisUser.showusersettings();
} }
(document.getElementById("settings") as HTMLImageElement).onclick = (document.getElementById("settings") as HTMLImageElement).onclick = userSettings;
userSettings;
if(mobile){ if (mobile) {
const channelWrapper = document.getElementById("channelw") as HTMLDivElement; const channelWrapper = document.getElementById("channelw") as HTMLDivElement;
channelWrapper.onclick = ()=>{ channelWrapper.onclick = () => {
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
toggle.checked = true; toggle.checked = true;
}; };
const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement; const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement;
memberListToggle.checked = false; memberListToggle.checked = false;
} }
let dragendtimeout=setTimeout(()=>{}) let dragendtimeout = setTimeout(() => {});
document.addEventListener("dragover",(e)=>{ document.addEventListener("dragover", (e) => {
clearTimeout(dragendtimeout); clearTimeout(dragendtimeout);
const data = e.dataTransfer; const data = e.dataTransfer;
const bg=document.getElementById("gimmefile") as HTMLDivElement; const bg = document.getElementById("gimmefile") as HTMLDivElement;
if(data){ if (data) {
const isfile=data.types.includes("Files")||data.types.includes("application/x-moz-file"); const isfile = data.types.includes("Files") || data.types.includes("application/x-moz-file");
if(!isfile){ if (!isfile) {
bg.hidden=true; bg.hidden = true;
return; return;
} }
e.preventDefault(); e.preventDefault();
bg.hidden=false; bg.hidden = false;
//console.log(data.types,data) //console.log(data.types,data)
}else{ } else {
bg.hidden=true; bg.hidden = true;
} }
}); });
document.addEventListener("dragleave",(_)=>{ document.addEventListener("dragleave", (_) => {
dragendtimeout=setTimeout(()=>{ dragendtimeout = setTimeout(() => {
const bg=document.getElementById("gimmefile") as HTMLDivElement; const bg = document.getElementById("gimmefile") as HTMLDivElement;
bg.hidden=true; bg.hidden = true;
},1000) }, 1000);
}); });
document.addEventListener("dragenter",(e)=>{ document.addEventListener("dragenter", (e) => {
e.preventDefault(); e.preventDefault();
}) });
document.addEventListener("drop",e=>{ document.addEventListener("drop", (e) => {
const data = e.dataTransfer; const data = e.dataTransfer;
const bg=document.getElementById("gimmefile") as HTMLDivElement; const bg = document.getElementById("gimmefile") as HTMLDivElement;
bg.hidden=true; bg.hidden = true;
if(data){ if (data) {
const isfile=data.types.includes("Files")||data.types.includes("application/x-moz-file"); const isfile = data.types.includes("Files") || data.types.includes("application/x-moz-file");
if(isfile){ if (isfile) {
e.preventDefault(); e.preventDefault();
console.log(data.files); console.log(data.files);
for(const file of Array.from(data.files)){ for (const file of Array.from(data.files)) {
const fileInstance = File.initFromBlob(file); const fileInstance = File.initFromBlob(file);
const html = fileInstance.upHTML(images, file); const html = fileInstance.upHTML(images, file);
pasteImageElement.appendChild(html); pasteImageElement.appendChild(html);
@ -307,14 +308,14 @@ import { I18n } from "./i18n.js";
} }
} }
}); });
(document.getElementById("upload") as HTMLElement).onclick=()=>{ (document.getElementById("upload") as HTMLElement).onclick = () => {
const input=document.createElement("input"); const input = document.createElement("input");
input.type="file"; input.type = "file";
input.click(); input.click();
console.log("clicked") console.log("clicked");
input.onchange=(() => { input.onchange = () => {
if(input.files){ if (input.files) {
for(const file of Array.from(input.files)){ for (const file of Array.from(input.files)) {
const fileInstance = File.initFromBlob(file); const fileInstance = File.initFromBlob(file);
const html = fileInstance.upHTML(images, file); const html = fileInstance.upHTML(images, file);
pasteImageElement.appendChild(html); pasteImageElement.appendChild(html);
@ -322,7 +323,6 @@ import { I18n } from "./i18n.js";
imagesHtml.push(html); imagesHtml.push(html);
} }
} }
}) };
} };
})(); })();

View file

@ -1,8 +1,5 @@
class InfiniteScroller{ class InfiniteScroller {
readonly getIDFromOffset: ( readonly getIDFromOffset: (ID: string, offset: number) => Promise<string | undefined>;
ID: string,
offset: number
) => Promise<string | undefined>;
readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>; readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>;
readonly destroyFromID: (ID: string) => Promise<boolean>; readonly destroyFromID: (ID: string) => Promise<boolean>;
readonly reachesBottom: () => void; readonly reachesBottom: () => void;
@ -19,39 +16,39 @@ offset: number
averageheight = 60; averageheight = 60;
watchtime = false; watchtime = false;
changePromise: Promise<boolean> | undefined; changePromise: Promise<boolean> | undefined;
scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; scollDiv!: {scrollTop: number; scrollHeight: number; clientHeight: number};
resetVars(){ resetVars() {
this.scrollTop=0; this.scrollTop = 0;
this.scrollBottom=0; this.scrollBottom = 0;
this.averageheight=60; this.averageheight = 60;
this.watchtime=false; this.watchtime = false;
this.needsupdate=true; this.needsupdate = true;
this.beenloaded=false; this.beenloaded = false;
this.changePromise=undefined; this.changePromise = undefined;
if(this.timeout){ if (this.timeout) {
clearTimeout(this.timeout); clearTimeout(this.timeout);
this.timeout=null; this.timeout = null;
} }
for(const thing of this.HTMLElements){ for (const thing of this.HTMLElements) {
this.destroyFromID(thing[1]); this.destroyFromID(thing[1]);
} }
this.HTMLElements=[]; this.HTMLElements = [];
} }
constructor( constructor(
getIDFromOffset: InfiniteScroller["getIDFromOffset"], getIDFromOffset: InfiniteScroller["getIDFromOffset"],
getHTMLFromID: InfiniteScroller["getHTMLFromID"], getHTMLFromID: InfiniteScroller["getHTMLFromID"],
destroyFromID: InfiniteScroller["destroyFromID"], destroyFromID: InfiniteScroller["destroyFromID"],
reachesBottom: InfiniteScroller["reachesBottom"] = ()=>{} reachesBottom: InfiniteScroller["reachesBottom"] = () => {},
){ ) {
this.getIDFromOffset = getIDFromOffset; this.getIDFromOffset = getIDFromOffset;
this.getHTMLFromID = getHTMLFromID; this.getHTMLFromID = getHTMLFromID;
this.destroyFromID = destroyFromID; this.destroyFromID = destroyFromID;
this.reachesBottom = reachesBottom; this.reachesBottom = reachesBottom;
} }
async getDiv(initialId: string): Promise<HTMLDivElement>{ async getDiv(initialId: string): Promise<HTMLDivElement> {
if(this.div){ if (this.div) {
throw new Error("Div already exists, exiting."); throw new Error("Div already exists, exiting.");
} }
this.resetVars(); this.resetVars();
@ -59,24 +56,24 @@ offset: number
scroll.classList.add("scroller"); scroll.classList.add("scroller");
this.div = scroll; this.div = scroll;
this.div.addEventListener("scroll", ()=>{ this.div.addEventListener("scroll", () => {
this.checkscroll(); this.checkscroll();
if(this.scrollBottom < 5){ if (this.scrollBottom < 5) {
this.scrollBottom = 5; this.scrollBottom = 5;
} }
if(this.timeout === null){ if (this.timeout === null) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
this.watchForChange(); this.watchForChange();
}); });
let oldheight = 0; let oldheight = 0;
new ResizeObserver(()=>{ new ResizeObserver(() => {
this.checkscroll(); this.checkscroll();
const func = this.snapBottom(); const func = this.snapBottom();
this.updatestuff(); this.updatestuff();
const change = oldheight - scroll.offsetHeight; const change = oldheight - scroll.offsetHeight;
if(change > 0 && this.div){ if (change > 0 && this.div) {
this.div.scrollTop += change; this.div.scrollTop += change;
} }
oldheight = scroll.offsetHeight; oldheight = scroll.offsetHeight;
@ -88,7 +85,7 @@ offset: number
await this.firstElement(initialId); await this.firstElement(initialId);
this.updatestuff(); this.updatestuff();
await this.watchForChange().then(()=>{ await this.watchForChange().then(() => {
this.updatestuff(); this.updatestuff();
this.beenloaded = true; this.beenloaded = true;
}); });
@ -96,76 +93,72 @@ offset: number
return scroll; return scroll;
} }
checkscroll(): void{ checkscroll(): void {
if(this.beenloaded && this.div && !document.body.contains(this.div)){ if (this.beenloaded && this.div && !document.body.contains(this.div)) {
console.warn("not in document"); console.warn("not in document");
this.div = null; this.div = null;
} }
} }
async updatestuff(): Promise<void>{ async updatestuff(): Promise<void> {
this.timeout = null; this.timeout = null;
if(!this.div)return; if (!this.div) return;
this.scrollBottom = this.scrollBottom = this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight;
this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight;
this.averageheight = this.div.scrollHeight / this.HTMLElements.length; this.averageheight = this.div.scrollHeight / this.HTMLElements.length;
if(this.averageheight < 10){ if (this.averageheight < 10) {
this.averageheight = 60; this.averageheight = 60;
} }
this.scrollTop = this.div.scrollTop; this.scrollTop = this.div.scrollTop;
if(!this.scrollBottom && !(await this.watchForChange())){ if (!this.scrollBottom && !(await this.watchForChange())) {
this.reachesBottom(); this.reachesBottom();
} }
if(!this.scrollTop){ if (!this.scrollTop) {
await this.watchForChange(); await this.watchForChange();
} }
this.needsupdate = false; this.needsupdate = false;
} }
async firstElement(id: string): Promise<void>{ async firstElement(id: string): Promise<void> {
if(!this.div)return; if (!this.div) return;
const html = await this.getHTMLFromID(id); const html = await this.getHTMLFromID(id);
this.div.appendChild(html); this.div.appendChild(html);
this.HTMLElements.push([html, id]); this.HTMLElements.push([html, id]);
} }
async addedBottom(): Promise<void>{ async addedBottom(): Promise<void> {
await this.updatestuff(); await this.updatestuff();
const func = this.snapBottom(); const func = this.snapBottom();
await this.watchForChange(); await this.watchForChange();
func(); func();
} }
snapBottom(): () => void{ snapBottom(): () => void {
const scrollBottom = this.scrollBottom; const scrollBottom = this.scrollBottom;
return()=>{ return () => {
if(this.div && scrollBottom < 4){ if (this.div && scrollBottom < 4) {
this.div.scrollTop = this.div.scrollHeight; this.div.scrollTop = this.div.scrollHeight;
} }
}; };
} }
private async watchForTop( private async watchForTop(already = false, fragment = new DocumentFragment()): Promise<boolean> {
already = false, if (!this.div) return false;
fragment = new DocumentFragment() try {
): Promise<boolean>{
if(!this.div)return false;
try{
let again = false; let again = false;
if(this.scrollTop < (already ? this.fillDist : this.minDist)){ if (this.scrollTop < (already ? this.fillDist : this.minDist)) {
let nextid: string | undefined; let nextid: string | undefined;
const firstelm = this.HTMLElements.at(0); const firstelm = this.HTMLElements.at(0);
if(firstelm){ if (firstelm) {
const previd = firstelm[1]; const previd = firstelm[1];
nextid = await this.getIDFromOffset(previd, 1); nextid = await this.getIDFromOffset(previd, 1);
} }
if(nextid){ if (nextid) {
const html = await this.getHTMLFromID(nextid); const html = await this.getHTMLFromID(nextid);
if(!html){ if (!html) {
this.destroyFromID(nextid); this.destroyFromID(nextid);
return false; return false;
} }
@ -173,26 +166,24 @@ offset: number
fragment.prepend(html); fragment.prepend(html);
this.HTMLElements.unshift([html, nextid]); this.HTMLElements.unshift([html, nextid]);
this.scrollTop += this.averageheight; this.scrollTop += this.averageheight;
} }
} }
if(this.scrollTop > this.maxDist){ if (this.scrollTop > this.maxDist) {
const html = this.HTMLElements.shift(); const html = this.HTMLElements.shift();
if(html){ if (html) {
again = true; again = true;
await this.destroyFromID(html[1]); await this.destroyFromID(html[1]);
this.scrollTop -= this.averageheight; this.scrollTop -= this.averageheight;
} }
} }
if(again){ if (again) {
await this.watchForTop(true, fragment); await this.watchForTop(true, fragment);
} }
return again; return again;
}finally{ } finally {
if(!already){ if (!already) {
if(this.div.scrollTop === 0){ if (this.div.scrollTop === 0) {
this.scrollTop = 1; this.scrollTop = 1;
this.div.scrollTop = 10; this.div.scrollTop = 10;
} }
@ -201,24 +192,21 @@ offset: number
} }
} }
async watchForBottom( async watchForBottom(already = false, fragment = new DocumentFragment()): Promise<boolean> {
already = false,
fragment = new DocumentFragment()
): Promise<boolean>{
let func: Function | undefined; let func: Function | undefined;
if(!already) func = this.snapBottom(); if (!already) func = this.snapBottom();
if(!this.div)return false; if (!this.div) return false;
try{ try {
let again = false; let again = false;
const scrollBottom = this.scrollBottom; const scrollBottom = this.scrollBottom;
if(scrollBottom < (already ? this.fillDist : this.minDist)){ if (scrollBottom < (already ? this.fillDist : this.minDist)) {
let nextid: string | undefined; let nextid: string | undefined;
const lastelm = this.HTMLElements.at(-1); const lastelm = this.HTMLElements.at(-1);
if(lastelm){ if (lastelm) {
const previd = lastelm[1]; const previd = lastelm[1];
nextid = await this.getIDFromOffset(previd, -1); nextid = await this.getIDFromOffset(previd, -1);
} }
if(nextid){ if (nextid) {
again = true; again = true;
const html = await this.getHTMLFromID(nextid); const html = await this.getHTMLFromID(nextid);
fragment.appendChild(html); fragment.appendChild(html);
@ -226,57 +214,56 @@ offset: number
this.scrollBottom += this.averageheight; this.scrollBottom += this.averageheight;
} }
} }
if(scrollBottom > this.maxDist){ if (scrollBottom > this.maxDist) {
const html = this.HTMLElements.pop(); const html = this.HTMLElements.pop();
if(html){ if (html) {
await this.destroyFromID(html[1]); await this.destroyFromID(html[1]);
this.scrollBottom -= this.averageheight; this.scrollBottom -= this.averageheight;
again = true; again = true;
} }
} }
if(again){ if (again) {
await this.watchForBottom(true, fragment); await this.watchForBottom(true, fragment);
} }
return again; return again;
}finally{ } finally {
if(!already){ if (!already) {
this.div.append(fragment); this.div.append(fragment);
if(func){ if (func) {
func(); func();
} }
} }
} }
} }
async watchForChange(): Promise<boolean>{ async watchForChange(): Promise<boolean> {
if(this.changePromise){ if (this.changePromise) {
this.watchtime = true; this.watchtime = true;
return await this.changePromise; return await this.changePromise;
}else{ } else {
this.watchtime = false; this.watchtime = false;
} }
this.changePromise = new Promise<boolean>(async res=>{ this.changePromise = new Promise<boolean>(async (res) => {
try{ try {
if(!this.div){ if (!this.div) {
res(false); res(false);
} }
const out = (await Promise.allSettled([ const out = (await Promise.allSettled([this.watchForTop(), this.watchForBottom()])) as {
this.watchForTop(), value: boolean;
this.watchForBottom(), }[];
])) as { value: boolean }[];
const changed = out[0].value || out[1].value; const changed = out[0].value || out[1].value;
if(this.timeout === null && changed){ if (this.timeout === null && changed) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
res(Boolean(changed)); res(Boolean(changed));
}catch(e){ } catch (e) {
console.error(e); console.error(e);
res(false); res(false);
}finally{ } finally {
setTimeout(()=>{ setTimeout(() => {
this.changePromise = undefined; this.changePromise = undefined;
if(this.watchtime){ if (this.watchtime) {
this.watchForChange(); this.watchForChange();
} }
}, 300); }, 300);
@ -285,65 +272,65 @@ offset: number
return await this.changePromise; return await this.changePromise;
} }
async focus(id: string, flash = true): Promise<void>{ async focus(id: string, flash = true): Promise<void> {
let element: HTMLElement | undefined; let element: HTMLElement | undefined;
for(const thing of this.HTMLElements){ for (const thing of this.HTMLElements) {
if(thing[1] === id){ if (thing[1] === id) {
element = thing[0]; element = thing[0];
} }
} }
if(element){ if (element) {
if(flash){ if (flash) {
element.scrollIntoView({ element.scrollIntoView({
behavior: "smooth", behavior: "smooth",
block: "center", block: "center",
}); });
await new Promise(resolve=>{ await new Promise((resolve) => {
setTimeout(resolve, 1000); setTimeout(resolve, 1000);
}); });
element.classList.remove("jumped"); element.classList.remove("jumped");
await new Promise(resolve=>{ await new Promise((resolve) => {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
element.classList.add("jumped"); element.classList.add("jumped");
}else{ } else {
element.scrollIntoView(); element.scrollIntoView();
} }
}else{ } else {
this.resetVars(); this.resetVars();
//TODO may be a redundant loop, not 100% sure :P //TODO may be a redundant loop, not 100% sure :P
for(const thing of this.HTMLElements){ for (const thing of this.HTMLElements) {
await this.destroyFromID(thing[1]); await this.destroyFromID(thing[1]);
} }
this.HTMLElements = []; this.HTMLElements = [];
await this.firstElement(id); await this.firstElement(id);
this.updatestuff(); this.updatestuff();
await this.watchForChange(); await this.watchForChange();
await new Promise(resolve=>{ await new Promise((resolve) => {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
await this.focus(id, true); await this.focus(id, true);
} }
} }
async delete(): Promise<void>{ async delete(): Promise<void> {
if(this.div){ if (this.div) {
this.div.remove(); this.div.remove();
this.div = null; this.div = null;
} }
this.resetVars(); this.resetVars();
try{ try {
for(const thing of this.HTMLElements){ for (const thing of this.HTMLElements) {
await this.destroyFromID(thing[1]); await this.destroyFromID(thing[1]);
} }
}catch(e){ } catch (e) {
console.error(e); console.error(e);
} }
this.HTMLElements = []; this.HTMLElements = [];
if(this.timeout){ if (this.timeout) {
clearTimeout(this.timeout); clearTimeout(this.timeout);
} }
} }
} }
export{ InfiniteScroller }; export {InfiniteScroller};

View file

@ -1,17 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Invite" property="og:title"> <meta content="Invite" property="og:title" />
<meta content="Accept this invite for a spacebar guild" property="og:description"> <meta content="Accept this invite for a spacebar guild" property="og:description" />
<meta name="description" content="You shouldn't see this, but this is an invite URL"> <meta name="description" content="You shouldn't see this, but this is an invite URL" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div> <div>

View file

@ -1,48 +1,47 @@
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import{ getapiurls }from"./utils/utils.js"; import {getapiurls} from "./utils/utils.js";
import { getBulkUsers, Specialuser } from "./utils/utils.js"; import {getBulkUsers, Specialuser} from "./utils/utils.js";
(async ()=>{ (async () => {
const users = getBulkUsers(); const users = getBulkUsers();
const well = new URLSearchParams(window.location.search).get("instance"); const well = new URLSearchParams(window.location.search).get("instance");
const joinable: Specialuser[] = []; const joinable: Specialuser[] = [];
for(const key in users.users){ for (const key in users.users) {
if(Object.prototype.hasOwnProperty.call(users.users, key)){ if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key]; const user: Specialuser = users.users[key];
if(well && user.serverurls.wellknown.includes(well)){ if (well && user.serverurls.wellknown.includes(well)) {
joinable.push(user); joinable.push(user);
} }
console.log(user); console.log(user);
} }
} }
let urls: { api: string; cdn: string } | undefined; let urls: {api: string; cdn: string} | undefined;
if(!joinable.length && well){ if (!joinable.length && well) {
const out = await getapiurls(well); const out = await getapiurls(well);
if(out){ if (out) {
urls = out; urls = out;
for(const key in users.users){ for (const key in users.users) {
if(Object.prototype.hasOwnProperty.call(users.users, key)){ if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key]; const user: Specialuser = users.users[key];
if(user.serverurls.api.includes(out.api)){ if (user.serverurls.api.includes(out.api)) {
joinable.push(user); joinable.push(user);
} }
console.log(user); console.log(user);
} }
} }
}else{ } else {
throw new Error( throw new Error("Someone needs to handle the case where the servers don't exist");
"Someone needs to handle the case where the servers don't exist"
);
} }
}else{ } else {
urls = joinable[0].serverurls; urls = joinable[0].serverurls;
} }
await I18n.done; await I18n.done;
if(!joinable.length){ if (!joinable.length) {
document.getElementById("AcceptInvite")!.textContent = I18n.getTranslation("htmlPages.noAccount"); document.getElementById("AcceptInvite")!.textContent =
I18n.getTranslation("htmlPages.noAccount");
} }
const code = window.location.pathname.split("/")[2]; const code = window.location.pathname.split("/")[2];
@ -51,34 +50,36 @@ import { getBulkUsers, Specialuser } from "./utils/utils.js";
fetch(`${urls!.api}/invites/${code}`, { fetch(`${urls!.api}/invites/${code}`, {
method: "GET", method: "GET",
}) })
.then(response=>response.json()) .then((response) => response.json())
.then(json=>{ .then((json) => {
const guildjson = json.guild; const guildjson = json.guild;
guildinfo = guildjson; guildinfo = guildjson;
document.getElementById("invitename")!.textContent = guildjson.name; document.getElementById("invitename")!.textContent = guildjson.name;
document.getElementById( document.getElementById("invitedescription")!.textContent = I18n.getTranslation(
"invitedescription" "invite.longInvitedBy",
)!.textContent = I18n.getTranslation("invite.longInvitedBy",json.inviter.username,guildjson.name) json.inviter.username,
if(guildjson.icon){ guildjson.name,
const img = document.createElement("img"); );
img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; if (guildjson.icon) {
img.classList.add("inviteGuild"); const img = document.createElement("img");
document.getElementById("inviteimg")!.append(img); img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`;
}else{ img.classList.add("inviteGuild");
const txt = guildjson.name document.getElementById("inviteimg")!.append(img);
.replace(/'s /g, " ") } else {
.replace(/\w+/g, (word: any[])=>word[0]) const txt = guildjson.name
.replace(/\s/g, ""); .replace(/'s /g, " ")
const div = document.createElement("div"); .replace(/\w+/g, (word: any[]) => word[0])
div.textContent = txt; .replace(/\s/g, "");
div.classList.add("inviteGuild"); const div = document.createElement("div");
document.getElementById("inviteimg")!.append(div); div.textContent = txt;
} div.classList.add("inviteGuild");
document.getElementById("inviteimg")!.append(div);
}
}); });
function showAccounts(): void{ function showAccounts(): void {
const table = document.createElement("dialog"); const table = document.createElement("dialog");
for(const user of joinable){ for (const user of joinable) {
console.log(user.pfpsrc); console.log(user.pfpsrc);
const userinfo = document.createElement("div"); const userinfo = document.createElement("div");
@ -95,23 +96,21 @@ document.getElementById("inviteimg")!.append(div);
userDiv.append(document.createElement("br")); userDiv.append(document.createElement("br"));
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = user.serverurls.wellknown span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
.replace("https://", "")
.replace("http://", "");
span.classList.add("serverURL"); span.classList.add("serverURL");
userDiv.append(span); userDiv.append(span);
userinfo.append(userDiv); userinfo.append(userDiv);
table.append(userinfo); table.append(userinfo);
userinfo.addEventListener("click", ()=>{ userinfo.addEventListener("click", () => {
console.log(user); console.log(user);
fetch(`${urls!.api}/invites/${code}`, { fetch(`${urls!.api}/invites/${code}`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: user.token, Authorization: user.token,
}, },
}).then(()=>{ }).then(() => {
users.currentuser = user.uid; users.currentuser = user.uid;
localStorage.setItem("userinfos", JSON.stringify(users)); localStorage.setItem("userinfos", JSON.stringify(users));
window.location.href = "/channels/" + guildinfo.id; window.location.href = "/channels/" + guildinfo.id;
@ -122,14 +121,14 @@ document.getElementById("inviteimg")!.append(div);
const td = document.createElement("div"); const td = document.createElement("div");
td.classList.add("switchtable"); td.classList.add("switchtable");
td.textContent = I18n.getTranslation("invite.loginOrCreateAccount"); td.textContent = I18n.getTranslation("invite.loginOrCreateAccount");
td.addEventListener("click", ()=>{ td.addEventListener("click", () => {
const l = new URLSearchParams("?"); const l = new URLSearchParams("?");
l.set("goback", window.location.href); l.set("goback", window.location.href);
l.set("instance", well!); l.set("instance", well!);
window.location.href = "/login?" + l.toString(); window.location.href = "/login?" + l.toString();
}); });
if(!joinable.length){ if (!joinable.length) {
const l = new URLSearchParams("?"); const l = new URLSearchParams("?");
l.set("goback", window.location.href); l.set("goback", window.location.href);
l.set("instance", well!); l.set("instance", well!);
@ -137,12 +136,10 @@ document.getElementById("inviteimg")!.append(div);
} }
table.append(td); table.append(td);
table.classList.add("flexttb","accountSwitcher"); table.classList.add("flexttb", "accountSwitcher");
console.log(table); console.log(table);
document.body.append(table); document.body.append(table);
} }
document document.getElementById("AcceptInvite")!.addEventListener("click", showAccounts);
.getElementById("AcceptInvite")!
.addEventListener("click", showAccounts);
})(); })();

View file

@ -63,7 +63,12 @@ type readyjson = {
}; };
user_guild_settings: { user_guild_settings: {
entries: { entries: {
channel_overrides: {message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}[]; channel_overrides: {
message_notifications: number;
muted: boolean;
mute_config: {selected_time_window: number; end_time: number};
channel_id: string;
}[];
message_notifications: number; message_notifications: number;
flags: number; flags: number;
hide_muted_channels: boolean; hide_muted_channels: boolean;
@ -149,12 +154,12 @@ type memberjson = {
id: string; id: string;
user: userjson | null; user: userjson | null;
guild_id: string; guild_id: string;
avatar?:string; avatar?: string;
banner?:string; banner?: string;
guild: { guild: {
id: string; id: string;
} | null; } | null;
presence?:presencejson presence?: presencejson;
nick?: string; nick?: string;
roles: string[]; roles: string[];
joined_at: string; joined_at: string;
@ -168,21 +173,20 @@ type emojijson = {
name: string; name: string;
id?: string; id?: string;
animated?: boolean; animated?: boolean;
emoji?:string; emoji?: string;
}; };
type emojipjson=emojijson&{ type emojipjson = emojijson & {
available: boolean, available: boolean;
guild_id:string, guild_id: string;
user_id:string, user_id: string;
managed:boolean, managed: boolean;
require_colons:boolean, require_colons: boolean;
roles:string[], roles: string[];
groups:null//TODO figure out what this means lol groups: null; //TODO figure out what this means lol
}; };
type guildjson = { type guildjson = {
application_command_counts: { [key: string]: number }; application_command_counts: {[key: string]: number};
channels: channeljson[]; channels: channeljson[];
data_mode: string; data_mode: string;
emojis: emojipjson[]; emojis: emojipjson[];
@ -404,165 +408,178 @@ type messageCreateJson = {
s: number; s: number;
t: "MESSAGE_CREATE"; t: "MESSAGE_CREATE";
}; };
type roleCreate={ type roleCreate = {
op: 0,
t: "GUILD_ROLE_CREATE",
d: {
guild_id: string,
role: rolesjson
},
s: 6
}
type wsjson =
roleCreate | {
op: 0;
d: any;
s: number;
t:
| "TYPING_START"
| "USER_UPDATE"
| "CHANNEL_UPDATE"
| "CHANNEL_CREATE"
| "CHANNEL_DELETE"
| "GUILD_DELETE"
| "GUILD_CREATE"
| "MESSAGE_REACTION_REMOVE_ALL"
| "MESSAGE_REACTION_REMOVE_EMOJI";
}
| {
op: 0;
t: "GUILD_MEMBERS_CHUNK";
d: memberChunk;
s: number;
}
| {
op: 0; op: 0;
t: "GUILD_ROLE_CREATE";
d: { d: {
id: string;
guild_id?: string;
channel_id: string;
};
s: number;
t: "MESSAGE_DELETE";
}
| {
op: 0;
d: {
guild_id?: string;
channel_id: string;
} & messagejson;
s: number;
t: "MESSAGE_UPDATE";
}
| messageCreateJson
| readyjson
| {
op: 11;
s: undefined;
d: {};
}
| {
op: 10;
s: undefined;
d: {
heartbeat_interval: number;
};
}
| {
op: 0;
t: "MESSAGE_REACTION_ADD";
d: {
user_id: string;
channel_id: string;
message_id: string;
guild_id?: string;
emoji: emojijson;
member?: memberjson;
};
s: number;
}
| {
op: 0;
t: "MESSAGE_REACTION_REMOVE";
d: {
user_id: string;
channel_id: string;
message_id: string;
guild_id: string; guild_id: string;
emoji: emojijson; role: rolesjson;
}; };
s: number; s: 6;
}|{
op: 0,
t: "GUILD_ROLE_UPDATE",
d: {
guild_id: string,
role: rolesjson
},
"s": number
}|{
op: 0,
t: "GUILD_ROLE_DELETE",
d: {
guild_id: string,
role_id: string
},
s:number
}|{
op: 0,
t: "GUILD_MEMBER_UPDATE",
d: memberjson,
s: 3
}|{
op:9,
d:boolean,
s:number
}|memberlistupdatejson|voiceupdate|voiceserverupdate|{
op: 0,
t: "RELATIONSHIP_ADD",
d: {
id: string,
type: 0|1|2|3|4|5|6,
user: userjson
},
s: number
}|{
op: 0,
t: "RELATIONSHIP_REMOVE",
d: {
id: string,
type: number,
nickname: null
},
s: number
}|{
op: 0,
t: "PRESENCE_UPDATE",
d: presencejson,
s:number
}|{
op:0,
t:"GUILD_MEMBER_ADD",
d:memberjson,
s:number
}|{
op:0,
t:"GUILD_MEMBER_REMOVE",
d:{
guild_id:string,
user:userjson
},
s:number
}|{
op: 0,
t: "GUILD_EMOJIS_UPDATE",
d: {
guild_id: string,
emojis: emojipjson[]
},
s: number
}; };
type wsjson =
| roleCreate
| {
op: 0;
d: any;
s: number;
t:
| "TYPING_START"
| "USER_UPDATE"
| "CHANNEL_UPDATE"
| "CHANNEL_CREATE"
| "CHANNEL_DELETE"
| "GUILD_DELETE"
| "GUILD_CREATE"
| "MESSAGE_REACTION_REMOVE_ALL"
| "MESSAGE_REACTION_REMOVE_EMOJI";
}
| {
op: 0;
t: "GUILD_MEMBERS_CHUNK";
d: memberChunk;
s: number;
}
| {
op: 0;
d: {
id: string;
guild_id?: string;
channel_id: string;
};
s: number;
t: "MESSAGE_DELETE";
}
| {
op: 0;
d: {
guild_id?: string;
channel_id: string;
} & messagejson;
s: number;
t: "MESSAGE_UPDATE";
}
| messageCreateJson
| readyjson
| {
op: 11;
s: undefined;
d: {};
}
| {
op: 10;
s: undefined;
d: {
heartbeat_interval: number;
};
}
| {
op: 0;
t: "MESSAGE_REACTION_ADD";
d: {
user_id: string;
channel_id: string;
message_id: string;
guild_id?: string;
emoji: emojijson;
member?: memberjson;
};
s: number;
}
| {
op: 0;
t: "MESSAGE_REACTION_REMOVE";
d: {
user_id: string;
channel_id: string;
message_id: string;
guild_id: string;
emoji: emojijson;
};
s: number;
}
| {
op: 0;
t: "GUILD_ROLE_UPDATE";
d: {
guild_id: string;
role: rolesjson;
};
s: number;
}
| {
op: 0;
t: "GUILD_ROLE_DELETE";
d: {
guild_id: string;
role_id: string;
};
s: number;
}
| {
op: 0;
t: "GUILD_MEMBER_UPDATE";
d: memberjson;
s: 3;
}
| {
op: 9;
d: boolean;
s: number;
}
| memberlistupdatejson
| voiceupdate
| voiceserverupdate
| {
op: 0;
t: "RELATIONSHIP_ADD";
d: {
id: string;
type: 0 | 1 | 2 | 3 | 4 | 5 | 6;
user: userjson;
};
s: number;
}
| {
op: 0;
t: "RELATIONSHIP_REMOVE";
d: {
id: string;
type: number;
nickname: null;
};
s: number;
}
| {
op: 0;
t: "PRESENCE_UPDATE";
d: presencejson;
s: number;
}
| {
op: 0;
t: "GUILD_MEMBER_ADD";
d: memberjson;
s: number;
}
| {
op: 0;
t: "GUILD_MEMBER_REMOVE";
d: {
guild_id: string;
user: userjson;
};
s: number;
}
| {
op: 0;
t: "GUILD_EMOJIS_UPDATE";
d: {
guild_id: string;
emojis: emojipjson[];
};
s: number;
};
type memberChunk = { type memberChunk = {
guild_id: string; guild_id: string;
@ -573,134 +590,140 @@ type memberChunk = {
chunk_count: number; chunk_count: number;
not_found: string[]; not_found: string[];
}; };
type voiceupdate={ type voiceupdate = {
op: 0, op: 0;
t: "VOICE_STATE_UPDATE", t: "VOICE_STATE_UPDATE";
d: { d: {
guild_id: string, guild_id: string;
channel_id: string, channel_id: string;
user_id: string, user_id: string;
member: memberjson, member: memberjson;
session_id: string, session_id: string;
token: string, token: string;
deaf: boolean, deaf: boolean;
mute: boolean, mute: boolean;
self_deaf: boolean, self_deaf: boolean;
self_mute: boolean, self_mute: boolean;
self_video: boolean, self_video: boolean;
suppress: boolean suppress: boolean;
}, };
s: number s: number;
}; };
type voiceserverupdate={ type voiceserverupdate = {
op: 0, op: 0;
t: "VOICE_SERVER_UPDATE", t: "VOICE_SERVER_UPDATE";
d: { d: {
token: string, token: string;
guild_id: string, guild_id: string;
endpoint: string endpoint: string;
}, };
s: 6 s: 6;
}; };
type memberlistupdatejson={ type memberlistupdatejson = {
op: 0, op: 0;
s: number, s: number;
t: "GUILD_MEMBER_LIST_UPDATE", t: "GUILD_MEMBER_LIST_UPDATE";
d: { d: {
ops: [ ops: [
{ {
items:({ items: (
group:{ | {
count:number, group: {
id:string count: number;
} id: string;
}|{ };
member:memberjson }
})[] | {
op: "SYNC", member: memberjson;
range: [ }
number, )[];
number op: "SYNC";
] range: [number, number];
} },
], ];
online_count: number, online_count: number;
member_count: number, member_count: number;
id: string, id: string;
guild_id: string, guild_id: string;
groups: { groups: {
count: number, count: number;
id: string id: string;
}[] }[];
} };
}
type webRTCSocket= {
op: 8,
d: {
heartbeat_interval: number
}
}|{
op:6,
d:{t: number}
}|{
op: 2,
d: {
ssrc: number,
"streams": {
type: "video",//probally more options, but idk
rid: string,
quality: number,
ssrc: number,
rtx_ssrc:number
}[],
ip: number,
port: number,
"modes": [],//no clue
"experiments": []//no clue
}
}|sdpback|opRTC12|{
op: 5,
d: {
user_id: string,
speaking: 0,
ssrc: 940464811
}
}; };
type sdpback={ type webRTCSocket =
op: 4, | {
op: 8;
d: {
heartbeat_interval: number;
};
}
| {
op: 6;
d: {t: number};
}
| {
op: 2;
d: {
ssrc: number;
streams: {
type: "video"; //probally more options, but idk
rid: string;
quality: number;
ssrc: number;
rtx_ssrc: number;
}[];
ip: number;
port: number;
modes: []; //no clue
experiments: []; //no clue
};
}
| sdpback
| opRTC12
| {
op: 5;
d: {
user_id: string;
speaking: 0;
ssrc: 940464811;
};
};
type sdpback = {
op: 4;
d: { d: {
audioCodec: string, audioCodec: string;
videoCodec: string, videoCodec: string;
media_session_id: string, media_session_id: string;
sdp: string sdp: string;
} };
}; };
type opRTC12={ type opRTC12 = {
op: 12, op: 12;
d: { d: {
user_id: string, user_id: string;
audio_ssrc: number, audio_ssrc: number;
video_ssrc: number, video_ssrc: number;
streams: [ streams: [
{ {
type: "video", type: "video";
rid: "100", rid: "100";
ssrc: number, ssrc: number;
active: boolean, active: boolean;
quality: 100, quality: 100;
rtx_ssrc: number, rtx_ssrc: number;
max_bitrate: 2500000, max_bitrate: 2500000;
max_framerate: number, max_framerate: number;
max_resolution: { max_resolution: {
type: "fixed", type: "fixed";
width: number, width: number;
height: number height: number;
} };
} },
] ];
} };
} };
export{ export {
readyjson, readyjson,
dirrectjson, dirrectjson,
startTypingjson, startTypingjson,
@ -725,5 +748,5 @@ export{
webRTCSocket, webRTCSocket,
sdpback, sdpback,
opRTC12, opRTC12,
emojipjson emojipjson,
}; };

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,25 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
content="A spacebar client that has DMs, replying and more" <meta content="/logo.webp" property="og:image" />
property="og:description" <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
> <link href="/style.css" rel="stylesheet" />
<meta content="/logo.webp" property="og:image"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <style>
<link href="/style.css" rel="stylesheet"> body.no-theme {
<link href="/themes.css" rel="stylesheet" id="lightcss"> background: #16191b;
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> }
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div id="logindiv"> <div id="logindiv">
@ -29,25 +35,13 @@
id="instancein" id="instancein"
value="" value=""
required required
> />
<label for="uname" id="emailField"><b>Email:</b></label> <label for="uname" id="emailField"><b>Email:</b></label>
<input <input type="text" placeholder="Enter email address" name="uname" id="uname" required />
type="text"
placeholder="Enter email address"
name="uname"
id="uname"
required
>
<label for="psw" id="pwField"><b>Password:</b></label> <label for="psw" id="pwField"><b>Password:</b></label>
<input <input type="password" placeholder="Enter Password" name="psw" id="psw" required />
type="password"
placeholder="Enter Password"
name="psw"
id="psw"
required
>
<p class="wrongred" id="wrong"></p> <p class="wrongred" id="wrong"></p>
<div id="h-captcha"></div> <div id="h-captcha"></div>

View file

@ -1,55 +1,50 @@
import { getBulkInfo, Specialuser } from "./utils/utils.js"; import {getBulkInfo, Specialuser} from "./utils/utils.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import { Dialog, FormError } from "./settings.js"; import {Dialog, FormError} from "./settings.js";
import { checkInstance } from "./utils/utils.js"; import {checkInstance} from "./utils/utils.js";
await I18n.done; await I18n.done;
(async () => {
await I18n.done;
(async ()=>{ const instanceField = document.getElementById("instanceField");
await I18n.done const emailField = document.getElementById("emailField");
const instanceField=document.getElementById("instanceField"); const pwField = document.getElementById("pwField");
const emailField= document.getElementById("emailField"); const loginButton = document.getElementById("loginButton");
const pwField= document.getElementById("pwField"); const noAccount = document.getElementById("switch");
const loginButton=document.getElementById("loginButton"); if (instanceField && emailField && pwField && loginButton && noAccount) {
const noAccount=document.getElementById("switch") instanceField.textContent = I18n.getTranslation("htmlPages.instanceField");
if(instanceField&&emailField&&pwField&&loginButton&&noAccount){ emailField.textContent = I18n.getTranslation("htmlPages.emailField");
instanceField.textContent=I18n.getTranslation("htmlPages.instanceField"); pwField.textContent = I18n.getTranslation("htmlPages.pwField");
emailField.textContent=I18n.getTranslation("htmlPages.emailField"); loginButton.textContent = I18n.getTranslation("htmlPages.loginButton");
pwField.textContent=I18n.getTranslation("htmlPages.pwField"); noAccount.textContent = I18n.getTranslation("htmlPages.noAccount");
loginButton.textContent=I18n.getTranslation("htmlPages.loginButton");
noAccount.textContent=I18n.getTranslation("htmlPages.noAccount");
} }
})() })();
function trimswitcher() {
function trimswitcher(){
const json = getBulkInfo(); const json = getBulkInfo();
const map = new Map(); const map = new Map();
for(const thing in json.users){ for (const thing in json.users) {
const user = json.users[thing]; const user = json.users[thing];
let wellknown = user.serverurls.wellknown; let wellknown = user.serverurls.wellknown;
if(wellknown.at(-1) !== "/"){ if (wellknown.at(-1) !== "/") {
wellknown += "/"; wellknown += "/";
} }
wellknown =(user.id||user.email)+"@"+wellknown; wellknown = (user.id || user.email) + "@" + wellknown;
if(map.has(wellknown)){ if (map.has(wellknown)) {
const otheruser = map.get(wellknown); const otheruser = map.get(wellknown);
if(otheruser[1].serverurls.wellknown.at(-1) === "/"){ if (otheruser[1].serverurls.wellknown.at(-1) === "/") {
delete json.users[otheruser[0]]; delete json.users[otheruser[0]];
map.set(wellknown, [thing, user]); map.set(wellknown, [thing, user]);
}else{ } else {
delete json.users[thing]; delete json.users[thing];
} }
}else{ } else {
map.set(wellknown, [thing, user]); map.set(wellknown, [thing, user]);
} }
} }
for(const thing in json.users){ for (const thing in json.users) {
if(thing.at(-1) === "/"){ if (thing.at(-1) === "/") {
const user = json.users[thing]; const user = json.users[thing];
delete json.users[thing]; delete json.users[thing];
json.users[thing.slice(0, -1)] = user; json.users[thing.slice(0, -1)] = user;
@ -59,9 +54,7 @@ function trimswitcher(){
console.log(json); console.log(json);
} }
function adduser(user: typeof Specialuser.prototype.json) {
function adduser(user: typeof Specialuser.prototype.json){
user = new Specialuser(user); user = new Specialuser(user);
const info = getBulkInfo(); const info = getBulkInfo();
info.users[user.uid] = user; info.users[user.uid] = user;
@ -73,32 +66,30 @@ const instancein = document.getElementById("instancein") as HTMLInputElement;
let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null; let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null;
// let instanceinfo; // let instanceinfo;
if (instancein) {
if(instancein){
console.log(instancein); console.log(instancein);
instancein.addEventListener("keydown", ()=>{ instancein.addEventListener("keydown", () => {
const verify = document.getElementById("verify"); const verify = document.getElementById("verify");
verify!.textContent = I18n.getTranslation("login.waiting"); verify!.textContent = I18n.getTranslation("login.waiting");
if(timeout !== null && typeof timeout !== "string"){ if (timeout !== null && typeof timeout !== "string") {
clearTimeout(timeout); clearTimeout(timeout);
} }
timeout = setTimeout(()=>checkInstance((instancein as HTMLInputElement).value), 1000); timeout = setTimeout(() => checkInstance((instancein as HTMLInputElement).value), 1000);
}); });
if(localStorage.getItem("instanceinfo")){ if (localStorage.getItem("instanceinfo")) {
const json = JSON.parse(localStorage.getItem("instanceinfo")!); const json = JSON.parse(localStorage.getItem("instanceinfo")!);
if(json.value){ if (json.value) {
(instancein as HTMLInputElement).value = json.value; (instancein as HTMLInputElement).value = json.value;
}else{ } else {
(instancein as HTMLInputElement).value = json.wellknown; (instancein as HTMLInputElement).value = json.wellknown;
} }
}else{ } else {
checkInstance("https://spacebar.chat/"); checkInstance("https://spacebar.chat/");
} }
} }
async function login(username: string, password: string, captcha: string){ async function login(username: string, password: string, captcha: string) {
if(captcha === ""){ if (captcha === "") {
captcha = ""; captcha = "";
} }
const options = { const options = {
@ -113,25 +104,25 @@ async function login(username: string, password: string, captcha: string){
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
}, },
}; };
try{ try {
const info = JSON.parse(localStorage.getItem("instanceinfo")!); const info = JSON.parse(localStorage.getItem("instanceinfo")!);
const api = info.login + (info.login.startsWith("/") ? "/" : ""); const api = info.login + (info.login.startsWith("/") ? "/" : "");
return await fetch(api + "/auth/login", options) return await fetch(api + "/auth/login", options)
.then(response=>response.json()) .then((response) => response.json())
.then(response=>{ .then((response) => {
console.log(response, response.message); console.log(response, response.message);
if(response.message === "Invalid Form Body"){ if (response.message === "Invalid Form Body") {
return response.errors.login._errors[0].message; return response.errors.login._errors[0].message;
console.log("test"); console.log("test");
} }
//this.serverurls||!this.email||!this.token //this.serverurls||!this.email||!this.token
console.log(response); console.log(response);
if(response.captcha_sitekey){ if (response.captcha_sitekey) {
const capt = document.getElementById("h-captcha"); const capt = document.getElementById("h-captcha");
if(capt!.children.length){ if (capt!.children.length) {
eval("hcaptcha.reset()"); eval("hcaptcha.reset()");
}else{ } else {
const capty = document.createElement("div"); const capty = document.createElement("div");
capty.classList.add("h-captcha"); capty.classList.add("h-captcha");
@ -141,107 +132,100 @@ async function login(username: string, password: string, captcha: string){
capt!.append(script); capt!.append(script);
capt!.append(capty); capt!.append(capty);
} }
}else{ } else {
console.log(response); console.log(response);
if(response.ticket){ if (response.ticket) {
const better=new Dialog(""); const better = new Dialog("");
const form=better.options.addForm("",(res:any)=>{ const form = better.options.addForm(
if(res.message){ "",
throw new FormError(ti,res.message); (res: any) => {
}else{ if (res.message) {
console.warn(res); throw new FormError(ti, res.message);
if(!res.token)return; } else {
adduser({ console.warn(res);
serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string), if (!res.token) return;
email: username, adduser({
token: res.token, serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string),
}).username = username; email: username,
const redir = new URLSearchParams( token: res.token,
window.location.search }).username = username;
).get("goback"); const redir = new URLSearchParams(window.location.search).get("goback");
if(redir){ if (redir) {
window.location.href = redir; window.location.href = redir;
}else{ } else {
window.location.href = "/channels/@me"; window.location.href = "/channels/@me";
}
} }
} },
},{ {
fetchURL:api + "/auth/mfa/totp", fetchURL: api + "/auth/mfa/totp",
method:"POST", method: "POST",
headers:{ headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
} },
}); },
);
form.addTitle(I18n.getTranslation("2faCode")); form.addTitle(I18n.getTranslation("2faCode"));
const ti=form.addTextInput("","code"); const ti = form.addTextInput("", "code");
better.show() better.show();
}else{ } else {
console.warn(response); console.warn(response);
if(!response.token)return; if (!response.token) return;
adduser({ adduser({
serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), serverurls: JSON.parse(localStorage.getItem("instanceinfo")!),
email: username, email: username,
token: response.token, token: response.token,
}).username = username; }).username = username;
const redir = new URLSearchParams(window.location.search).get( const redir = new URLSearchParams(window.location.search).get("goback");
"goback" if (redir) {
);
if(redir){
window.location.href = redir; window.location.href = redir;
}else{ } else {
window.location.href = "/channels/@me"; window.location.href = "/channels/@me";
} }
return""; return "";
} }
} }
}); });
}catch(error){ } catch (error) {
console.error("Error:", error); console.error("Error:", error);
} }
} }
async function check(e: SubmitEvent){ async function check(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
const target = e.target as HTMLFormElement; const target = e.target as HTMLFormElement;
const h = await login( const h = await login(
(target[1] as HTMLInputElement).value, (target[1] as HTMLInputElement).value,
(target[2] as HTMLInputElement).value, (target[2] as HTMLInputElement).value,
(target[3] as HTMLInputElement).value (target[3] as HTMLInputElement).value,
); );
const wrongElement = document.getElementById("wrong"); const wrongElement = document.getElementById("wrong");
if(wrongElement){ if (wrongElement) {
wrongElement.textContent = h; wrongElement.textContent = h;
} }
console.log(h); console.log(h);
} }
if(document.getElementById("form")){ if (document.getElementById("form")) {
const form = document.getElementById("form"); const form = document.getElementById("form");
if(form){ if (form) {
form.addEventListener("submit", (e: SubmitEvent)=>check(e)); 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(!localStorage.getItem("SWMode")){ if (!localStorage.getItem("SWMode")) {
localStorage.setItem("SWMode","true"); localStorage.setItem("SWMode", "true");
} }
const switchurl = document.getElementById("switch") as HTMLAreaElement; const switchurl = document.getElementById("switch") as HTMLAreaElement;
if(switchurl){ if (switchurl) {
switchurl.href += window.location.search; switchurl.href += window.location.search;
const instance = new URLSearchParams(window.location.search).get("instance"); const instance = new URLSearchParams(window.location.search).get("instance");
console.log(instance); console.log(instance);
if(instance){ if (instance) {
instancein.value = instance; instancein.value = instance;
checkInstance(""); checkInstance("");
} }
} }
trimswitcher(); trimswitcher();
export{ export {adduser};
adduser,
};

File diff suppressed because it is too large Load diff

View file

@ -1,125 +1,127 @@
import{ User }from"./user.js"; import {User} from "./user.js";
import{ Role }from"./role.js"; import {Role} from "./role.js";
import{ Guild }from"./guild.js"; import {Guild} from "./guild.js";
import{ SnowFlake }from"./snowflake.js"; import {SnowFlake} from "./snowflake.js";
import{ memberjson, presencejson }from"./jsontypes.js"; import {memberjson, presencejson} from "./jsontypes.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import { Dialog, Options, Settings } from "./settings.js"; import {Dialog, Options, Settings} from "./settings.js";
class Member extends SnowFlake{ class Member extends SnowFlake {
static already = {}; static already = {};
owner: Guild; owner: Guild;
user: User; user: User;
roles: Role[] = []; roles: Role[] = [];
nick!: string; nick!: string;
avatar:void|string=undefined; avatar: void | string = undefined;
banner:void|string=undefined; banner: void | string = undefined;
private constructor(memberjson: memberjson, owner: Guild){ private constructor(memberjson: memberjson, owner: Guild) {
super(memberjson.id); super(memberjson.id);
this.owner = owner; this.owner = owner;
if(this.localuser.userMap.has(memberjson.id)){ if (this.localuser.userMap.has(memberjson.id)) {
this.user = this.localuser.userMap.get(memberjson.id) as User; this.user = this.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){ } else if (memberjson.user) {
this.user = new User(memberjson.user, owner.localuser); this.user = new User(memberjson.user, owner.localuser);
}else{ } else {
throw new Error("Missing user object of this member"); throw new Error("Missing user object of this member");
} }
if(this.localuser.userMap.has(this?.id)){ if (this.localuser.userMap.has(this?.id)) {
this.user = this.localuser.userMap.get(this?.id) as User; this.user = this.localuser.userMap.get(this?.id) as User;
} }
for(const key of Object.keys(memberjson)){ for (const key of Object.keys(memberjson)) {
if(key === "guild" || key === "owner" || key === "user"){ if (key === "guild" || key === "owner" || key === "user") {
continue; continue;
} }
if(key === "roles"){ if (key === "roles") {
for(const strrole of memberjson.roles){ for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole); const role = this.guild.roleids.get(strrole);
if(!role)continue; if (!role) continue;
this.roles.push(role); this.roles.push(role);
} }
continue; continue;
} }
if(key === "presence"){ if (key === "presence") {
this.getPresence(memberjson.presence); this.getPresence(memberjson.presence);
continue; continue;
} }
(this as any)[key] = (memberjson as any)[key]; (this as any)[key] = (memberjson as any)[key];
} }
if(!this.user.bot){ if (!this.user.bot) {
const everyone=this.guild.roleids.get(this.guild.id); const everyone = this.guild.roleids.get(this.guild.id);
if(everyone&&(this.roles.indexOf(everyone)===-1)){ if (everyone && this.roles.indexOf(everyone) === -1) {
this.roles.push(everyone) this.roles.push(everyone);
} }
} }
this.roles.sort((a, b)=>{ this.roles.sort((a, b) => {
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
}); });
} }
remove(){ remove() {
this.user.members.delete(this.guild); this.user.members.delete(this.guild);
this.guild.members.delete(this); this.guild.members.delete(this);
} }
getpfpsrc():string{ getpfpsrc(): string {
if(this.hypotheticalpfp&&this.avatar){ if (this.hypotheticalpfp && this.avatar) {
return this.avatar; return this.avatar;
} }
if(this.avatar !== undefined&&this.avatar!==null){ if (this.avatar !== undefined && this.avatar !== null) {
return`${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${ return `${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${
this.avatar this.avatar
}.${this.avatar.startsWith("a_")?"gif":"png"}`; }.${this.avatar.startsWith("a_") ? "gif" : "png"}`;
} }
return this.user.getpfpsrc(); return this.user.getpfpsrc();
} }
getBannerUrl():string|undefined{ getBannerUrl(): string | undefined {
if(this.hypotheticalbanner&&this.banner){ if (this.hypotheticalbanner && this.banner) {
return this.banner; return this.banner;
} }
if(this.banner){ if (this.banner) {
return `${this.info.cdn}/banners/${this.guild.id}/${ return `${this.info.cdn}/banners/${this.guild.id}/${
this.banner this.banner
}.${this.banner.startsWith("a_")?"gif":"png"}`;; }.${this.banner.startsWith("a_") ? "gif" : "png"}`;
}else{ } else {
return undefined; return undefined;
} }
} }
joined_at!:string; joined_at!: string;
premium_since!:string; premium_since!: string;
deaf!:boolean; deaf!: boolean;
mute!:boolean; mute!: boolean;
pending!:boolean pending!: boolean;
clone(){ clone() {
return new Member({ return new Member(
id:this.id+"#clone", {
user:this.user.tojson(), id: this.id + "#clone",
guild_id:this.guild.id, user: this.user.tojson(),
guild:{id:this.guild.id}, guild_id: this.guild.id,
avatar:this.avatar as (string|undefined), guild: {id: this.guild.id},
banner:this.banner as (string|undefined), avatar: this.avatar as string | undefined,
//TODO presence banner: this.banner as string | undefined,
nick:this.nick, //TODO presence
roles:this.roles.map(_=>_.id), nick: this.nick,
joined_at:this.joined_at, roles: this.roles.map((_) => _.id),
premium_since:this.premium_since, joined_at: this.joined_at,
deaf:this.deaf, premium_since: this.premium_since,
mute:this.mute, deaf: this.deaf,
pending:this.pending mute: this.mute,
pending: this.pending,
},this.owner) },
this.owner,
);
} }
pronouns?:string; pronouns?: string;
bio?:string; bio?: string;
hypotheticalpfp=false; hypotheticalpfp = false;
hypotheticalbanner=false; hypotheticalbanner = false;
accent_color?:number; accent_color?: number;
get headers(){ get headers() {
return this.owner.headers; return this.owner.headers;
} }
updatepfp(file: Blob): void{ updatepfp(file: Blob): void {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onload = ()=>{ reader.onload = () => {
fetch(this.info.api + `/guilds/${this.guild.id}/members/${this.id}/`, { fetch(this.info.api + `/guilds/${this.guild.id}/members/${this.id}/`, {
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
@ -129,11 +131,11 @@ class Member extends SnowFlake{
}); });
}; };
} }
updatebanner(file: Blob | null): void{ updatebanner(file: Blob | null): void {
if(file){ if (file) {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onload = ()=>{ reader.onload = () => {
fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, { fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, {
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
@ -142,7 +144,7 @@ class Member extends SnowFlake{
}), }),
}); });
}; };
}else{ } else {
fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, { fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, {
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
@ -153,11 +155,7 @@ class Member extends SnowFlake{
} }
} }
updateProfile(json: { updateProfile(json: {bio?: string | null; pronouns?: string | null; nick?: string | null}) {
bio?: string|null;
pronouns?: string|null;
nick?:string|null;
}){
console.log(JSON.stringify(json)); console.log(JSON.stringify(json));
/* /*
if(json.bio===""){ if(json.bio===""){
@ -176,24 +174,26 @@ class Member extends SnowFlake{
body: JSON.stringify(json), body: JSON.stringify(json),
}); });
} }
showEditProfile(){ showEditProfile() {
const settings=new Settings(""); const settings = new Settings("");
this.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true})); this.editProfile(
settings.addButton(I18n.getTranslation("user.editServerProfile"), {ltr: true}),
);
settings.show(); settings.show();
} }
editProfile(options:Options){ editProfile(options: Options) {
if(this.hasPermission("CHANGE_NICKNAME")){ if (this.hasPermission("CHANGE_NICKNAME")) {
const hypotheticalProfile = document.createElement("div"); const hypotheticalProfile = document.createElement("div");
let file: undefined | File | null; let file: undefined | File | null;
let newpronouns: string | undefined; let newpronouns: string | undefined;
let newbio: string | undefined; let newbio: string | undefined;
let nick:string|undefined; let nick: string | undefined;
const hypomember = this.clone(); const hypomember = this.clone();
let color: string; let color: string;
async function regen(){ async function regen() {
hypotheticalProfile.textContent = ""; hypotheticalProfile.textContent = "";
const hypoprofile = await hypomember.user.buildprofile(-1, -1,hypomember); const hypoprofile = await hypomember.user.buildprofile(-1, -1, hypomember);
hypotheticalProfile.appendChild(hypoprofile); hypotheticalProfile.appendChild(hypoprofile);
} }
@ -202,33 +202,33 @@ class Member extends SnowFlake{
const settingsRight = options.addOptions(""); const settingsRight = options.addOptions("");
settingsRight.addHTMLArea(hypotheticalProfile); settingsRight.addHTMLArea(hypotheticalProfile);
const nicky=settingsLeft.addTextInput(I18n.getTranslation("member.nick:"),()=>{},{ const nicky = settingsLeft.addTextInput(I18n.getTranslation("member.nick:"), () => {}, {
initText:this.nick||"" initText: this.nick || "",
}); });
nicky.watchForChange(_=>{ nicky.watchForChange((_) => {
hypomember.nick=_; hypomember.nick = _;
nick=_; nick = _;
regen(); regen();
}) });
const finput = settingsLeft.addFileInput( const finput = settingsLeft.addFileInput(
I18n.getTranslation("uploadPfp"), I18n.getTranslation("uploadPfp"),
_=>{ (_) => {
if(file){ if (file) {
this.updatepfp(file); this.updatepfp(file);
} }
}, },
{ clear: true } {clear: true},
); );
finput.watchForChange(_=>{ finput.watchForChange((_) => {
if(!_){ if (!_) {
file = null; file = null;
hypomember.avatar = undefined; hypomember.avatar = undefined;
hypomember.hypotheticalpfp = true; hypomember.hypotheticalpfp = true;
regen(); regen();
return; return;
} }
if(_.length){ if (_.length) {
file = _[0]; file = _[0];
const blob = URL.createObjectURL(file); const blob = URL.createObjectURL(file);
hypomember.avatar = blob; hypomember.avatar = blob;
@ -239,22 +239,22 @@ class Member extends SnowFlake{
let bfile: undefined | File | null; let bfile: undefined | File | null;
const binput = settingsLeft.addFileInput( const binput = settingsLeft.addFileInput(
I18n.getTranslation("uploadBanner"), I18n.getTranslation("uploadBanner"),
_=>{ (_) => {
if(bfile !== undefined){ if (bfile !== undefined) {
this.updatebanner(bfile); this.updatebanner(bfile);
} }
}, },
{ clear: true } {clear: true},
); );
binput.watchForChange(_=>{ binput.watchForChange((_) => {
if(!_){ if (!_) {
bfile = null; bfile = null;
hypomember.banner = undefined; hypomember.banner = undefined;
hypomember.hypotheticalbanner = true; hypomember.hypotheticalbanner = true;
regen(); regen();
return; return;
} }
if(_.length){ if (_.length) {
bfile = _[0]; bfile = _[0];
const blob = URL.createObjectURL(bfile); const blob = URL.createObjectURL(bfile);
hypomember.banner = blob; hypomember.banner = blob;
@ -265,43 +265,43 @@ class Member extends SnowFlake{
let changed = false; let changed = false;
const pronounbox = settingsLeft.addTextInput( const pronounbox = settingsLeft.addTextInput(
I18n.getTranslation("pronouns"), I18n.getTranslation("pronouns"),
_=>{ (_) => {
if(newpronouns!==undefined||newbio!==undefined||changed!==undefined){ if (newpronouns !== undefined || newbio !== undefined || changed !== undefined) {
this.updateProfile({ this.updateProfile({
pronouns: newpronouns, pronouns: newpronouns,
bio: newbio, bio: newbio,
//accent_color: Number.parseInt("0x" + color.substr(1), 16), //accent_color: Number.parseInt("0x" + color.substr(1), 16),
nick nick,
}); });
} }
}, },
{ initText: this.pronouns } {initText: this.pronouns},
); );
pronounbox.watchForChange(_=>{ pronounbox.watchForChange((_) => {
hypomember.pronouns = _; hypomember.pronouns = _;
newpronouns = _; newpronouns = _;
regen(); regen();
}); });
const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, { const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), (_) => {}, {
initText: this.bio, initText: this.bio,
}); });
bioBox.watchForChange(_=>{ bioBox.watchForChange((_) => {
newbio = _; newbio = _;
hypomember.bio = _; hypomember.bio = _;
regen(); regen();
}); });
return;//Returns early to stop errors return; //Returns early to stop errors
if(this.accent_color){ if (this.accent_color) {
color = "#" + this.accent_color.toString(16); color = "#" + this.accent_color.toString(16);
}else{ } else {
color = "transparent"; color = "transparent";
} }
const colorPicker = settingsLeft.addColorInput( const colorPicker = settingsLeft.addColorInput(
I18n.getTranslation("profileColor"), I18n.getTranslation("profileColor"),
_=>{}, (_) => {},
{ initColor: color } {initColor: color},
); );
colorPicker.watchForChange(_=>{ colorPicker.watchForChange((_) => {
console.log(); console.log();
color = _; color = _;
hypomember.accent_color = Number.parseInt("0x" + _.substr(1), 16); hypomember.accent_color = Number.parseInt("0x" + _.substr(1), 16);
@ -310,116 +310,116 @@ class Member extends SnowFlake{
}); });
} }
} }
update(memberjson: memberjson){ update(memberjson: memberjson) {
if(memberjson.roles){ if (memberjson.roles) {
this.roles=[]; this.roles = [];
} }
for(const key of Object.keys(memberjson)){ for (const key of Object.keys(memberjson)) {
if(key === "guild" || key === "owner" || key === "user"){ if (key === "guild" || key === "owner" || key === "user") {
continue; continue;
} }
if(key === "roles"){ if (key === "roles") {
for(const strrole of memberjson.roles){ for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole); const role = this.guild.roleids.get(strrole);
if(!role)continue; if (!role) continue;
this.roles.push(role); this.roles.push(role);
} }
if(!this.user.bot){ if (!this.user.bot) {
const everyone=this.guild.roleids.get(this.guild.id); const everyone = this.guild.roleids.get(this.guild.id);
if(everyone&&(this.roles.indexOf(everyone)===-1)){ if (everyone && this.roles.indexOf(everyone) === -1) {
this.roles.push(everyone) this.roles.push(everyone);
} }
} }
continue; continue;
} }
if(key === "presence"){ if (key === "presence") {
this.getPresence(memberjson.presence); this.getPresence(memberjson.presence);
continue; continue;
} }
(this as any)[key] = (memberjson as any)[key]; (this as any)[key] = (memberjson as any)[key];
} }
this.roles.sort((a, b)=>{ this.roles.sort((a, b) => {
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b); return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
}); });
} }
get guild(){ get guild() {
return this.owner; return this.owner;
} }
get localuser(){ get localuser() {
return this.guild.localuser; return this.guild.localuser;
} }
get info(){ get info() {
return this.owner.info; return this.owner.info;
} }
static async new( static async new(memberjson: memberjson, owner: Guild): Promise<Member | undefined> {
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined>{
let user: User; let user: User;
if(owner.localuser.userMap.has(memberjson.id)){ if (owner.localuser.userMap.has(memberjson.id)) {
if(memberjson.user){ if (memberjson.user) {
new User(memberjson.user, owner.localuser); new User(memberjson.user, owner.localuser);
} }
user = owner.localuser.userMap.get(memberjson.id) as User; user = owner.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){ } else if (memberjson.user) {
user = new User(memberjson.user, owner.localuser); user = new User(memberjson.user, owner.localuser);
}else{ } else {
throw new Error("missing user object of this member"); throw new Error("missing user object of this member");
} }
if(user.members.has(owner)){ if (user.members.has(owner)) {
let memb = user.members.get(owner); let memb = user.members.get(owner);
if(memb === undefined){ if (memb === undefined) {
memb = new Member(memberjson, owner); memb = new Member(memberjson, owner);
user.members.set(owner, memb); user.members.set(owner, memb);
owner.members.add(memb); owner.members.add(memb);
return memb; return memb;
}else if(memb instanceof Promise){ } else if (memb instanceof Promise) {
const member=await memb; //I should do something else, though for now this is "good enough"; const member = await memb; //I should do something else, though for now this is "good enough";
if(member){ if (member) {
member.update(memberjson); member.update(memberjson);
} }
return member; return member;
}else{ } else {
if(memberjson.presence){ if (memberjson.presence) {
memb.getPresence(memberjson.presence); memb.getPresence(memberjson.presence);
} }
memb.update(memberjson); memb.update(memberjson);
return memb; return memb;
} }
}else{ } else {
const memb = new Member(memberjson, owner); const memb = new Member(memberjson, owner);
user.members.set(owner, memb); user.members.set(owner, memb);
owner.members.add(memb); owner.members.add(memb);
return memb; return memb;
} }
} }
compare(str:string){ compare(str: string) {
function similar(str2:string|null|undefined){ function similar(str2: string | null | undefined) {
if(!str2) return 0; if (!str2) return 0;
const strl=Math.max(str.length,1) const strl = Math.max(str.length, 1);
if(str2.includes(str)){ if (str2.includes(str)) {
return strl/str2.length; return strl / str2.length;
}else if(str2.toLowerCase().includes(str.toLowerCase())){ } else if (str2.toLowerCase().includes(str.toLowerCase())) {
return strl/str2.length/1.2; return strl / str2.length / 1.2;
} }
return 0; return 0;
} }
return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username),similar(this.id)/1.5); return Math.max(
similar(this.user.name),
similar(this.user.nickname),
similar(this.nick),
similar(this.user.username),
similar(this.id) / 1.5,
);
} }
static async resolveMember( static async resolveMember(user: User, guild: Guild): Promise<Member | undefined> {
user: User,
guild: Guild
): Promise<Member | undefined>{
const maybe = user.members.get(guild); const maybe = user.members.get(guild);
if(!user.members.has(guild)){ if (!user.members.has(guild)) {
const membpromise = guild.localuser.resolvemember(user.id, guild.id); const membpromise = guild.localuser.resolvemember(user.id, guild.id);
const promise = new Promise<Member | undefined>(async res=>{ const promise = new Promise<Member | undefined>(async (res) => {
const membjson = await membpromise; const membjson = await membpromise;
if(membjson === undefined){ if (membjson === undefined) {
return res(undefined); return res(undefined);
}else{ } else {
const member = new Member(membjson, guild); const member = new Member(membjson, guild);
const map = guild.localuser.presences; const map = guild.localuser.presences;
member.getPresence(map.get(member.id)); member.getPresence(map.get(member.id));
@ -429,62 +429,62 @@ class Member extends SnowFlake{
} }
}); });
user.members.set(guild, promise); user.members.set(guild, promise);
const member=await promise; const member = await promise;
if(member){ if (member) {
guild.members.add(member); guild.members.add(member);
} }
return member; return member;
} }
if(maybe instanceof Promise){ if (maybe instanceof Promise) {
return await maybe; return await maybe;
}else{ } else {
return maybe; return maybe;
} }
} }
public getPresence(presence: presencejson | undefined){ public getPresence(presence: presencejson | undefined) {
this.user.getPresence(presence); this.user.getPresence(presence);
} }
/** /**
* @todo * @todo
*/ */
highInfo(){ highInfo() {
fetch( fetch(
this.info.api + this.info.api +
"/users/" + "/users/" +
this.id + this.id +
"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" +
this.guild.id, this.guild.id,
{ headers: this.guild.headers } {headers: this.guild.headers},
); );
} }
hasRole(ID: string){ hasRole(ID: string) {
for(const thing of this.roles){ for (const thing of this.roles) {
if(thing.id === ID){ if (thing.id === ID) {
return true; return true;
} }
} }
return false; return false;
} }
getColor(){ getColor() {
for(const thing of this.roles){ for (const thing of this.roles) {
const color = thing.getColor(); const color = thing.getColor();
if(color){ if (color) {
return color; return color;
} }
} }
return""; return "";
} }
isAdmin(){ isAdmin() {
for(const role of this.roles){ for (const role of this.roles) {
if(role.permissions.getPermission("ADMINISTRATOR")){ if (role.permissions.getPermission("ADMINISTRATOR")) {
return true; return true;
} }
} }
return this.guild.properties.owner_id === this.user.id; return this.guild.properties.owner_id === this.user.id;
} }
bind(html: HTMLElement){ bind(html: HTMLElement) {
if(html.tagName === "SPAN"){ if (html.tagName === "SPAN") {
if(!this){ if (!this) {
return; return;
} }
/* /*
@ -497,23 +497,23 @@ class Member extends SnowFlake{
//this.profileclick(html); //this.profileclick(html);
} }
profileclick(/* html: HTMLElement */){ profileclick(/* html: HTMLElement */) {
//to be implemented //to be implemented
} }
get name(){ get name() {
return this.nick || this.user.username; return this.nick || this.user.username;
} }
kick(){ kick() {
const menu = new Dialog(""); const menu = new Dialog("");
const form=menu.options.addForm("",((e:any)=>{ const form = menu.options.addForm("", (e: any) => {
this.kickAPI(e.reason); this.kickAPI(e.reason);
menu.hide(); menu.hide();
})); });
form.addTitle(I18n.getTranslation("member.kick",this.name,this.guild.properties.name)); form.addTitle(I18n.getTranslation("member.kick", this.name, this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"),"reason"); form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
menu.show(); menu.show();
} }
kickAPI(reason: string){ kickAPI(reason: string) {
const headers = structuredClone(this.guild.headers); const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason; (headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, { fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, {
@ -521,35 +521,35 @@ class Member extends SnowFlake{
headers, headers,
}); });
} }
ban(){ ban() {
const menu = new Dialog(""); const menu = new Dialog("");
const form=menu.options.addForm("",((e:any)=>{ const form = menu.options.addForm("", (e: any) => {
this.banAPI(e.reason); this.banAPI(e.reason);
menu.hide(); menu.hide();
})); });
form.addTitle(I18n.getTranslation("member.ban",this.name,this.guild.properties.name)); form.addTitle(I18n.getTranslation("member.ban", this.name, this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"),"reason"); form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
menu.show(); menu.show();
} }
addRole(role:Role){ addRole(role: Role) {
const roles=this.roles.map(_=>_.id) const roles = this.roles.map((_) => _.id);
roles.push(role.id); roles.push(role.id);
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{ fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
method:"PATCH", method: "PATCH",
headers:this.guild.headers, headers: this.guild.headers,
body:JSON.stringify({roles}) body: JSON.stringify({roles}),
}) });
} }
removeRole(role:Role){ removeRole(role: Role) {
let roles=this.roles.map(_=>_.id) let roles = this.roles.map((_) => _.id);
roles=roles.filter(_=>_!==role.id); roles = roles.filter((_) => _ !== role.id);
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{ fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
method:"PATCH", method: "PATCH",
headers:this.guild.headers, headers: this.guild.headers,
body:JSON.stringify({roles}) body: JSON.stringify({roles}),
}) });
} }
banAPI(reason: string){ banAPI(reason: string) {
const headers = structuredClone(this.guild.headers); const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason; (headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, { fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, {
@ -557,16 +557,16 @@ class Member extends SnowFlake{
headers, headers,
}); });
} }
hasPermission(name: string): boolean{ hasPermission(name: string): boolean {
if(this.isAdmin()){ if (this.isAdmin()) {
return true; return true;
} }
for(const thing of this.roles){ for (const thing of this.roles) {
if(thing.permissions.getPermission(name)){ if (thing.permissions.getPermission(name)) {
return true; return true;
} }
} }
return false; return false;
} }
} }
export{ Member }; export {Member};

File diff suppressed because it is too large Load diff

View file

@ -1,157 +1,157 @@
import { I18n } from "../i18n.js"; import {I18n} from "../i18n.js";
import{ getapiurls }from"../utils/utils.js"; import {getapiurls} from "../utils/utils.js";
import { getBulkUsers, Specialuser } from "../utils/utils.js"; import {getBulkUsers, Specialuser} from "../utils/utils.js";
import { Permissions } from "../permissions.js"; import {Permissions} from "../permissions.js";
type botjsonfetch={ type botjsonfetch = {
guilds:{ guilds: {
id: string, id: string;
name: string, name: string;
icon: string, icon: string;
mfa_level: number, mfa_level: number;
permissions: string permissions: string;
}[], }[];
"user": { user: {
id: string, id: string;
username: string, username: string;
avatar: string, avatar: string;
avatar_decoration?: string, avatar_decoration?: string;
discriminator: string, discriminator: string;
public_flags: number public_flags: number;
}, };
application: { application: {
id: string, id: string;
name: string, name: string;
icon: string|null, icon: string | null;
description: string, description: string;
summary: string, summary: string;
type: null,//not sure what this means :P type: null; //not sure what this means :P
hook: boolean, hook: boolean;
guild_id: null|string, guild_id: null | string;
bot_public: boolean, bot_public: boolean;
bot_require_code_grant: boolean, bot_require_code_grant: boolean;
verify_key: "IMPLEMENTME",//no clue what this is meant to be :P verify_key: "IMPLEMENTME"; //no clue what this is meant to be :P
flags: number flags: number;
}, };
bot: { bot: {
id: string, id: string;
username: string, username: string;
avatar: string|null, avatar: string | null;
avatar_decoration: null|string, avatar_decoration: null | string;
discriminator: string, discriminator: string;
public_flags: number, public_flags: number;
bot: boolean, bot: boolean;
approximated_guild_count: number approximated_guild_count: number;
}, };
authorized: boolean authorized: boolean;
} };
(async ()=>{ (async () => {
const users = getBulkUsers(); const users = getBulkUsers();
const params=new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const well = params.get("instance"); const well = params.get("instance");
const permstr=params.get("permissions"); const permstr = params.get("permissions");
const joinable: Specialuser[] = []; const joinable: Specialuser[] = [];
for(const key in users.users){ for (const key in users.users) {
if(Object.prototype.hasOwnProperty.call(users.users, key)){ if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key]; const user: Specialuser = users.users[key];
if(well && user.serverurls.wellknown.includes(well)){ if (well && user.serverurls.wellknown.includes(well)) {
joinable.push(user); joinable.push(user);
} }
console.log(user); console.log(user);
} }
} }
let urls: { api: string; cdn: string } | undefined; let urls: {api: string; cdn: string} | undefined;
if(!joinable.length && well){ if (!joinable.length && well) {
const out = await getapiurls(well); const out = await getapiurls(well);
if(out){ if (out) {
urls = out; urls = out;
for(const key in users.users){ for (const key in users.users) {
if(Object.prototype.hasOwnProperty.call(users.users, key)){ if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key]; const user: Specialuser = users.users[key];
if(user.serverurls.api.includes(out.api)){ if (user.serverurls.api.includes(out.api)) {
joinable.push(user); joinable.push(user);
} }
console.log(user); console.log(user);
} }
} }
}else{ } else {
throw new Error( throw new Error("Someone needs to handle the case where the servers don't exist");
"Someone needs to handle the case where the servers don't exist"
);
} }
}else{ } else {
urls = joinable[0].serverurls; urls = joinable[0].serverurls;
} }
if(!joinable.length){ if (!joinable.length) {
document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot"; document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot";
} }
await I18n.done; await I18n.done;
function showGuilds(user:Specialuser){ function showGuilds(user: Specialuser) {
if(!urls) return; if (!urls) return;
fetch(urls.api+"/oauth2/authorize/"+window.location.search,{ fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers:{ headers: {
Authorization:user.token Authorization: user.token,
} },
}).then(_=>_.json()).then((json:botjsonfetch)=>{
const guilds:botjsonfetch["guilds"]=[];
for(const guild of json.guilds){
const permisions=new Permissions(guild.permissions)
if(permisions.hasPermission("MANAGE_GUILD")){
guilds.push(guild);
}
}
const dialog=document.createElement("dialog");
dialog.classList.add("flexttb","accountSwitcher");
const h1=document.createElement("h1");
dialog.append(h1);
h1.textContent="Invite to server:";
const select=document.createElement("select");
const selectSpan=document.createElement("span");
selectSpan.classList.add("selectspan");
const selectArrow = document.createElement("span");
selectArrow.classList.add("svgicon","svg-category","selectarrow");
for(const guild of guilds){
const option=document.createElement("option");
option.textContent=guild.name;
option.value=guild.id;
select.append(option);
}
selectSpan.append(select);
selectSpan.append(selectArrow);
dialog.append(selectSpan);
const button=document.createElement("button");
button.textContent="Invite";
dialog.append(button);
button.onclick=()=>{
const id=select.value;
const params2=new URLSearchParams("");
params2.set("client_id",params.get("client_id") as string)
fetch(urls.api+"/oauth2/authorize?"+params2.toString(),{
method:"POST",
body:JSON.stringify({
authorize:true,
guild_id:id,
permissions:permstr
}),
headers:{
"Content-type": "application/json; charset=UTF-8",
Authorization:user.token,
}
}).then(req=>{
if(req.ok){
alert("Bot added successfully");
}
})
}
document.body.append(dialog);
}) })
.then((_) => _.json())
.then((json: botjsonfetch) => {
const guilds: botjsonfetch["guilds"] = [];
for (const guild of json.guilds) {
const permisions = new Permissions(guild.permissions);
if (permisions.hasPermission("MANAGE_GUILD")) {
guilds.push(guild);
}
}
const dialog = document.createElement("dialog");
dialog.classList.add("flexttb", "accountSwitcher");
const h1 = document.createElement("h1");
dialog.append(h1);
h1.textContent = "Invite to server:";
const select = document.createElement("select");
const selectSpan = document.createElement("span");
selectSpan.classList.add("selectspan");
const selectArrow = document.createElement("span");
selectArrow.classList.add("svgicon", "svg-category", "selectarrow");
for (const guild of guilds) {
const option = document.createElement("option");
option.textContent = guild.name;
option.value = guild.id;
select.append(option);
}
selectSpan.append(select);
selectSpan.append(selectArrow);
dialog.append(selectSpan);
const button = document.createElement("button");
button.textContent = "Invite";
dialog.append(button);
button.onclick = () => {
const id = select.value;
const params2 = new URLSearchParams("");
params2.set("client_id", params.get("client_id") as string);
fetch(urls.api + "/oauth2/authorize?" + params2.toString(), {
method: "POST",
body: JSON.stringify({
authorize: true,
guild_id: id,
permissions: permstr,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
Authorization: user.token,
},
}).then((req) => {
if (req.ok) {
alert("Bot added successfully");
}
});
};
document.body.append(dialog);
});
} }
function showAccounts(): void{ function showAccounts(): void {
const table = document.createElement("dialog"); const table = document.createElement("dialog");
for(const user of joinable){ for (const user of joinable) {
console.log(user.pfpsrc); console.log(user.pfpsrc);
const userinfo = document.createElement("div"); const userinfo = document.createElement("div");
@ -168,16 +168,14 @@ type botjsonfetch={
userDiv.append(document.createElement("br")); userDiv.append(document.createElement("br"));
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = user.serverurls.wellknown span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
.replace("https://", "")
.replace("http://", "");
span.classList.add("serverURL"); span.classList.add("serverURL");
userDiv.append(span); userDiv.append(span);
userinfo.append(userDiv); userinfo.append(userDiv);
table.append(userinfo); table.append(userinfo);
userinfo.addEventListener("click", ()=>{ userinfo.addEventListener("click", () => {
table.remove(); table.remove();
showGuilds(user); showGuilds(user);
}); });
@ -186,14 +184,14 @@ type botjsonfetch={
const td = document.createElement("div"); const td = document.createElement("div");
td.classList.add("switchtable"); td.classList.add("switchtable");
td.textContent = "Login or create an account ⇌"; td.textContent = "Login or create an account ⇌";
td.addEventListener("click", ()=>{ td.addEventListener("click", () => {
const l = new URLSearchParams("?"); const l = new URLSearchParams("?");
l.set("goback", window.location.href); l.set("goback", window.location.href);
l.set("instance", well!); l.set("instance", well!);
window.location.href = "/login?" + l.toString(); window.location.href = "/login?" + l.toString();
}); });
if(!joinable.length){ if (!joinable.length) {
const l = new URLSearchParams("?"); const l = new URLSearchParams("?");
l.set("goback", window.location.href); l.set("goback", window.location.href);
l.set("instance", well!); l.set("instance", well!);
@ -201,54 +199,56 @@ type botjsonfetch={
} }
table.append(td); table.append(td);
table.classList.add("flexttb","accountSwitcher"); table.classList.add("flexttb", "accountSwitcher");
console.log(table); console.log(table);
document.body.append(table); document.body.append(table);
} }
const user=joinable[0]; const user = joinable[0];
if(!user){ if (!user) {
return; return;
} }
fetch(urls.api+"/oauth2/authorize/"+window.location.search,{ fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers:{ headers: {
Authorization:user.token Authorization: user.token,
} },
}).then(_=>_.json()).then((json:botjsonfetch)=>{ })
const title=document.getElementById("invitename"); .then((_) => _.json())
if(title){ .then((json: botjsonfetch) => {
title.textContent=`Invite ${json.bot.username} to your servers` const title = document.getElementById("invitename");
} if (title) {
const desc=document.getElementById("invitedescription"); title.textContent = `Invite ${json.bot.username} to your servers`;
if(desc){ }
desc.textContent=json.application.description; const desc = document.getElementById("invitedescription");
} if (desc) {
const pfp=document.getElementById("inviteimg") as HTMLImageElement; desc.textContent = json.application.description;
if(json.bot.avatar !== null){ }
pfp.src=`${urls.cdn}/avatars/${json.bot.id}/${json.bot.avatar}.png`; const pfp = document.getElementById("inviteimg") as HTMLImageElement;
}else{ if (json.bot.avatar !== null) {
const int = Number((BigInt(json.bot.id) >> 22n) % 6n); pfp.src = `${urls.cdn}/avatars/${json.bot.id}/${json.bot.avatar}.png`;
pfp.src=`${urls.cdn}/embed/avatars/${int}.png`; } else {
} const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
const perms=document.getElementById("permissions") as HTMLDivElement; pfp.src = `${urls.cdn}/embed/avatars/${int}.png`;
}
const perms = document.getElementById("permissions") as HTMLDivElement;
if(perms&&permstr){ if (perms && permstr) {
perms.children[0].textContent=I18n.getTranslation("htmlPages.idpermissions") perms.children[0].textContent = I18n.getTranslation("htmlPages.idpermissions");
const permisions=new Permissions(permstr) const permisions = new Permissions(permstr);
for(const perm of Permissions.info()){ for (const perm of Permissions.info()) {
if(permisions.hasPermission(perm.name,false)){ if (permisions.hasPermission(perm.name, false)) {
const div=document.createElement("div"); const div = document.createElement("div");
const h2=document.createElement("h2"); const h2 = document.createElement("h2");
h2.textContent=perm.readableName; h2.textContent = perm.readableName;
div.append(h2,perm.description); div.append(h2, perm.description);
div.classList.add("flexttb"); div.classList.add("flexttb");
perms.append(div); perms.append(div);
}
} }
} }
} });
}) const AcceptInvite = document.getElementById("AcceptInvite");
const AcceptInvite=document.getElementById("AcceptInvite"); if (AcceptInvite) {
if(AcceptInvite){
AcceptInvite.addEventListener("click", showAccounts); AcceptInvite.addEventListener("click", showAccounts);
AcceptInvite.textContent=I18n.getTranslation("htmlPages.addBot") AcceptInvite.textContent = I18n.getTranslation("htmlPages.addBot");
} }
})(); })();

View file

@ -1,21 +1,30 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Bot Invite" property="og:title"> <meta content="Bot Invite" property="og:title" />
<meta name="description" content="Invite this bot to your server!"> <meta name="description" content="Invite this bot to your server!" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div> <div>
<div id="invitebody"> <div id="invitebody">
<img id="inviteimg" class="pfp"/> <img id="inviteimg" class="pfp" />
<h1 id="invitename">Bot Name</h1> <h1 id="invitename">Bot Name</h1>
<p id="invitedescription">Add Bot</p> <p id="invitedescription">Add Bot</p>
<div id="permissions"><h1>This will allow the bot to:</h1></div> <div id="permissions"><h1>This will allow the bot to:</h1></div>

View file

@ -1,40 +1,40 @@
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
class Permissions{ class Permissions {
allow: bigint; allow: bigint;
deny: bigint; deny: bigint;
readonly hasDeny: boolean; readonly hasDeny: boolean;
constructor(allow: string, deny: string = ""){ constructor(allow: string, deny: string = "") {
this.hasDeny = Boolean(deny); this.hasDeny = Boolean(deny);
try{ try {
this.allow = BigInt(allow); this.allow = BigInt(allow);
this.deny = BigInt(deny); this.deny = BigInt(deny);
}catch{ } catch {
this.allow = 0n; this.allow = 0n;
this.deny = 0n; this.deny = 0n;
console.error( 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.` `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{ getPermissionbit(b: number, big: bigint): boolean {
return Boolean((big >> BigInt(b)) & 1n); return Boolean((big >> BigInt(b)) & 1n);
} }
setPermissionbit(b: number, state: boolean, big: bigint): bigint{ setPermissionbit(b: number, state: boolean, big: bigint): bigint {
const bit = 1n << BigInt(b); const bit = 1n << BigInt(b);
return(big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
} }
//private static info: { name: string; readableName: string; description: string }[]; //private static info: { name: string; readableName: string; description: string }[];
static *info():Generator<{ name: string; readableName: string; description: string }>{ static *info(): Generator<{name: string; readableName: string; description: string}> {
for(const thing of this.permisions){ for (const thing of this.permisions) {
yield { yield {
name:thing, name: thing,
readableName:I18n.getTranslation("permissions.readableNames."+thing), readableName: I18n.getTranslation("permissions.readableNames." + thing),
description:I18n.getTranslation("permissions.descriptions."+thing), description: I18n.getTranslation("permissions.descriptions." + thing),
} };
} }
} }
static permisions=[ static permisions = [
"CREATE_INSTANT_INVITE", "CREATE_INSTANT_INVITE",
"KICK_MEMBERS", "KICK_MEMBERS",
"BAN_MEMBERS", "BAN_MEMBERS",
@ -83,57 +83,50 @@ class Permissions{
"USE_EXTERNAL_SOUNDS", "USE_EXTERNAL_SOUNDS",
"SEND_VOICE_MESSAGES", "SEND_VOICE_MESSAGES",
"SEND_POLLS", "SEND_POLLS",
"USE_EXTERNAL_APPS" "USE_EXTERNAL_APPS",
]; ];
getPermission(name: string): number{ getPermission(name: string): number {
if(undefined===Permissions.permisions.indexOf(name)){ if (undefined === Permissions.permisions.indexOf(name)) {
console.error(name +" is not found in map",Permissions.permisions); console.error(name + " is not found in map", Permissions.permisions);
} }
if(this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)){ if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) {
return 1; return 1;
}else if( } else if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)) {
this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny) return -1;
){ } else {
return-1;
}else{
return 0; return 0;
} }
} }
hasPermission(name: string,adminOverride=true): boolean{ hasPermission(name: string, adminOverride = true): boolean {
if(this.deny){ if (this.deny) {
console.warn( console.warn(
"This function may of been used in error, think about using getPermision instead" "This function may of been used in error, think about using getPermision instead",
); );
} }
if(this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) return true;
return true; if (name !== "ADMINISTRATOR" && adminOverride) return this.hasPermission("ADMINISTRATOR");
if(name !== "ADMINISTRATOR"&&adminOverride)return this.hasPermission("ADMINISTRATOR");
return false; return false;
} }
setPermission(name: string, setto: number): void{ setPermission(name: string, setto: number): void {
const bit = Permissions.permisions.indexOf(name); const bit = Permissions.permisions.indexOf(name);
if(bit===undefined){ if (bit === undefined) {
return console.error( return console.error(
"Tried to set permission to " + "Tried to set permission to " + setto + " for " + name + " but it doesn't exist",
setto +
" for " +
name +
" but it doesn't exist"
); );
} }
if(setto === 0){ if (setto === 0) {
this.deny = this.setPermissionbit(bit, false, this.deny); this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow); this.allow = this.setPermissionbit(bit, false, this.allow);
}else if(setto === 1){ } else if (setto === 1) {
this.deny = this.setPermissionbit(bit, false, this.deny); this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, true, this.allow); this.allow = this.setPermissionbit(bit, true, this.allow);
}else if(setto === -1){ } else if (setto === -1) {
this.deny = this.setPermissionbit(bit, true, this.deny); this.deny = this.setPermissionbit(bit, true, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow); this.allow = this.setPermissionbit(bit, false, this.allow);
}else{ } else {
console.error("invalid number entered:" + setto); console.error("invalid number entered:" + setto);
} }
} }
} }
export{ Permissions }; export {Permissions};

View file

@ -1,59 +1,80 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description"> <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div id="logindiv"> <div id="logindiv">
<h1>Create an account</h1> <h1>Create an account</h1>
<form id="register" submit="registertry(e)"> <form id="register" submit="registertry(e)">
<div> <div>
<label for="instance" id="instanceField"><b>Instance:</b></label> <label for="instance" id="instanceField"><b>Instance:</b></label>
<p id="verify"></p> <p id="verify"></p>
<input type="search" list="instances" placeholder="Instance URL" id="instancein" name="instance" value="" required> <input
type="search"
list="instances"
placeholder="Instance URL"
id="instancein"
name="instance"
value=""
required
/>
</div> </div>
<div> <div>
<label for="uname" id="emailField"><b>Email:</b></label> <label for="uname" id="emailField"><b>Email:</b></label>
<input type="text" placeholder="Enter Email" name="uname" id="uname" required> <input type="text" placeholder="Enter Email" name="uname" id="uname" required />
</div> </div>
<div> <div>
<label for="uname" id="userField"><b>Username:</b></label> <label for="uname" id="userField"><b>Username:</b></label>
<input type="text" placeholder="Enter Username" name="username" id="username" required> <input type="text" placeholder="Enter Username" name="username" id="username" required />
</div> </div>
<div> <div>
<label for="psw" id="pwField"><b>Password:</b></label> <label for="psw" id="pwField"><b>Password:</b></label>
<input type="password" placeholder="Enter Password" name="psw" id="psw" required> <input type="password" placeholder="Enter Password" name="psw" id="psw" required />
</div> </div>
<div> <div>
<label for="psw2" id="pw2Field"><b>Enter password again:</b></label> <label for="psw2" id="pw2Field"><b>Enter password again:</b></label>
<input type="password" placeholder="Enter Password Again" name="psw2" id="psw2" required> <input
type="password"
placeholder="Enter Password Again"
name="psw2"
id="psw2"
required
/>
</div> </div>
<div> <div>
<label for="date" id="dobField"><b>Date of birth:</b></label> <label for="date" id="dobField"><b>Date of birth:</b></label>
<input type="date" id="date" name="date"> <input type="date" id="date" name="date" />
</div> </div>
<div> <div>
<b id="TOSbox">I agree to the <a href="" id="TOSa">Terms of Service</a>:</b> <b id="TOSbox">I agree to the <a href="" id="TOSa">Terms of Service</a>:</b>
<input type="checkbox" id="TOS" name="TOS"> <input type="checkbox" id="TOS" name="TOS" />
</div> </div>
<p class="wrongred" id="wrong"></p> <p class="wrongred" id="wrong"></p>
<div id="h-captcha"> <div id="h-captcha"></div>
</div>
<button type="submit" class="dontgrow" id="createAccount">Create account</button> <button type="submit" class="dontgrow" id="createAccount">Create account</button>
</form> </form>
<a href="/login.html" id="switch" id="alreadyHave">Already have an account?</a> <a href="/login.html" id="switch" id="alreadyHave">Already have an account?</a>

View file

@ -1,31 +1,30 @@
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import{ checkInstance }from"./utils/utils.js"; import {checkInstance} from "./utils/utils.js";
import {adduser} from"./login.js"; import {adduser} from "./login.js";
import { MarkDown } from "./markdown.js"; import {MarkDown} from "./markdown.js";
await I18n.done await I18n.done;
const registerElement = document.getElementById("register"); const registerElement = document.getElementById("register");
if(registerElement){ if (registerElement) {
registerElement.addEventListener("submit", registertry); registerElement.addEventListener("submit", registertry);
} }
(async ()=>{ (async () => {
await I18n.done; await I18n.done;
const userField=document.getElementById("userField"); const userField = document.getElementById("userField");
const pw2Field=document.getElementById("pw2Field"); const pw2Field = document.getElementById("pw2Field");
const dobField=document.getElementById("dobField"); const dobField = document.getElementById("dobField");
const createAccount=document.getElementById("createAccount"); const createAccount = document.getElementById("createAccount");
const alreadyHave=document.getElementById("alreadyHave"); const alreadyHave = document.getElementById("alreadyHave");
if(userField&&pw2Field&&alreadyHave&&createAccount&&dobField){ if (userField && pw2Field && alreadyHave && createAccount && dobField) {
userField.textContent=I18n.getTranslation("htmlPages.userField") userField.textContent = I18n.getTranslation("htmlPages.userField");
pw2Field.textContent=I18n.getTranslation("htmlPages.pw2Field") pw2Field.textContent = I18n.getTranslation("htmlPages.pw2Field");
dobField.textContent=I18n.getTranslation("htmlPages.dobField") dobField.textContent = I18n.getTranslation("htmlPages.dobField");
createAccount.textContent=I18n.getTranslation("htmlPages.createAccount") createAccount.textContent = I18n.getTranslation("htmlPages.createAccount");
alreadyHave.textContent=I18n.getTranslation("htmlPages.alreadyHave") alreadyHave.textContent = I18n.getTranslation("htmlPages.alreadyHave");
} }
})() })();
async function registertry(e: Event){ async function registertry(e: Event) {
e.preventDefault(); e.preventDefault();
const elements = (e.target as HTMLFormElement) const elements = (e.target as HTMLFormElement).elements as HTMLFormControlsCollection;
.elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value; const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value; const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value; const password = (elements[3] as HTMLInputElement).value;
@ -34,15 +33,17 @@ async function registertry(e: Event){
const consent = (elements[6] as HTMLInputElement).checked; const consent = (elements[6] as HTMLInputElement).checked;
const captchaKey = (elements[7] as HTMLInputElement)?.value; const captchaKey = (elements[7] as HTMLInputElement)?.value;
if(password !== confirmPassword){ if (password !== confirmPassword) {
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation("localuser.PasswordsNoMatch"); (document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation(
"localuser.PasswordsNoMatch",
);
return; return;
} }
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api); const apiurl = new URL(instanceInfo.api);
try{ try {
const response = await fetch(apiurl + "/auth/register", { const response = await fetch(apiurl + "/auth/register", {
body: JSON.stringify({ body: JSON.stringify({
date_of_birth: dateofbirth, date_of_birth: dateofbirth,
@ -60,9 +61,9 @@ async function registertry(e: Event){
const data = await response.json(); const data = await response.json();
if(data.captcha_sitekey){ if (data.captcha_sitekey) {
const capt = document.getElementById("h-captcha"); const capt = document.getElementById("h-captcha");
if(capt && !capt.children.length){ if (capt && !capt.children.length) {
const capty = document.createElement("div"); const capty = document.createElement("div");
capty.classList.add("h-captcha"); capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", data.captcha_sitekey); capty.setAttribute("data-sitekey", data.captcha_sitekey);
@ -70,15 +71,15 @@ async function registertry(e: Event){
script.src = "https://js.hcaptcha.com/1/api.js"; script.src = "https://js.hcaptcha.com/1/api.js";
capt.append(script); capt.append(script);
capt.append(capty); capt.append(capty);
}else{ } else {
eval("hcaptcha.reset()"); eval("hcaptcha.reset()");
} }
return; return;
} }
if(!data.token){ if (!data.token) {
handleErrors(data.errors, elements); handleErrors(data.errors, elements);
}else{ } else {
adduser({ adduser({
serverurls: instanceInfo, serverurls: instanceInfo,
email, email,
@ -88,74 +89,72 @@ async function registertry(e: Event){
const redir = new URLSearchParams(window.location.search).get("goback"); const redir = new URLSearchParams(window.location.search).get("goback");
window.location.href = redir ? redir : "/channels/@me"; window.location.href = redir ? redir : "/channels/@me";
} }
}catch(error){ } catch (error) {
console.error("Registration failed:", error); console.error("Registration failed:", error);
} }
} }
function handleErrors(errors: any, elements: HTMLFormControlsCollection){ function handleErrors(errors: any, elements: HTMLFormControlsCollection) {
if(errors.consent){ if (errors.consent) {
showError(elements[6] as HTMLElement, errors.consent._errors[0].message); showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
}else if(errors.password){ } else if (errors.password) {
showError( showError(
elements[3] as HTMLElement, elements[3] as HTMLElement,
I18n.getTranslation("register.passwordError",errors.password._errors[0].message) I18n.getTranslation("register.passwordError", errors.password._errors[0].message),
); );
}else if(errors.username){ } else if (errors.username) {
showError( showError(
elements[2] as HTMLElement, elements[2] as HTMLElement,
I18n.getTranslation("register.usernameError",errors.username._errors[0].message) I18n.getTranslation("register.usernameError", errors.username._errors[0].message),
); );
}else if(errors.email){ } else if (errors.email) {
showError( showError(
elements[1] as HTMLElement, elements[1] as HTMLElement,
I18n.getTranslation("register.emailError",errors.email._errors[0].message) I18n.getTranslation("register.emailError", errors.email._errors[0].message),
); );
}else if(errors.date_of_birth){ } else if (errors.date_of_birth) {
showError( showError(
elements[5] as HTMLElement, elements[5] as HTMLElement,
I18n.getTranslation("register.DOBError",errors.date_of_birth._errors[0].message) I18n.getTranslation("register.DOBError", errors.date_of_birth._errors[0].message),
); );
}else{ } else {
(document.getElementById("wrong") as HTMLElement).textContent = (document.getElementById("wrong") as HTMLElement).textContent =
errors[Object.keys(errors)[0]]._errors[0].message; errors[Object.keys(errors)[0]]._errors[0].message;
} }
} }
function showError(element: HTMLElement, message: string){ function showError(element: HTMLElement, message: string) {
const parent = element.parentElement!; const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName( let errorElement = parent.getElementsByClassName("suberror")[0] as HTMLElement;
"suberror" if (!errorElement) {
)[0] as HTMLElement;
if(!errorElement){
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("suberror", "suberrora"); div.classList.add("suberror", "suberrora");
parent.append(div); parent.append(div);
errorElement = div; errorElement = div;
}else{ } else {
errorElement.classList.remove("suberror"); errorElement.classList.remove("suberror");
setTimeout(()=>{ setTimeout(() => {
errorElement.classList.add("suberror"); errorElement.classList.add("suberror");
}, 100); }, 100);
} }
errorElement.textContent = message; errorElement.textContent = message;
} }
async function tosLogic(){ async function tosLogic() {
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api); const apiurl = new URL(instanceInfo.api);
const urlstr=apiurl.toString(); const urlstr = apiurl.toString();
const response = await fetch(urlstr + (urlstr.endsWith("/") ? "" : "/") + "ping"); const response = await fetch(urlstr + (urlstr.endsWith("/") ? "" : "/") + "ping");
const data = await response.json(); const data = await response.json();
const tosPage = data.instance.tosPage; const tosPage = data.instance.tosPage;
if(tosPage){ if (tosPage) {
const box=document.getElementById("TOSbox"); const box = document.getElementById("TOSbox");
if(!box) return; if (!box) return;
box.innerHTML =""; box.innerHTML = "";
box.append(new MarkDown(I18n.getTranslation("register.agreeTOS",tosPage)).makeHTML()); box.append(new MarkDown(I18n.getTranslation("register.agreeTOS", tosPage)).makeHTML());
}else{ } else {
document.getElementById("TOSbox")!.textContent =I18n.getTranslation("register.noTOS"); document.getElementById("TOSbox")!.textContent = I18n.getTranslation("register.noTOS");
} }
console.log(tosPage); console.log(tosPage);
} }

View file

@ -1,10 +1,10 @@
import{ Permissions }from"./permissions.js"; import {Permissions} from "./permissions.js";
import{ Localuser }from"./localuser.js"; import {Localuser} from "./localuser.js";
import{ Guild }from"./guild.js"; import {Guild} from "./guild.js";
import{ SnowFlake }from"./snowflake.js"; import {SnowFlake} from "./snowflake.js";
import{ rolesjson }from"./jsontypes.js"; import {rolesjson} from "./jsontypes.js";
import{ Search }from"./search.js"; import {Search} from "./search.js";
class Role extends SnowFlake{ class Role extends SnowFlake {
permissions: Permissions; permissions: Permissions;
owner: Guild; owner: Guild;
color!: number; color!: number;
@ -14,14 +14,14 @@ class Role extends SnowFlake{
icon!: string; icon!: string;
mentionable!: boolean; mentionable!: boolean;
unicode_emoji!: string; unicode_emoji!: string;
position!:number; position!: number;
headers: Guild["headers"]; headers: Guild["headers"];
constructor(json: rolesjson, owner: Guild){ constructor(json: rolesjson, owner: Guild) {
super(json.id); super(json.id);
this.headers = owner.headers; this.headers = owner.headers;
this.info = owner.info; this.info = owner.info;
for(const thing of Object.keys(json)){ for (const thing of Object.keys(json)) {
if(thing === "id"){ if (thing === "id") {
continue; continue;
} }
(this as any)[thing] = (json as any)[thing]; (this as any)[thing] = (json as any)[thing];
@ -29,39 +29,39 @@ class Role extends SnowFlake{
this.permissions = new Permissions(json.permissions); this.permissions = new Permissions(json.permissions);
this.owner = owner; this.owner = owner;
} }
newJson(json: rolesjson){ newJson(json: rolesjson) {
for(const thing of Object.keys(json)){ for (const thing of Object.keys(json)) {
if(thing === "id"||thing==="permissions"){ if (thing === "id" || thing === "permissions") {
continue; continue;
} }
(this as any)[thing] = (json as any)[thing]; (this as any)[thing] = (json as any)[thing];
} }
this.permissions.allow=BigInt(json.permissions); this.permissions.allow = BigInt(json.permissions);
} }
get guild(): Guild{ get guild(): Guild {
return this.owner; return this.owner;
} }
get localuser(): Localuser{ get localuser(): Localuser {
return this.guild.localuser; return this.guild.localuser;
} }
getColor(): string | null{ getColor(): string | null {
if(this.color === 0){ if (this.color === 0) {
return null; return null;
} }
return`#${this.color.toString(16)}`; return `#${this.color.toString(16)}`;
} }
canManage(){ canManage() {
if(this.guild.member.hasPermission("MANAGE_ROLES")){ if (this.guild.member.hasPermission("MANAGE_ROLES")) {
let max=-Infinity; let max = -Infinity;
this.guild.member.roles.forEach(r=>max=Math.max(max,r.position)) this.guild.member.roles.forEach((r) => (max = Math.max(max, r.position)));
return this.position<=max||this.guild.properties.owner_id===this.guild.member.id; return this.position <= max || this.guild.properties.owner_id === this.guild.member.id;
} }
return false; return false;
} }
} }
export{ Role }; export {Role};
import{ Options }from"./settings.js"; import {Options} from "./settings.js";
class PermissionToggle implements OptionsElement<number>{ class PermissionToggle implements OptionsElement<number> {
readonly rolejson: { readonly rolejson: {
name: string; name: string;
readableName: string; readableName: string;
@ -70,17 +70,13 @@ class PermissionToggle implements OptionsElement<number>{
permissions: Permissions; permissions: Permissions;
owner: Options; owner: Options;
value!: number; value!: number;
constructor( constructor(roleJSON: PermissionToggle["rolejson"], permissions: Permissions, owner: Options) {
roleJSON: PermissionToggle["rolejson"],
permissions: Permissions,
owner: Options
){
this.rolejson = roleJSON; this.rolejson = roleJSON;
this.permissions = permissions; this.permissions = permissions;
this.owner = owner; this.owner = owner;
} }
watchForChange(){} watchForChange() {}
generateHTML(): HTMLElement{ generateHTML(): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("setting"); div.classList.add("setting");
const name = document.createElement("span"); const name = document.createElement("span");
@ -94,7 +90,7 @@ class PermissionToggle implements OptionsElement<number>{
div.appendChild(p); div.appendChild(p);
return div; return div;
} }
generateCheckbox(): HTMLElement{ generateCheckbox(): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("tritoggle"); div.classList.add("tritoggle");
const state = this.permissions.getPermission(this.rolejson.name); const state = this.permissions.getPermission(this.rolejson.name);
@ -103,10 +99,10 @@ class PermissionToggle implements OptionsElement<number>{
on.type = "radio"; on.type = "radio";
on.name = this.rolejson.name; on.name = this.rolejson.name;
div.append(on); div.append(on);
if(state === 1){ if (state === 1) {
on.checked = true; on.checked = true;
} }
on.onclick = _=>{ on.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 1); this.permissions.setPermission(this.rolejson.name, 1);
this.owner.changed(); this.owner.changed();
}; };
@ -115,283 +111,293 @@ class PermissionToggle implements OptionsElement<number>{
no.type = "radio"; no.type = "radio";
no.name = this.rolejson.name; no.name = this.rolejson.name;
div.append(no); div.append(no);
if(state === 0){ if (state === 0) {
no.checked = true; no.checked = true;
} }
no.onclick = _=>{ no.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 0); this.permissions.setPermission(this.rolejson.name, 0);
this.owner.changed(); this.owner.changed();
}; };
if(this.permissions.hasDeny){ if (this.permissions.hasDeny) {
const off = document.createElement("input"); const off = document.createElement("input");
off.type = "radio"; off.type = "radio";
off.name = this.rolejson.name; off.name = this.rolejson.name;
div.append(off); div.append(off);
if(state === -1){ if (state === -1) {
off.checked = true; off.checked = true;
} }
off.onclick = _=>{ off.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, -1); this.permissions.setPermission(this.rolejson.name, -1);
this.owner.changed(); this.owner.changed();
}; };
} }
return div; return div;
} }
submit(){} submit() {}
} }
import{ OptionsElement, Buttons }from"./settings.js"; import {OptionsElement, Buttons} from "./settings.js";
import { Contextmenu } from "./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
import { Channel } from "./channel.js"; import {Channel} from "./channel.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
class RoleList extends Buttons{ class RoleList extends Buttons {
permissions: [Role, Permissions][]; permissions: [Role, Permissions][];
permission: Permissions; permission: Permissions;
readonly guild: Guild; readonly guild: Guild;
readonly channel: false|Channel; readonly channel: false | Channel;
declare buttons: [string, string][]; declare buttons: [string, string][];
readonly options: Options; readonly options: Options;
onchange: Function; onchange: Function;
curid?: string; curid?: string;
get info(){ get info() {
return this.guild.info; return this.guild.info;
} }
get headers(){ get headers() {
return this.guild.headers; return this.guild.headers;
} }
constructor(permissions:[Role, Permissions][], guild:Guild, onchange:Function, channel:false|Channel){ constructor(
permissions: [Role, Permissions][],
guild: Guild,
onchange: Function,
channel: false | Channel,
) {
super(""); super("");
this.guild = guild; this.guild = guild;
this.permissions = permissions; this.permissions = permissions;
this.channel = channel; this.channel = channel;
this.onchange = onchange; this.onchange = onchange;
const options = new Options("", this); const options = new Options("", this);
if(channel){ if (channel) {
this.permission = new Permissions("0", "0"); this.permission = new Permissions("0", "0");
}else{ } else {
this.permission = new Permissions("0"); this.permission = new Permissions("0");
} }
this.makeguildmenus(options); this.makeguildmenus(options);
for(const thing of Permissions.info()){ for (const thing of Permissions.info()) {
options.options.push( options.options.push(new PermissionToggle(thing, this.permission, options));
new PermissionToggle(thing, this.permission, options)
);
} }
for(const i of permissions){ for (const i of permissions) {
this.buttons.push([i[0].name, i[0].id]); this.buttons.push([i[0].name, i[0].id]);
} }
this.options = options; this.options = options;
guild.roleUpdate=this.groleUpdate.bind(this); guild.roleUpdate = this.groleUpdate.bind(this);
if(channel){ if (channel) {
channel.croleUpdate=this.croleUpdate.bind(this); channel.croleUpdate = this.croleUpdate.bind(this);
} }
} }
private groleUpdate(role:Role,added:1|0|-1){ private groleUpdate(role: Role, added: 1 | 0 | -1) {
if(!this.channel){ if (!this.channel) {
if(added===1){ if (added === 1) {
this.permissions.push([role,role.permissions]); this.permissions.push([role, role.permissions]);
} }
} }
if(added===-1){ if (added === -1) {
this.permissions=this.permissions.filter(r=>r[0]!==role); this.permissions = this.permissions.filter((r) => r[0] !== role);
} }
this.redoButtons(); this.redoButtons();
} }
private croleUpdate(role:Role,perm:Permissions,added:boolean){ private croleUpdate(role: Role, perm: Permissions, added: boolean) {
if(added){ if (added) {
this.permissions.push([role,perm]) this.permissions.push([role, perm]);
}else{ } else {
this.permissions=this.permissions.filter(r=>r[0]!==role); this.permissions = this.permissions.filter((r) => r[0] !== role);
} }
this.redoButtons(); this.redoButtons();
} }
makeguildmenus(option:Options){ makeguildmenus(option: Options) {
option.addButtonInput("",I18n.getTranslation("role.displaySettings"),()=>{ option.addButtonInput("", I18n.getTranslation("role.displaySettings"), () => {
const role=this.guild.roleids.get(this.curid as string); const role = this.guild.roleids.get(this.curid as string);
if(!role) return; if (!role) return;
const form=option.addSubForm(I18n.getTranslation("role.displaySettings"),()=>{},{ const form = option.addSubForm(I18n.getTranslation("role.displaySettings"), () => {}, {
fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid, fetchURL: this.info.api + "/guilds/" + this.guild.id + "/roles/" + this.curid,
method:"PATCH", method: "PATCH",
headers:this.headers, headers: this.headers,
traditionalSubmit:true traditionalSubmit: true,
}); });
form.addTextInput(I18n.getTranslation("role.name"),"name",{ form.addTextInput(I18n.getTranslation("role.name"), "name", {
initText:role.name initText: role.name,
}); });
form.addCheckboxInput(I18n.getTranslation("role.hoisted"),"hoist",{ form.addCheckboxInput(I18n.getTranslation("role.hoisted"), "hoist", {
initState:role.hoist initState: role.hoist,
}); });
form.addCheckboxInput(I18n.getTranslation("role.mentionable"),"mentionable",{ form.addCheckboxInput(I18n.getTranslation("role.mentionable"), "mentionable", {
initState:role.mentionable initState: role.mentionable,
}); });
const color="#"+role.color.toString(16).padStart(6,"0"); const color = "#" + role.color.toString(16).padStart(6, "0");
form.addColorInput(I18n.getTranslation("role.color"),"color",{ form.addColorInput(I18n.getTranslation("role.color"), "color", {
initColor:color initColor: color,
}); });
form.addPreprocessor((obj:any)=>{ form.addPreprocessor((obj: any) => {
obj.color=Number("0x"+obj.color.substring(1)); obj.color = Number("0x" + obj.color.substring(1));
console.log(obj.color); console.log(obj.color);
}) });
}) });
} }
static channelrolemenu=this.ChannelRoleMenu(); static channelrolemenu = this.ChannelRoleMenu();
static guildrolemenu=this.GuildRoleMenu(); static guildrolemenu = this.GuildRoleMenu();
private static ChannelRoleMenu(){ private static ChannelRoleMenu() {
const menu=new Contextmenu<RoleList,Role>("role settings"); const menu = new Contextmenu<RoleList, Role>("role settings");
menu.addbutton(()=>I18n.getTranslation("role.remove"),function(role){ menu.addbutton(
if(!this.channel) return; () => I18n.getTranslation("role.remove"),
console.log(role); function (role) {
fetch(this.info.api+"/channels/"+this.channel.id+"/permissions/"+role.id,{ if (!this.channel) return;
method:"DELETE", console.log(role);
headers:this.headers fetch(this.info.api + "/channels/" + this.channel.id + "/permissions/" + role.id, {
}) method: "DELETE",
},null); headers: this.headers,
});
},
null,
);
return menu; return menu;
} }
private static GuildRoleMenu(){ private static GuildRoleMenu() {
const menu=new Contextmenu<RoleList,Role>("role settings"); const menu = new Contextmenu<RoleList, Role>("role settings");
menu.addbutton(()=>I18n.getTranslation("role.delete"),function(role){ menu.addbutton(
if(!confirm(I18n.getTranslation("role.confirmDelete"))) return; () => I18n.getTranslation("role.delete"),
console.log(role); function (role) {
fetch(this.info.api+"/guilds/"+this.guild.id+"/roles/"+role.id,{ if (!confirm(I18n.getTranslation("role.confirmDelete"))) return;
method:"DELETE", console.log(role);
headers:this.headers fetch(this.info.api + "/guilds/" + this.guild.id + "/roles/" + role.id, {
}) method: "DELETE",
},null); headers: this.headers,
});
},
null,
);
return menu; return menu;
} }
redoButtons(){ redoButtons() {
this.buttons=[]; this.buttons = [];
this.permissions.sort(([a],[b])=>b.position-a.position); this.permissions.sort(([a], [b]) => b.position - a.position);
for(const i of this.permissions){ for (const i of this.permissions) {
this.buttons.push([i[0].name, i[0].id]); this.buttons.push([i[0].name, i[0].id]);
} }
console.log("in here :P")
if(!this.buttonList)return;
console.log("in here :P"); console.log("in here :P");
const elms=Array.from(this.buttonList.children); if (!this.buttonList) return;
const div=elms[0] as HTMLDivElement; console.log("in here :P");
const div2=elms[1] as HTMLDivElement; const elms = Array.from(this.buttonList.children);
const div = elms[0] as HTMLDivElement;
const div2 = elms[1] as HTMLDivElement;
console.log(div); console.log(div);
div.innerHTML=""; div.innerHTML = "";
div.append(this.buttonListGen(div2));//not actually sure why the html is needed div.append(this.buttonListGen(div2)); //not actually sure why the html is needed
} }
buttonMap=new WeakMap<HTMLButtonElement,Role>(); buttonMap = new WeakMap<HTMLButtonElement, Role>();
dragged?:HTMLButtonElement; dragged?: HTMLButtonElement;
buttonDragEvents(button:HTMLButtonElement,role:Role){ buttonDragEvents(button: HTMLButtonElement, role: Role) {
this.buttonMap.set(button,role); this.buttonMap.set(button, role);
button.addEventListener("dragstart", e=>{ button.addEventListener("dragstart", (e) => {
this.dragged = button; this.dragged = button;
e.stopImmediatePropagation(); e.stopImmediatePropagation();
}); });
button.addEventListener("dragend", ()=>{ button.addEventListener("dragend", () => {
this.dragged = undefined; this.dragged = undefined;
}); });
button.addEventListener("dragenter", event=>{ button.addEventListener("dragenter", (event) => {
console.log("enter"); console.log("enter");
event.preventDefault(); event.preventDefault();
return true; return true;
}); });
button.addEventListener("dragover", event=>{ button.addEventListener("dragover", (event) => {
event.preventDefault(); event.preventDefault();
return true; return true;
}); });
button.addEventListener("drop", _=>{ button.addEventListener("drop", (_) => {
const role2=this.buttonMap.get(this.dragged as HTMLButtonElement); const role2 = this.buttonMap.get(this.dragged as HTMLButtonElement);
if(!role2) return; if (!role2) return;
const index2=this.guild.roles.indexOf(role2); const index2 = this.guild.roles.indexOf(role2);
this.guild.roles.splice(index2,1); this.guild.roles.splice(index2, 1);
const index=this.guild.roles.indexOf(role); const index = this.guild.roles.indexOf(role);
this.guild.roles.splice(index+1,0,role2); this.guild.roles.splice(index + 1, 0, role2);
this.guild.recalcRoles(); this.guild.recalcRoles();
console.log(role); console.log(role);
}); });
} }
buttonListGen(html:HTMLElement){ buttonListGen(html: HTMLElement) {
const buttonTable=document.createElement("div"); const buttonTable = document.createElement("div");
buttonTable.classList.add("flexttb"); buttonTable.classList.add("flexttb");
const roleRow=document.createElement("div"); const roleRow = document.createElement("div");
roleRow.classList.add("flexltr","rolesheader"); roleRow.classList.add("flexltr", "rolesheader");
roleRow.append("Roles"); roleRow.append("Roles");
const add=document.createElement("span"); const add = document.createElement("span");
add.classList.add("svg-plus","svgicon","addrole"); add.classList.add("svg-plus", "svgicon", "addrole");
add.onclick=async (e)=>{ add.onclick = async (e) => {
const box=add.getBoundingClientRect(); const box = add.getBoundingClientRect();
e.stopPropagation(); e.stopPropagation();
if(this.channel){ if (this.channel) {
const roles:[Role,string[]][]=[]; const roles: [Role, string[]][] = [];
for(const role of this.guild.roles){ for (const role of this.guild.roles) {
if(this.permissions.find(r=>r[0]==role)){ if (this.permissions.find((r) => r[0] == role)) {
continue; continue;
} }
roles.push([role,[role.name]]); roles.push([role, [role.name]]);
} }
const search=new Search(roles); const search = new Search(roles);
const found=await search.find(box.left,box.top); const found = await search.find(box.left, box.top);
if (!found) return;
if(!found) return;
console.log(found); console.log(found);
this.onchange(found.id,new Permissions("0","0")); this.onchange(found.id, new Permissions("0", "0"));
}else{ } else {
const div=document.createElement("div"); const div = document.createElement("div");
const bar=document.createElement("input"); const bar = document.createElement("input");
div.classList.add("fixedsearch","OptionList"); div.classList.add("fixedsearch", "OptionList");
bar.type="text"; bar.type = "text";
div.style.left=(box.left^0)+"px"; div.style.left = (box.left ^ 0) + "px";
div.style.top=(box.top^0)+"px"; div.style.top = (box.top ^ 0) + "px";
div.append(bar) div.append(bar);
document.body.append(div); document.body.append(div);
if(Contextmenu.currentmenu != ""){ if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
Contextmenu.currentmenu=div; Contextmenu.currentmenu = div;
Contextmenu.keepOnScreen(div); Contextmenu.keepOnScreen(div);
bar.onchange=()=>{ bar.onchange = () => {
div.remove(); div.remove();
console.log(bar.value) console.log(bar.value);
if(bar.value==="") return; if (bar.value === "") return;
fetch(this.info.api+`/guilds/${this.guild.id}/roles`,{ fetch(this.info.api + `/guilds/${this.guild.id}/roles`, {
method:"POST", method: "POST",
headers:this.headers, headers: this.headers,
body:JSON.stringify({ body: JSON.stringify({
color:0, color: 0,
name:bar.value, name: bar.value,
permissions:"" permissions: "",
}) }),
}) });
} };
} }
} };
roleRow.append(add); roleRow.append(add);
buttonTable.append(roleRow); buttonTable.append(roleRow);
for(const thing of this.buttons){ for (const thing of this.buttons) {
const button = document.createElement("button"); const button = document.createElement("button");
button.classList.add("SettingsButton"); button.classList.add("SettingsButton");
button.textContent = thing[0]; button.textContent = thing[0];
const role=this.guild.roleids.get(thing[1]); const role = this.guild.roleids.get(thing[1]);
if(role){ if (role) {
if(!this.channel){ if (!this.channel) {
if(role.canManage()){ if (role.canManage()) {
this.buttonDragEvents(button,role); this.buttonDragEvents(button, role);
button.draggable=true; button.draggable = true;
RoleList.guildrolemenu.bindContextmenu(button,this,role) RoleList.guildrolemenu.bindContextmenu(button, this, role);
} }
}else{ } else {
if(role.canManage()){ if (role.canManage()) {
RoleList.channelrolemenu.bindContextmenu(button,this,role) RoleList.channelrolemenu.bindContextmenu(button, this, role);
} }
} }
} }
button.onclick = _=>{ button.onclick = (_) => {
this.generateHTMLArea(thing[1], html); this.generateHTMLArea(thing[1], html);
if(this.warndiv){ if (this.warndiv) {
this.warndiv.remove(); this.warndiv.remove();
} }
}; };
@ -400,29 +406,29 @@ class RoleList extends Buttons{
return buttonTable; return buttonTable;
} }
generateButtons(html:HTMLElement):HTMLDivElement{ generateButtons(html: HTMLElement): HTMLDivElement {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("settingbuttons"); div.classList.add("settingbuttons");
div.append(this.buttonListGen(html)); div.append(this.buttonListGen(html));
return div; return div;
} }
handleString(str: string): HTMLElement{ handleString(str: string): HTMLElement {
this.curid = str; this.curid = str;
const arr = this.permissions.find(_=>_[0].id === str); const arr = this.permissions.find((_) => _[0].id === str);
if(arr){ if (arr) {
const perm = arr[1]; const perm = arr[1];
this.permission.deny = perm.deny; this.permission.deny = perm.deny;
this.permission.allow = perm.allow; this.permission.allow = perm.allow;
const role = this.permissions.find(e=>e[0].id === str); const role = this.permissions.find((e) => e[0].id === str);
if(role){ if (role) {
this.options.name = role[0].name; this.options.name = role[0].name;
this.options.haschanged = false; this.options.haschanged = false;
} }
} }
return this.options.generateHTML(); return this.options.generateHTML();
} }
save(){ save() {
this.onchange(this.curid, this.permission); this.onchange(this.curid, this.permission);
} }
} }
export{ RoleList, PermissionToggle }; export {RoleList, PermissionToggle};

View file

@ -1,72 +1,70 @@
import { Contextmenu } from "./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
class Search<E>{ class Search<E> {
options:Map<string,E>; options: Map<string, E>;
readonly keys:string[]; readonly keys: string[];
constructor(options:[E,string[]][]){ constructor(options: [E, string[]][]) {
const map=options.flatMap(e=>{ const map = options.flatMap((e) => {
const val=e[1].map(f=>[f,e[0]]); const val = e[1].map((f) => [f, e[0]]);
return val as [string,E][]; return val as [string, E][];
}) });
this.options=new Map(map); this.options = new Map(map);
this.keys=[...this.options.keys()]; this.keys = [...this.options.keys()];
} }
generateList(str:string,max:number,res:(e:E)=>void){ generateList(str: string, max: number, res: (e: E) => void) {
str=str.toLowerCase(); str = str.toLowerCase();
const options=this.keys.filter(e=>{ const options = this.keys.filter((e) => {
return e.toLowerCase().includes(str) return e.toLowerCase().includes(str);
}); });
const div=document.createElement("div"); const div = document.createElement("div");
div.classList.add("OptionList","flexttb"); div.classList.add("OptionList", "flexttb");
for(const option of options.slice(0, max)){ for (const option of options.slice(0, max)) {
const hoption=document.createElement("span"); const hoption = document.createElement("span");
hoption.textContent=option; hoption.textContent = option;
hoption.onclick=()=>{ hoption.onclick = () => {
if(!this.options.has(option)) return; if (!this.options.has(option)) return;
res(this.options.get(option) as E) res(this.options.get(option) as E);
} };
div.append(hoption); div.append(hoption);
} }
return div; return div;
} }
async find(x:number,y:number,max=4):Promise<E|undefined>{ async find(x: number, y: number, max = 4): Promise<E | undefined> {
return new Promise<E|undefined>((res)=>{ return new Promise<E | undefined>((res) => {
const container = document.createElement("div");
container.classList.add("fixedsearch");
console.log((x ^ 0) + "", (y ^ 0) + "");
container.style.left = (x ^ 0) + "px";
container.style.top = (y ^ 0) + "px";
const remove = container.remove;
container.remove = () => {
remove.call(container);
res(undefined);
};
const container=document.createElement("div"); function resolve(e: E) {
container.classList.add("fixedsearch"); res(e);
console.log((x^0)+"",(y^0)+""); container.remove();
container.style.left=(x^0)+"px"; }
container.style.top=(y^0)+"px"; const bar = document.createElement("input");
const remove=container.remove; const options = document.createElement("div");
container.remove=()=>{ const keydown = () => {
remove.call(container); const html = this.generateList(bar.value, max, resolve);
res(undefined); options.innerHTML = "";
} options.append(html);
};
function resolve(e:E){ bar.oninput = keydown;
res(e); keydown();
container.remove(); bar.type = "text";
} container.append(bar);
const bar=document.createElement("input"); container.append(options);
const options=document.createElement("div"); document.body.append(container);
const keydown=()=>{ if (Contextmenu.currentmenu != "") {
const html=this.generateList(bar.value,max,resolve); Contextmenu.currentmenu.remove();
options.innerHTML=""; }
options.append(html); Contextmenu.currentmenu = container;
} Contextmenu.keepOnScreen(container);
bar.oninput=keydown; });
keydown(); }
bar.type="text";
container.append(bar);
container.append(options);
document.body.append(container);
if(Contextmenu.currentmenu != ""){
Contextmenu.currentmenu.remove();
}
Contextmenu.currentmenu=container;
Contextmenu.keepOnScreen(container);
})
}
} }
export {Search}; export {Search};

View file

@ -1,42 +1,45 @@
function deleteoldcache(){ function deleteoldcache() {
caches.delete("cache"); caches.delete("cache");
console.log("this ran :P"); console.log("this ran :P");
} }
async function putInCache(request: URL | RequestInfo, response: Response){ async function putInCache(request: URL | RequestInfo, response: Response) {
console.log(request, response); console.log(request, response);
const cache = await caches.open("cache"); const cache = await caches.open("cache");
console.log("Grabbed"); console.log("Grabbed");
try{ try {
console.log(await cache.put(request, response)); console.log(await cache.put(request, response));
}catch(error){ } catch (error) {
console.error(error); console.error(error);
} }
} }
let lastcache: string; let lastcache: string;
self.addEventListener("activate", async ()=>{ self.addEventListener("activate", async () => {
console.log("Service Worker activated"); console.log("Service Worker activated");
checkCache(); checkCache();
}); });
async function checkCache(){ async function checkCache() {
if(checkedrecently){ if (checkedrecently) {
return; return;
} }
const promise = await caches.match("/getupdates"); const promise = await caches.match("/getupdates");
if(promise){ if (promise) {
lastcache = await promise.text(); lastcache = await promise.text();
} }
console.log(lastcache); console.log(lastcache);
fetch("/getupdates").then(async data=>{ fetch("/getupdates").then(async (data) => {
setTimeout((_: any)=>{ setTimeout(
checkedrecently = false; (_: any) => {
}, 1000 * 60 * 30); checkedrecently = false;
if(!data.ok) return; },
1000 * 60 * 30,
);
if (!data.ok) return;
const text = await data.clone().text(); const text = await data.clone().text();
console.log(text, lastcache); console.log(text, lastcache);
if(lastcache !== text){ if (lastcache !== text) {
deleteoldcache(); deleteoldcache();
putInCache("/getupdates", data); putInCache("/getupdates", data);
} }
@ -45,98 +48,100 @@ async function checkCache(){
} }
var checkedrecently = false; var checkedrecently = false;
function samedomain(url: string | URL){ function samedomain(url: string | URL) {
return new URL(url).origin === self.origin; return new URL(url).origin === self.origin;
} }
const htmlFiles=new Set(["/index","/login","/home","/register","/oauth2/auth"]); const htmlFiles = new Set(["/index", "/login", "/home", "/register", "/oauth2/auth"]);
function isHtml(url: string): string | void {
function isHtml(url:string):string|void{ const path = new URL(url).pathname;
const path=new URL(url).pathname; if (htmlFiles.has(path) || htmlFiles.has(path + ".html")) {
if(htmlFiles.has(path)||htmlFiles.has(path+".html")){ return path + path.endsWith(".html") ? "" : ".html";
return path+path.endsWith(".html")?"":".html";
} }
} }
let enabled="false"; let enabled = "false";
let offline=false; let offline = false;
function toPath(url:string):string{ function toPath(url: string): string {
const Url= new URL(url); const Url = new URL(url);
let html=isHtml(url); let html = isHtml(url);
if(!html){ if (!html) {
const path=Url.pathname; const path = Url.pathname;
if(path.startsWith("/channels")){ if (path.startsWith("/channels")) {
html="./index.html" html = "./index.html";
}else if(path.startsWith("/invite/")||path==="/invite"){ } else if (path.startsWith("/invite/") || path === "/invite") {
html="./invite.html" html = "./invite.html";
} }
} }
return html||Url.pathname; return html || Url.pathname;
} }
let fails=0; let fails = 0;
async function getfile(event: FetchEvent):Promise<Response>{ async function getfile(event: FetchEvent): Promise<Response> {
checkCache(); checkCache();
if(!samedomain(event.request.url)||enabled==="false"||(enabled==="offlineOnly"&&!offline)){ if (
const responce=await fetch(event.request.clone()); !samedomain(event.request.url) ||
if(samedomain(event.request.url)){ enabled === "false" ||
if(enabled==="offlineOnly"&&responce.ok){ (enabled === "offlineOnly" && !offline)
putInCache(toPath(event.request.url),responce.clone()); ) {
const responce = await fetch(event.request.clone());
if (samedomain(event.request.url)) {
if (enabled === "offlineOnly" && responce.ok) {
putInCache(toPath(event.request.url), responce.clone());
} }
if(!responce.ok){ if (!responce.ok) {
fails++; fails++;
if(fails>5){ if (fails > 5) {
offline=true; offline = true;
} }
} }
} }
return responce; return responce;
} }
let path=toPath(event.request.url); let path = toPath(event.request.url);
if(path === "/instances.json"){ if (path === "/instances.json") {
return await fetch(path); return await fetch(path);
} }
console.log("Getting path: "+path); console.log("Getting path: " + path);
const responseFromCache = await caches.match(path); const responseFromCache = await caches.match(path);
if(responseFromCache){ if (responseFromCache) {
console.log("cache hit"); console.log("cache hit");
return responseFromCache; return responseFromCache;
} }
try{ try {
const responseFromNetwork = await fetch(path); const responseFromNetwork = await fetch(path);
if(responseFromNetwork.ok){ if (responseFromNetwork.ok) {
await putInCache(path, responseFromNetwork.clone()); await putInCache(path, responseFromNetwork.clone());
} }
return responseFromNetwork; return responseFromNetwork;
}catch(e){ } catch (e) {
console.error(e); console.error(e);
return new Response(null); return new Response(null);
} }
} }
self.addEventListener("fetch", (e) => {
self.addEventListener("fetch", (e)=>{ const event = e as FetchEvent;
const event=e as FetchEvent; try {
try{
event.respondWith(getfile(event)); event.respondWith(getfile(event));
}catch(e){ } catch (e) {
console.error(e); console.error(e);
} }
}); });
self.addEventListener("message", (message)=>{ self.addEventListener("message", (message) => {
const data=message.data; const data = message.data;
switch(data.code){ switch (data.code) {
case "setMode": case "setMode":
enabled=data.data; enabled = data.data;
break; break;
case "CheckUpdate": case "CheckUpdate":
checkedrecently=false; checkedrecently = false;
checkCache(); checkCache();
break; break;
case "ForceClear": case "ForceClear":
deleteoldcache(); deleteoldcache();
break; break;
} }
}) });

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,17 @@
abstract class SnowFlake{ abstract class SnowFlake {
public readonly id: string; public readonly id: string;
constructor(id: string){ constructor(id: string) {
this.id = id; this.id = id;
} }
getUnixTime(): number{ getUnixTime(): number {
return SnowFlake.stringToUnixTime(this.id); return SnowFlake.stringToUnixTime(this.id);
} }
static stringToUnixTime(str: string){ static stringToUnixTime(str: string) {
try{ try {
return Number((BigInt(str) >> 22n) + 1420070400000n); return Number((BigInt(str) >> 22n) + 1420070400000n);
}catch{ } catch {
throw new Error(`The ID is corrupted, it's ${str} when it should be some number.`); throw new Error(`The ID is corrupted, it's ${str} when it should be some number.`);
} }
} }
} }
export{ SnowFlake }; export {SnowFlake};

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,16 @@
import{ Member }from"./member.js"; import {Member} from "./member.js";
import{ MarkDown }from"./markdown.js"; import {MarkDown} from "./markdown.js";
import{ Contextmenu }from"./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
import{ Localuser }from"./localuser.js"; import {Localuser} from "./localuser.js";
import{ Guild }from"./guild.js"; import {Guild} from "./guild.js";
import{ SnowFlake }from"./snowflake.js"; import {SnowFlake} from "./snowflake.js";
import{ presencejson, userjson }from"./jsontypes.js"; import {presencejson, userjson} from "./jsontypes.js";
import { Role } from "./role.js"; import {Role} from "./role.js";
import { Search } from "./search.js"; import {Search} from "./search.js";
import { I18n } from "./i18n.js"; import {I18n} from "./i18n.js";
import { Direct } from "./direct.js"; import {Direct} from "./direct.js";
class User extends SnowFlake{ class User extends SnowFlake {
owner: Localuser; owner: Localuser;
hypotheticalpfp!: boolean; hypotheticalpfp!: boolean;
avatar!: string | null; avatar!: string | null;
@ -29,33 +29,32 @@ class User extends SnowFlake{
premium_type!: number; premium_type!: number;
theme_colors!: string; theme_colors!: string;
badge_ids!: string[]; badge_ids!: string[];
members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> = members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> = new WeakMap();
new WeakMap();
status!: string; status!: string;
resolving: false | Promise<any> = false; resolving: false | Promise<any> = false;
constructor(userjson: userjson, owner: Localuser, dontclone = false){ constructor(userjson: userjson, owner: Localuser, dontclone = false) {
super(userjson.id); super(userjson.id);
this.owner = owner; this.owner = owner;
if(localStorage.getItem("logbad")&&owner.user&&owner.user.id!==userjson.id){ if (localStorage.getItem("logbad") && owner.user && owner.user.id !== userjson.id) {
this.checkfortmi(userjson) this.checkfortmi(userjson);
} }
if(!owner){ if (!owner) {
console.error("missing localuser"); console.error("missing localuser");
} }
if(dontclone){ if (dontclone) {
for(const key of Object.keys(userjson)){ for (const key of Object.keys(userjson)) {
if(key === "bio"){ if (key === "bio") {
this.bio = new MarkDown(userjson[key], this.localuser); this.bio = new MarkDown(userjson[key], this.localuser);
continue; continue;
} }
if(key === "id"){ if (key === "id") {
continue; continue;
} }
(this as any)[key] = (userjson as any)[key]; (this as any)[key] = (userjson as any)[key];
} }
this.hypotheticalpfp = false; this.hypotheticalpfp = false;
}else{ } else {
return User.checkuser(userjson, owner); return User.checkuser(userjson, owner);
} }
} }
@ -64,84 +63,90 @@ class User extends SnowFlake{
* *
* *
*/ */
checkfortmi(json:any){ checkfortmi(json: any) {
if(json.data){ if (json.data) {
console.error("Server sent *way* too much info, this is really bad, it sent data") console.error("Server sent *way* too much info, this is really bad, it sent data");
} }
const bad=new Set(["fingerprints", "extended_settings", "mfa_enabled", "nsfw_allowed", "premium_usage_flags", "totp_last_ticket", "totp_secret", "webauthn_enabled"]); const bad = new Set([
for(const thing of bad){ "fingerprints",
if(json.hasOwnProperty(thing)){ "extended_settings",
console.error(thing+" should not be exposed to the client"); "mfa_enabled",
"nsfw_allowed",
"premium_usage_flags",
"totp_last_ticket",
"totp_secret",
"webauthn_enabled",
]);
for (const thing of bad) {
if (json.hasOwnProperty(thing)) {
console.error(thing + " should not be exposed to the client");
} }
} }
} }
tojson():userjson{ tojson(): userjson {
return { return {
username: this.username, username: this.username,
id: this.id, id: this.id,
public_flags: this.public_flags, public_flags: this.public_flags,
discriminator: this.discriminator, discriminator: this.discriminator,
avatar: this.avatar, avatar: this.avatar,
accent_color: this.accent_color, accent_color: this.accent_color,
banner: this.banner, banner: this.banner,
bio: this.bio.rawString, bio: this.bio.rawString,
premium_since: this.premium_since, premium_since: this.premium_since,
premium_type: this.premium_type, premium_type: this.premium_type,
bot: this.bot, bot: this.bot,
theme_colors: this.theme_colors, theme_colors: this.theme_colors,
pronouns: this.pronouns, pronouns: this.pronouns,
badge_ids: this.badge_ids, badge_ids: this.badge_ids,
} };
} }
clone(): User{ clone(): User {
const json=this.tojson(); const json = this.tojson();
json.id+="#clone"; json.id += "#clone";
return new User( return new User(json, this.owner);
json,
this.owner
);
} }
public getPresence(presence: presencejson | undefined): void{ public getPresence(presence: presencejson | undefined): void {
if(presence){ if (presence) {
this.setstatus(presence.status); this.setstatus(presence.status);
}else{ } else {
this.setstatus("offline"); this.setstatus("offline");
} }
} }
get online(){ get online() {
return (this.status)&&(this.status!="offline"); return this.status && this.status != "offline";
} }
setstatus(status: string): void{ setstatus(status: string): void {
this.status = status; this.status = status;
} }
getStatus(): string{ getStatus(): string {
return this.status || "offline"; return this.status || "offline";
} }
static contextmenu = new Contextmenu<User, Member | undefined>("User Menu"); static contextmenu = new Contextmenu<User, Member | undefined>("User Menu");
async opendm(){ async opendm() {
for(const dm of (this.localuser.guildids.get("@me") as Direct).channels){ for (const dm of (this.localuser.guildids.get("@me") as Direct).channels) {
if(dm.user.id===this.id){ if (dm.user.id === this.id) {
this.localuser.goToChannel(dm.id); this.localuser.goToChannel(dm.id);
return; return;
} }
} }
await fetch(this.info.api + "/users/@me/channels", { await fetch(this.info.api + "/users/@me/channels", {
method: "POST", method: "POST",
body: JSON.stringify({ recipients: [this.id] }), body: JSON.stringify({recipients: [this.id]}),
headers: this.localuser.headers, headers: this.localuser.headers,
}) })
.then(res=>res.json()) .then((res) => res.json())
.then(json=>{ .then((json) => {
this.localuser.goToChannel(json.id); this.localuser.goToChannel(json.id);
}); });
return; return;
} }
async changeRelationship(type:0|1|2|3|4|5){ async changeRelationship(type: 0 | 1 | 2 | 3 | 4 | 5) {
if(type!==0){ if (type !== 0) {
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT", method: "PUT",
headers: this.owner.headers, headers: this.owner.headers,
@ -149,197 +154,213 @@ class User extends SnowFlake{
type, type,
}), }),
}); });
}else{ } else {
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "DELETE", method: "DELETE",
headers: this.owner.headers headers: this.owner.headers,
}); });
} }
this.relationshipType=type; this.relationshipType = type;
} }
static setUpContextMenu(): void{ static setUpContextMenu(): void {
this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(this: User){
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton(()=>I18n.getTranslation("user.message"), function(this: User){
this.opendm();
});
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.block"), () => I18n.getTranslation("user.copyId"),
function(this: User){ function (this: User) {
navigator.clipboard.writeText(this.id);
},
);
this.contextmenu.addbutton(
() => I18n.getTranslation("user.message"),
function (this: User) {
this.opendm();
},
);
this.contextmenu.addbutton(
() => I18n.getTranslation("user.block"),
function (this: User) {
this.block(); this.block();
}, },
null, null,
function(){ function () {
return this.relationshipType !== 2; return this.relationshipType !== 2;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.unblock"), () => I18n.getTranslation("user.unblock"),
function(this: User){ function (this: User) {
this.unblock(); this.unblock();
}, },
null, null,
function(){ function () {
return this.relationshipType === 2; return this.relationshipType === 2;
} },
); );
this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){
this.changeRelationship(1);
},null,function(){
return this.relationshipType===0||this.relationshipType===3;
});
this.contextmenu.addbutton(()=>I18n.getTranslation("friends.removeFriend"), function(this: User){
this.changeRelationship(0);
},null,function(){
return this.relationshipType===1;
});
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.kick"), () => I18n.getTranslation("user.friendReq"),
function(this: User, member: Member | undefined){ function (this: User) {
this.changeRelationship(1);
},
null,
function () {
return this.relationshipType === 0 || this.relationshipType === 3;
},
);
this.contextmenu.addbutton(
() => I18n.getTranslation("friends.removeFriend"),
function (this: User) {
this.changeRelationship(0);
},
null,
function () {
return this.relationshipType === 1;
},
);
this.contextmenu.addbutton(
() => I18n.getTranslation("user.kick"),
function (this: User, member: Member | undefined) {
member?.kick(); member?.kick();
}, },
null, null,
member=>{ (member) => {
if(!member)return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
if(member.id === us.id){ if (member.id === us.id) {
return false; return false;
} }
if(member.id === member.guild.properties.owner_id){ if (member.id === member.guild.properties.owner_id) {
return false; return false;
} }
return us.hasPermission("KICK_MEMBERS") || false; return us.hasPermission("KICK_MEMBERS") || false;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.editServerProfile"), () => I18n.getTranslation("user.editServerProfile"),
function(this: User, member: Member | undefined){ function (this: User, member: Member | undefined) {
if(!member) return; if (!member) return;
member.showEditProfile(); member.showEditProfile();
}, },
null, null,
function(member){ function (member) {
return member?.id===this.localuser.user.id; return member?.id === this.localuser.user.id;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.ban"), () => I18n.getTranslation("user.ban"),
function(this: User, member: Member | undefined){ function (this: User, member: Member | undefined) {
member?.ban(); member?.ban();
}, },
null, null,
member=>{ (member) => {
if(!member)return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
if(member.id === us.id){ if (member.id === us.id) {
return false; return false;
} }
if(member.id === member.guild.properties.owner_id){ if (member.id === member.guild.properties.owner_id) {
return false; return false;
} }
return us.hasPermission("BAN_MEMBERS") || false; return us.hasPermission("BAN_MEMBERS") || false;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.addRole"), () => I18n.getTranslation("user.addRole"),
async function(this: User, member: Member | undefined,e){ async function (this: User, member: Member | undefined, e) {
if(member){ if (member) {
e.stopPropagation(); e.stopPropagation();
const roles:[Role,string[]][]=[]; const roles: [Role, string[]][] = [];
for(const role of member.guild.roles){ for (const role of member.guild.roles) {
if(!role.canManage()||member.roles.indexOf(role)!==-1){ if (!role.canManage() || member.roles.indexOf(role) !== -1) {
continue; continue;
} }
roles.push([role,[role.name]]); roles.push([role, [role.name]]);
} }
const search=new Search(roles); const search = new Search(roles);
const result=await search.find(e.x,e.y); const result = await search.find(e.x, e.y);
if(!result) return; if (!result) return;
member.addRole(result); member.addRole(result);
} }
}, },
null, null,
member=>{ (member) => {
if(!member)return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
console.log(us.hasPermission("MANAGE_ROLES")) console.log(us.hasPermission("MANAGE_ROLES"));
return us.hasPermission("MANAGE_ROLES") || false; return us.hasPermission("MANAGE_ROLES") || false;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
()=>I18n.getTranslation("user.removeRole"), () => I18n.getTranslation("user.removeRole"),
async function(this: User, member: Member | undefined,e){ async function (this: User, member: Member | undefined, e) {
if(member){ if (member) {
e.stopPropagation(); e.stopPropagation();
const roles:[Role,string[]][]=[]; const roles: [Role, string[]][] = [];
for(const role of member.roles){ for (const role of member.roles) {
if(!role.canManage()){ if (!role.canManage()) {
continue; continue;
} }
roles.push([role,[role.name]]); roles.push([role, [role.name]]);
} }
const search=new Search(roles); const search = new Search(roles);
const result=await search.find(e.x,e.y); const result = await search.find(e.x, e.y);
if(!result) return; if (!result) return;
member.removeRole(result); member.removeRole(result);
} }
}, },
null, null,
member=>{ (member) => {
if(!member)return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
console.log(us.hasPermission("MANAGE_ROLES")) console.log(us.hasPermission("MANAGE_ROLES"));
return us.hasPermission("MANAGE_ROLES") || false; return us.hasPermission("MANAGE_ROLES") || false;
} },
); );
} }
static checkuser(user: User | userjson, owner: Localuser): User{ static checkuser(user: User | userjson, owner: Localuser): User {
if(owner.userMap.has(user.id)){ if (owner.userMap.has(user.id)) {
return owner.userMap.get(user.id) as User; return owner.userMap.get(user.id) as User;
}else{ } else {
const tempuser = new User(user as userjson, owner, true); const tempuser = new User(user as userjson, owner, true);
owner.userMap.set(user.id, tempuser); owner.userMap.set(user.id, tempuser);
return tempuser; return tempuser;
} }
} }
get info(){ get info() {
return this.owner.info; return this.owner.info;
} }
get localuser(){ get localuser() {
return this.owner; return this.owner;
} }
get name(){ get name() {
return this.username; return this.username;
} }
async resolvemember(guild: Guild): Promise<Member | undefined>{ async resolvemember(guild: Guild): Promise<Member | undefined> {
return await Member.resolveMember(this, guild); return await Member.resolveMember(this, guild);
} }
async getUserProfile(): Promise<any>{ async getUserProfile(): Promise<any> {
return await fetch( return await fetch(
`${this.info.api}/users/${this.id.replace( `${this.info.api}/users/${this.id.replace(
"#clone", "#clone",
"" "",
)}/profile?with_mutual_guilds=true&with_mutual_friends=true`, )}/profile?with_mutual_guilds=true&with_mutual_friends=true`,
{ {
headers: this.localuser.headers, headers: this.localuser.headers,
} },
).then(res=>res.json()); ).then((res) => res.json());
} }
async getBadge(id: string): Promise<any>{ async getBadge(id: string): Promise<any> {
if(this.localuser.badges.has(id)){ if (this.localuser.badges.has(id)) {
return this.localuser.badges.get(id); return this.localuser.badges.get(id);
}else{ } else {
if(this.resolving){ if (this.resolving) {
await this.resolving; await this.resolving;
return this.localuser.badges.get(id); return this.localuser.badges.get(id);
} }
@ -348,126 +369,121 @@ class User extends SnowFlake{
this.resolving = prom; this.resolving = prom;
const badges = prom.badges; const badges = prom.badges;
this.resolving = false; this.resolving = false;
for(const badge of badges){ for (const badge of badges) {
this.localuser.badges.set(badge.id, badge); this.localuser.badges.set(badge.id, badge);
} }
return this.localuser.badges.get(id); return this.localuser.badges.get(id);
} }
} }
buildpfp(guild:Guild|void|Member|null): HTMLImageElement{ buildpfp(guild: Guild | void | Member | null): HTMLImageElement {
const pfp = document.createElement("img"); const pfp = document.createElement("img");
pfp.loading = "lazy"; pfp.loading = "lazy";
pfp.src = this.getpfpsrc(); pfp.src = this.getpfpsrc();
pfp.classList.add("pfp"); pfp.classList.add("pfp");
pfp.classList.add("userid:" + this.id); pfp.classList.add("userid:" + this.id);
if(guild){ if (guild) {
(async()=>{ (async () => {
if(guild instanceof Guild){ if (guild instanceof Guild) {
const memb= await Member.resolveMember(this,guild) const memb = await Member.resolveMember(this, guild);
if(!memb) return; if (!memb) return;
pfp.src = memb.getpfpsrc(); pfp.src = memb.getpfpsrc();
}else{ } else {
pfp.src = guild.getpfpsrc(); pfp.src = guild.getpfpsrc();
} }
})(); })();
} }
return pfp; return pfp;
} }
async buildstatuspfp(guild:Guild|void|Member|null): Promise<HTMLDivElement>{ async buildstatuspfp(guild: Guild | void | Member | null): Promise<HTMLDivElement> {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("pfpDiv") div.classList.add("pfpDiv");
const pfp = this.buildpfp(guild); const pfp = this.buildpfp(guild);
div.append(pfp); div.append(pfp);
const status = document.createElement("div"); const status = document.createElement("div");
status.classList.add("statusDiv"); status.classList.add("statusDiv");
switch(await this.getStatus()){ switch (await this.getStatus()) {
case"offline": case "offline":
status.classList.add("offlinestatus"); status.classList.add("offlinestatus");
break; break;
case"online": case "online":
default: default:
status.classList.add("onlinestatus"); status.classList.add("onlinestatus");
break; break;
} }
div.append(status); div.append(status);
return div; return div;
} }
userupdate(json: userjson): void{ userupdate(json: userjson): void {
if(json.avatar !== this.avatar){ if (json.avatar !== this.avatar) {
this.changepfp(json.avatar); this.changepfp(json.avatar);
} }
} }
bind(html: HTMLElement, guild: Guild | null = null, error = true): void{ bind(html: HTMLElement, guild: Guild | null = null, error = true): void {
if(guild && guild.id !== "@me"){ if (guild && guild.id !== "@me") {
Member.resolveMember(this, guild) Member.resolveMember(this, guild)
.then(member=>{ .then((member) => {
User.contextmenu.bindContextmenu(html, this, member); User.contextmenu.bindContextmenu(html, this, member);
if(member === undefined && error){ if (member === undefined && error) {
const errorSpan = document.createElement("span"); const errorSpan = document.createElement("span");
errorSpan.textContent = "!"; errorSpan.textContent = "!";
errorSpan.classList.add("membererror"); errorSpan.classList.add("membererror");
html.after(errorSpan); html.after(errorSpan);
return; return;
} }
if(member){ if (member) {
member.bind(html); member.bind(html);
}else{ } else {
User.contextmenu.bindContextmenu(html, this, undefined); User.contextmenu.bindContextmenu(html, this, undefined);
} }
}) })
.catch(err=>{ .catch((err) => {
console.log(err); console.log(err);
}); });
}else{ } else {
User.contextmenu.bindContextmenu(html, this, undefined); User.contextmenu.bindContextmenu(html, this, undefined);
} }
if(guild){ if (guild) {
this.profileclick(html, guild); this.profileclick(html, guild);
}else{ } else {
this.profileclick(html); this.profileclick(html);
} }
} }
static async resolve(id: string, localuser: Localuser): Promise<User>{ static async resolve(id: string, localuser: Localuser): Promise<User> {
const json = await fetch( const json = await fetch(localuser.info.api.toString() + "/users/" + id + "/profile", {
localuser.info.api.toString() + "/users/" + id + "/profile", headers: localuser.headers,
{ headers: localuser.headers } }).then((res) => res.json());
).then(res=>res.json());
return new User(json.user, localuser); return new User(json.user, localuser);
} }
changepfp(update: string | null): void{ changepfp(update: string | null): void {
this.avatar = update; this.avatar = update;
this.hypotheticalpfp = false; this.hypotheticalpfp = false;
const src = this.getpfpsrc(); const src = this.getpfpsrc();
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((element) => {
element=>{ (element as HTMLImageElement).src = src;
(element as HTMLImageElement).src = src; });
}
);
} }
async block(){ async block() {
await this.changeRelationship(2); await this.changeRelationship(2);
const channel = this.localuser.channelfocus; const channel = this.localuser.channelfocus;
if(channel){ if (channel) {
for(const message of channel.messages){ for (const message of channel.messages) {
message[1].generateMessage(); message[1].generateMessage();
} }
} }
} }
async unblock(){ async unblock() {
await this.changeRelationship(0); await this.changeRelationship(0);
const channel = this.localuser.channelfocus; const channel = this.localuser.channelfocus;
if(channel){ if (channel) {
for(const message of channel.messages){ for (const message of channel.messages) {
message[1].generateMessage(); message[1].generateMessage();
} }
} }
@ -475,81 +491,79 @@ class User extends SnowFlake{
/** /**
* @param guild this is an optional thing that'll get the src of the member if it exists, otherwise ignores it, this is meant to be fast, not accurate * @param guild this is an optional thing that'll get the src of the member if it exists, otherwise ignores it, this is meant to be fast, not accurate
*/ */
getpfpsrc(guild:Guild|void): string{ getpfpsrc(guild: Guild | void): string {
if(this.hypotheticalpfp && this.avatar){ if (this.hypotheticalpfp && this.avatar) {
return this.avatar; return this.avatar;
} }
if(guild){ if (guild) {
const member=this.members.get(guild) const member = this.members.get(guild);
if(member instanceof Member){ if (member instanceof Member) {
return member.getpfpsrc(); return member.getpfpsrc();
} }
} }
if(this.avatar !== null){ if (this.avatar !== null) {
return`${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${this.avatar}.png`;
this.avatar } else {
}.png`;
}else{
const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n);
return`${this.info.cdn}/embed/avatars/${int}.png`; return `${this.info.cdn}/embed/avatars/${int}.png`;
} }
} }
async buildprofile( async buildprofile(
x: number, x: number,
y: number, y: number,
guild: Guild | null | Member = null guild: Guild | null | Member = null,
): Promise<HTMLDivElement>{ ): Promise<HTMLDivElement> {
if(Contextmenu.currentmenu != ""){ if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
const membres=(async ()=>{ const membres = (async () => {
if(!guild) return; if (!guild) return;
let member:Member|undefined; let member: Member | undefined;
if(guild instanceof Guild){ if (guild instanceof Guild) {
member=await Member.resolveMember(this,guild) member = await Member.resolveMember(this, guild);
}else{ } else {
member=guild; member = guild;
} }
return member; return member;
})() })();
const div = document.createElement("div"); const div = document.createElement("div");
if(this.accent_color){ if (this.accent_color) {
div.style.setProperty( div.style.setProperty(
"--accent_color", "--accent_color",
`#${this.accent_color.toString(16).padStart(6, "0")}` `#${this.accent_color.toString(16).padStart(6, "0")}`,
); );
}else{ } else {
div.style.setProperty("--accent_color", "transparent"); div.style.setProperty("--accent_color", "transparent");
} }
const banner=this.getBanner(guild); const banner = this.getBanner(guild);
div.append(banner); div.append(banner);
membres.then(member=>{ membres.then((member) => {
if(!member) return; if (!member) return;
if(member.accent_color&&member.accent_color!==0){ if (member.accent_color && member.accent_color !== 0) {
div.style.setProperty( div.style.setProperty(
"--accent_color", "--accent_color",
`#${member.accent_color.toString(16).padStart(6, "0")}` `#${member.accent_color.toString(16).padStart(6, "0")}`,
); );
} }
}) });
if(x !== -1){ if (x !== -1) {
div.style.left = `${x}px`; div.style.left = `${x}px`;
div.style.top = `${y}px`; div.style.top = `${y}px`;
div.classList.add("profile", "flexttb"); div.classList.add("profile", "flexttb");
}else{ } else {
this.setstatus("online"); this.setstatus("online");
div.classList.add("hypoprofile", "profile", "flexttb"); div.classList.add("hypoprofile", "profile", "flexttb");
} }
const badgediv = document.createElement("div"); const badgediv = document.createElement("div");
badgediv.classList.add("badges"); badgediv.classList.add("badges");
(async ()=>{ (async () => {
if(!this.badge_ids)return; if (!this.badge_ids) return;
for(const id of this.badge_ids){ for (const id of this.badge_ids) {
const badgejson = await this.getBadge(id); const badgejson = await this.getBadge(id);
if(badgejson){ if (badgejson) {
const badge = document.createElement(badgejson.link ? "a" : "div"); const badge = document.createElement(badgejson.link ? "a" : "div");
badge.classList.add("badge"); badge.classList.add("badge");
const img = document.createElement("img"); const img = document.createElement("img");
@ -558,7 +572,7 @@ class User extends SnowFlake{
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = badgejson.description; span.textContent = badgejson.description;
badge.append(span); badge.append(span);
if(badge instanceof HTMLAnchorElement){ if (badge instanceof HTMLAnchorElement) {
badge.href = badgejson.link; badge.href = badgejson.link;
} }
badgediv.append(badge); badgediv.append(badge);
@ -568,7 +582,7 @@ class User extends SnowFlake{
const pfp = await this.buildstatuspfp(guild); const pfp = await this.buildstatuspfp(guild);
div.appendChild(pfp); div.appendChild(pfp);
const userbody = document.createElement("div"); const userbody = document.createElement("div");
userbody.classList.add("flexttb","infosection"); userbody.classList.add("flexttb", "infosection");
div.appendChild(userbody); div.appendChild(userbody);
const usernamehtml = document.createElement("h2"); const usernamehtml = document.createElement("h2");
usernamehtml.textContent = this.username; usernamehtml.textContent = this.username;
@ -580,14 +594,14 @@ class User extends SnowFlake{
userbody.appendChild(discrimatorhtml); userbody.appendChild(discrimatorhtml);
const pronounshtml = document.createElement("p"); const pronounshtml = document.createElement("p");
pronounshtml.textContent = this.pronouns||""; pronounshtml.textContent = this.pronouns || "";
pronounshtml.classList.add("pronouns"); pronounshtml.classList.add("pronouns");
userbody.appendChild(pronounshtml); userbody.appendChild(pronounshtml);
membres.then(member=>{ membres.then((member) => {
if(!member) return; if (!member) return;
if(member.pronouns&&member.pronouns!==""){ if (member.pronouns && member.pronouns !== "") {
pronounshtml.textContent=member.pronouns; pronounshtml.textContent = member.pronouns;
} }
}); });
@ -596,31 +610,28 @@ class User extends SnowFlake{
const biohtml = this.bio.makeHTML(); const biohtml = this.bio.makeHTML();
userbody.appendChild(biohtml); userbody.appendChild(biohtml);
membres.then(member=>{ membres.then((member) => {
if(!member)return; if (!member) return;
if(member.bio&&member.bio!==""){ if (member.bio && member.bio !== "") {
//TODO make markdown take Guild //TODO make markdown take Guild
userbody.insertBefore(new MarkDown(member.bio,this.localuser).makeHTML(),biohtml); userbody.insertBefore(new MarkDown(member.bio, this.localuser).makeHTML(), biohtml);
biohtml.remove(); biohtml.remove();
} }
}); });
if(guild){ if (guild) {
membres.then(member=>{ membres.then((member) => {
if(!member)return; if (!member) return;
usernamehtml.textContent=member.name; usernamehtml.textContent = member.name;
const roles = document.createElement("div"); const roles = document.createElement("div");
roles.classList.add("flexltr","rolesbox"); roles.classList.add("flexltr", "rolesbox");
for(const role of member.roles){ for (const role of member.roles) {
if(role.id===member.guild.id) continue; if (role.id === member.guild.id) continue;
const roleDiv = document.createElement("div"); const roleDiv = document.createElement("div");
roleDiv.classList.add("rolediv"); roleDiv.classList.add("rolediv");
const color = document.createElement("div"); const color = document.createElement("div");
roleDiv.append(color); roleDiv.append(color);
color.style.setProperty( color.style.setProperty("--role-color", `#${role.color.toString(16).padStart(6, "0")}`);
"--role-color",
`#${role.color.toString(16).padStart(6, "0")}`
);
color.classList.add("colorrolediv"); color.classList.add("colorrolediv");
const span = document.createElement("span"); const span = document.createElement("span");
roleDiv.append(span); roleDiv.append(span);
@ -630,57 +641,55 @@ class User extends SnowFlake{
userbody.append(roles); userbody.append(roles);
}); });
} }
if(x !== -1){ if (x !== -1) {
Contextmenu.currentmenu = div; Contextmenu.currentmenu = div;
document.body.appendChild(div); document.body.appendChild(div);
Contextmenu.keepOnScreen(div); Contextmenu.keepOnScreen(div);
} }
return div; return div;
} }
getBanner(guild:Guild|null|Member):HTMLImageElement{ getBanner(guild: Guild | null | Member): HTMLImageElement {
const banner = document.createElement("img"); const banner = document.createElement("img");
const bsrc=this.getBannerUrl(); const bsrc = this.getBannerUrl();
if(bsrc){ if (bsrc) {
banner.src = bsrc; banner.src = bsrc;
banner.classList.add("banner"); banner.classList.add("banner");
} }
if(guild){ if (guild) {
if(guild instanceof Member){ if (guild instanceof Member) {
const bsrc=guild.getBannerUrl(); const bsrc = guild.getBannerUrl();
if(bsrc){ if (bsrc) {
banner.src = bsrc; banner.src = bsrc;
banner.classList.add("banner"); banner.classList.add("banner");
} }
}else{ } else {
Member.resolveMember(this,guild).then(memb=>{ Member.resolveMember(this, guild).then((memb) => {
if(!memb) return; if (!memb) return;
const bsrc=memb.getBannerUrl(); const bsrc = memb.getBannerUrl();
if(bsrc){ if (bsrc) {
banner.src = bsrc; banner.src = bsrc;
banner.classList.add("banner"); banner.classList.add("banner");
} }
}) });
} }
} }
return banner return banner;
} }
getBannerUrl():string|undefined{ getBannerUrl(): string | undefined {
if(this.banner){ if (this.banner) {
if(!this.hypotheticalbanner){ if (!this.hypotheticalbanner) {
return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${this.banner}.png`;
this.banner } else {
}.png`;
}else{
return this.banner; return this.banner;
} }
}else{ } else {
return undefined; return undefined;
} }
} }
profileclick(obj: HTMLElement, guild?: Guild): void{ profileclick(obj: HTMLElement, guild?: Guild): void {
obj.onclick = (e: MouseEvent)=>{ obj.onclick = (e: MouseEvent) => {
this.buildprofile(e.clientX, e.clientY, guild); this.buildprofile(e.clientX, e.clientY, guild);
e.stopPropagation(); e.stopPropagation();
}; };
@ -688,4 +697,4 @@ class User extends SnowFlake{
} }
User.setUpContextMenu(); User.setUpContextMenu();
export{ User }; export {User};

View file

@ -1,88 +1,88 @@
class BinRead{ class BinRead {
private i = 0; private i = 0;
private view:DataView; private view: DataView;
constructor(buffer:ArrayBuffer){ constructor(buffer: ArrayBuffer) {
this.view=new DataView(buffer, 0) this.view = new DataView(buffer, 0);
} }
read16(){ read16() {
const int = this.view.getUint16(this.i); const int = this.view.getUint16(this.i);
this.i += 2; this.i += 2;
return int; return int;
} }
read8(){ read8() {
const int = this.view.getUint8(this.i); const int = this.view.getUint8(this.i);
this.i += 1; this.i += 1;
return int; return int;
} }
readString8(){ readString8() {
return this.readStringNo(this.read8()); return this.readStringNo(this.read8());
} }
readString16(){ readString16() {
return this.readStringNo(this.read16()); return this.readStringNo(this.read16());
} }
readFloat32(){ readFloat32() {
const float = this.view.getFloat32(this.i); const float = this.view.getFloat32(this.i);
this.i += 4; this.i += 4;
return float; return float;
} }
readStringNo(length: number){ readStringNo(length: number) {
const array = new Uint8Array(length); const array = new Uint8Array(length);
for(let i = 0; i < length; i++){ for (let i = 0; i < length; i++) {
array[i] = this.read8(); array[i] = this.read8();
} }
//console.log(array); //console.log(array);
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer); return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
} }
} }
class BinWrite{ class BinWrite {
private view: DataView; private view: DataView;
private buffer:ArrayBuffer; private buffer: ArrayBuffer;
private i=0; private i = 0;
constructor(maxSize:number=2**26){ constructor(maxSize: number = 2 ** 26) {
this.buffer=new ArrayBuffer(maxSize); this.buffer = new ArrayBuffer(maxSize);
this.view=new DataView(this.buffer, 0); this.view = new DataView(this.buffer, 0);
} }
write32Float(numb:number){ write32Float(numb: number) {
this.view.setFloat32(this.i,numb); this.view.setFloat32(this.i, numb);
this.i+=4; this.i += 4;
} }
write16(numb:number){ write16(numb: number) {
this.view.setUint16(this.i,numb); this.view.setUint16(this.i, numb);
this.i+=2; this.i += 2;
} }
write8(numb:number){ write8(numb: number) {
this.view.setUint8(this.i,numb); this.view.setUint8(this.i, numb);
this.i+=1; this.i += 1;
} }
writeString8(str:string){ writeString8(str: string) {
const encode=new TextEncoder().encode(str); const encode = new TextEncoder().encode(str);
this.write8(encode.length); this.write8(encode.length);
for(const thing of encode){ for (const thing of encode) {
this.write8(thing); this.write8(thing);
} }
} }
writeString16(str:string){ writeString16(str: string) {
const encode=new TextEncoder().encode(str); const encode = new TextEncoder().encode(str);
this.write16(encode.length); this.write16(encode.length);
for(const thing of encode){ for (const thing of encode) {
this.write8(thing); this.write8(thing);
} }
} }
writeStringNo(str:string){ writeStringNo(str: string) {
const encode=new TextEncoder().encode(str); const encode = new TextEncoder().encode(str);
for(const thing of encode){ for (const thing of encode) {
this.write8(thing); this.write8(thing);
} }
} }
getBuffer(){ getBuffer() {
const buf=new ArrayBuffer(this.i); const buf = new ArrayBuffer(this.i);
const ar1=new Uint8Array(buf); const ar1 = new Uint8Array(buf);
const ar2=new Uint8Array(this.buffer); const ar2 = new Uint8Array(this.buffer);
for(let i in ar1){ for (let i in ar1) {
ar1[+i]=ar2[+i]; ar1[+i] = ar2[+i];
} }
return buf; return buf;
} }
} }
export {BinRead,BinWrite} export {BinRead, BinWrite};

View file

@ -1,276 +1,259 @@
import { I18n } from "../i18n.js"; import {I18n} from "../i18n.js";
setTheme(); setTheme();
export function setTheme() { export function setTheme() {
let name = localStorage.getItem("theme"); let name = localStorage.getItem("theme");
if (!name) { if (!name) {
localStorage.setItem("theme", "Dark"); localStorage.setItem("theme", "Dark");
name = "Dark"; name = "Dark";
} }
document.body.className = name + "-theme"; document.body.className = name + "-theme";
} }
export function getBulkUsers() { export function getBulkUsers() {
const json = getBulkInfo(); const json = getBulkInfo();
for (const thing in json.users) { for (const thing in json.users) {
json.users[thing] = new Specialuser(json.users[thing]); json.users[thing] = new Specialuser(json.users[thing]);
} }
return json; return json;
} }
export function getBulkInfo() { export function getBulkInfo() {
return JSON.parse(localStorage.getItem("userinfos") as string); return JSON.parse(localStorage.getItem("userinfos") as string);
} }
export function setDefaults() { export function setDefaults() {
let userinfos = getBulkInfo(); let userinfos = getBulkInfo();
if (!userinfos) { if (!userinfos) {
localStorage.setItem( localStorage.setItem(
"userinfos", "userinfos",
JSON.stringify({ JSON.stringify({
currentuser: null, currentuser: null,
users: {}, users: {},
preferences: { preferences: {
theme: "Dark", theme: "Dark",
notifications: false, notifications: false,
notisound: "three", notisound: "three",
}, },
}) }),
); );
userinfos = getBulkInfo(); userinfos = getBulkInfo();
} }
if (userinfos.users === undefined) { if (userinfos.users === undefined) {
userinfos.users = {}; userinfos.users = {};
} }
if (userinfos.accent_color === undefined) { if (userinfos.accent_color === undefined) {
userinfos.accent_color = "#3096f7"; userinfos.accent_color = "#3096f7";
} }
document.documentElement.style.setProperty( document.documentElement.style.setProperty("--accent-color", userinfos.accent_color);
"--accent-color", if (userinfos.preferences === undefined) {
userinfos.accent_color userinfos.preferences = {
); theme: "Dark",
if (userinfos.preferences === undefined) { notifications: false,
userinfos.preferences = { notisound: "three",
theme: "Dark", };
notifications: false, }
notisound: "three", if (userinfos.preferences && userinfos.preferences.notisound === undefined) {
}; console.warn("uhoh");
} userinfos.preferences.notisound = "three";
if (userinfos.preferences && userinfos.preferences.notisound === undefined) { }
console.warn("uhoh"); localStorage.setItem("userinfos", JSON.stringify(userinfos));
userinfos.preferences.notisound = "three";
}
localStorage.setItem("userinfos", JSON.stringify(userinfos));
} }
setDefaults(); setDefaults();
export class Specialuser { export class Specialuser {
serverurls: { 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");
}
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;
}
set id(e) {
this.json.id = e;
this.updateLocal();
}
get id() {
return this.json.id;
}
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));
}
}
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
export{
mobile,
iOS,
}
let instances:
| {
name: string;
description?: string;
descriptionLong?: string;
image?: string;
url?: string;
display?: boolean;
online?: boolean;
uptime: { alltime: number; daytime: number; weektime: number };
urls: {
wellknown: string;
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
login?: string; wellknown: string;
login: string;
}; };
}[] email: string;
| null; 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;
}
set id(e) {
this.json.id = e;
this.updateLocal();
}
get id() {
return this.json.id;
}
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));
}
}
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
export {mobile, iOS};
let instances:
| {
name: string;
description?: string;
descriptionLong?: string;
image?: string;
url?: string;
display?: boolean;
online?: boolean;
uptime: {alltime: number; daytime: number; weektime: number};
urls: {
wellknown: string;
api: string;
cdn: string;
gateway: string;
login?: string;
};
}[]
| null;
const datalist = document.getElementById("instances"); const datalist = document.getElementById("instances");
console.warn(datalist); console.warn(datalist);
const instancefetch=fetch("/instances.json") const instancefetch = fetch("/instances.json")
.then(res=>res.json()) .then((res) => res.json())
.then( .then(
(json: { (
name: string; json: {
description?: string; name: string;
descriptionLong?: string; description?: string;
image?: string; descriptionLong?: string;
url?: string; image?: string;
display?: boolean; url?: string;
online?: boolean; display?: boolean;
uptime: { alltime: number; daytime: number; weektime: number }; online?: boolean;
urls: { uptime: {alltime: number; daytime: number; weektime: number};
wellknown: string; urls: {
api: string; wellknown: string;
cdn: string; api: string;
gateway: string; cdn: string;
login?: string; gateway: string;
} login?: string;
}[] };
)=>{ }[],
instances = json; ) => {
if(datalist){ instances = json;
console.warn(json); if (datalist) {
const instancein = document.getElementById("instancein") as HTMLInputElement; console.warn(json);
if(instancein && instancein.value === ""){ const instancein = document.getElementById("instancein") as HTMLInputElement;
instancein.value = json[0].name; if (instancein && instancein.value === "") {
} instancein.value = json[0].name;
for(const instance of json){ }
if(instance.display === false){ for (const instance of json) {
continue; if (instance.display === false) {
} continue;
const option = document.createElement("option"); }
option.disabled = !instance.online; const option = document.createElement("option");
option.value = instance.name; option.disabled = !instance.online;
if(instance.url){ option.value = instance.name;
stringURLMap.set(option.value, instance.url); if (instance.url) {
if(instance.urls){ stringURLMap.set(option.value, instance.url);
stringURLsMap.set(instance.url, instance.urls); if (instance.urls) {
} stringURLsMap.set(instance.url, instance.urls);
}else if(instance.urls){ }
stringURLsMap.set(option.value, instance.urls); } else if (instance.urls) {
}else{ stringURLsMap.set(option.value, instance.urls);
option.disabled = true; } else {
} option.disabled = true;
if(instance.description){ }
option.label = instance.description; if (instance.description) {
}else{ option.label = instance.description;
option.label = instance.name; } else {
} option.label = instance.name;
datalist.append(option); }
} datalist.append(option);
checkInstance(""); }
} checkInstance("");
} }
); },
);
const stringURLMap = new Map<string, string>(); const stringURLMap = new Map<string, string>();
const stringURLsMap = new Map< const stringURLsMap = new Map<
string, string,
{ {
wellknown: string; wellknown: string;
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
login?: string; login?: string;
} }
>(); >();
export async function getapiurls(str: string): Promise< export async function getapiurls(str: string): Promise<
{ | {
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
wellknown: string; wellknown: string;
login: string; login: string;
} }
| false | false
>{ > {
function appendApi(str:string){ function appendApi(str: string) {
return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api"); return str.includes("api") ? "" : str.endsWith("/") ? "api" : "/api";
} }
if(!URL.canParse(str)){ if (!URL.canParse(str)) {
const val = stringURLMap.get(str); const val = stringURLMap.get(str);
if(stringURLMap.size===0){ if (stringURLMap.size === 0) {
await new Promise<void>(res=>{ await new Promise<void>((res) => {
setInterval(()=>{ setInterval(() => {
if(stringURLMap.size!==0){ if (stringURLMap.size !== 0) {
res(); res();
} }
},100); }, 100);
}); });
} }
if(val){ if (val) {
str = val; str = val;
}else{ } else {
const val = stringURLsMap.get(str); const val = stringURLsMap.get(str);
if(val){ if (val) {
const responce = await fetch( const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
val.api + (val.api.endsWith("/") ? "" : "/") + "ping" if (responce.ok) {
); if (val.login) {
if(responce.ok){
if(val.login){
return val as { return val as {
wellknown: string; wellknown: string;
api: string; api: string;
@ -278,7 +261,7 @@ export async function getapiurls(str: string): Promise<
gateway: string; gateway: string;
login: string; login: string;
}; };
}else{ } else {
val.login = val.api; val.login = val.api;
return val as { return val as {
wellknown: string; wellknown: string;
@ -292,43 +275,38 @@ export async function getapiurls(str: string): Promise<
} }
} }
} }
if(str.at(-1) !== "/"){ if (str.at(-1) !== "/") {
str += "/"; str += "/";
} }
let api: string; let api: string;
try{ try {
const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json() const info = await fetch(`${str}.well-known/spacebar`).then((x) => x.json());
);
api = info.api; api = info.api;
}catch{ } catch {
api=str; api = str;
} }
if(!URL.canParse(api)){ if (!URL.canParse(api)) {
return false; return false;
} }
const url = new URL(api); const url = new URL(api);
try{ try {
const info = await fetch( const info = await fetch(
`${api}${ `${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
url.pathname.includes("api") ? "" : "api" ).then((x) => x.json());
}/policies/instance/domains`
).then(x=>x.json());
const apiurl = new URL(info.apiEndpoint); const apiurl = new URL(info.apiEndpoint);
return{ return {
api: info.apiEndpoint+appendApi(apiurl.pathname), api: info.apiEndpoint + appendApi(apiurl.pathname),
gateway: info.gateway, gateway: info.gateway,
cdn: info.cdn, cdn: info.cdn,
wellknown: str, wellknown: str,
login: info.apiEndpoint+appendApi(apiurl.pathname), login: info.apiEndpoint + appendApi(apiurl.pathname),
}; };
}catch{ } catch {
const val = stringURLsMap.get(str); const val = stringURLsMap.get(str);
if(val){ if (val) {
const responce = await fetch( const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
val.api + (val.api.endsWith("/") ? "" : "/") + "ping" if (responce.ok) {
); if (val.login) {
if(responce.ok){
if(val.login){
return val as { return val as {
wellknown: string; wellknown: string;
api: string; api: string;
@ -336,7 +314,7 @@ export async function getapiurls(str: string): Promise<
gateway: string; gateway: string;
login: string; login: string;
}; };
}else{ } else {
val.login = val.api; val.login = val.api;
return val as { return val as {
wellknown: string; wellknown: string;
@ -351,10 +329,10 @@ export async function getapiurls(str: string): Promise<
return false; return false;
} }
} }
export async function checkInstance(instance: string){ export async function checkInstance(instance: string) {
await instancefetch; await instancefetch;
const verify = document.getElementById("verify"); const verify = document.getElementById("verify");
try{ try {
verify!.textContent = I18n.getTranslation("login.checking"); verify!.textContent = I18n.getTranslation("login.checking");
const instanceValue = instance; const instanceValue = instance;
const instanceinfo = (await getapiurls(instanceValue)) as { const instanceinfo = (await getapiurls(instanceValue)) as {
@ -365,72 +343,74 @@ export async function checkInstance(instance: string){
login: string; login: string;
value: string; value: string;
}; };
if(instanceinfo){ if (instanceinfo) {
instanceinfo.value = instanceValue; instanceinfo.value = instanceValue;
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
verify!.textContent = I18n.getTranslation("login.allGood"); verify!.textContent = I18n.getTranslation("login.allGood");
// @ts-ignore // @ts-ignore
if(checkInstance.alt){ if (checkInstance.alt) {
// @ts-ignore // @ts-ignore
checkInstance.alt(); checkInstance.alt();
} }
setTimeout((_: any)=>{ setTimeout((_: any) => {
console.log(verify!.textContent); console.log(verify!.textContent);
verify!.textContent = ""; verify!.textContent = "";
}, 3000); }, 3000);
}else{ } else {
verify!.textContent = I18n.getTranslation("login.invalid"); verify!.textContent = I18n.getTranslation("login.invalid");
} }
}catch{ } catch {
console.log("catch"); console.log("catch");
verify!.textContent = I18n.getTranslation("login.invalid"); verify!.textContent = I18n.getTranslation("login.invalid");
} }
} }
export function getInstances(){ export function getInstances() {
return instances; return instances;
} }
export class SW{ export class SW {
static worker:undefined|ServiceWorker; static worker: undefined | ServiceWorker;
static setMode(mode:"false"|"offlineOnly"|"true"){ static setMode(mode: "false" | "offlineOnly" | "true") {
localStorage.setItem("SWMode",mode); localStorage.setItem("SWMode", mode);
if(this.worker){ if (this.worker) {
this.worker.postMessage({data:mode,code:"setMode"}); this.worker.postMessage({data: mode, code: "setMode"});
} }
} }
static checkUpdate(){ static checkUpdate() {
if(this.worker){ if (this.worker) {
this.worker.postMessage({code:"CheckUpdate"}); this.worker.postMessage({code: "CheckUpdate"});
} }
} }
static forceClear(){ static forceClear() {
if(this.worker){ if (this.worker) {
this.worker.postMessage({code:"ForceClear"}); this.worker.postMessage({code: "ForceClear"});
} }
} }
} }
if ("serviceWorker" in navigator){ if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service.js", { navigator.serviceWorker
scope: "/", .register("/service.js", {
}).then((registration) => { scope: "/",
let serviceWorker:ServiceWorker|undefined; })
if (registration.installing) { .then((registration) => {
serviceWorker = registration.installing; let serviceWorker: ServiceWorker | undefined;
console.log("installing"); if (registration.installing) {
} else if (registration.waiting) { serviceWorker = registration.installing;
serviceWorker = registration.waiting; console.log("installing");
console.log("waiting"); } else if (registration.waiting) {
} else if (registration.active) { serviceWorker = registration.waiting;
serviceWorker = registration.active; console.log("waiting");
console.log("active"); } else if (registration.active) {
} serviceWorker = registration.active;
SW.worker=serviceWorker; console.log("active");
SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true"); }
if (serviceWorker) { SW.worker = serviceWorker;
console.log(serviceWorker.state); SW.setMode(localStorage.getItem("SWMode") as "false" | "offlineOnly" | "true");
serviceWorker.addEventListener("statechange", (_) => { if (serviceWorker) {
console.log(serviceWorker.state); console.log(serviceWorker.state);
}); serviceWorker.addEventListener("statechange", (_) => {
} console.log(serviceWorker.state);
}) });
}
});
} }

File diff suppressed because it is too large Load diff