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",
"rimraf": "^6.0.1"
},
"prettier":{
"useTabs":true,
"printWidth":100,
"semi":true,
"bracketSpacing":false
},
"devDependencies": {
"@eslint/js": "^9.10.0",
"@html-eslint/eslint-plugin": "^0.25.0",

View file

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

View file

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

View file

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

View file

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

View file

@ -1,26 +1,39 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Audio</title>
<meta content="Jank Sound" property="og:title">
<meta content="A sound editor for jank clients sound format .jasf" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Sound" property="og:title" />
<meta content="A sound editor for jank clients sound format .jasf" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<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>
</head>
<body class="no-theme" style="overflow-y: scroll;">
<body class="no-theme" style="overflow-y: scroll">
<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>
<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>
</body>
<script src="/audio/page.js" type="module"></script>
</html>

View file

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

View file

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

View file

@ -1,46 +1,45 @@
import { BinRead } from "../utils/binaryUtils.js";
import { AVoice } from "./voice.js";
import {BinRead} from "../utils/binaryUtils.js";
import {AVoice} from "./voice.js";
export class Track{
seq:(AVoice|number)[];
constructor(playing:(AVoice|number)[]){
this.seq=playing;
}
static parse(read:BinRead,play:[AVoice,string][],six:boolean):Track{
const length=read.read16();
const play2:(AVoice|number)[]=[];
for(let i=0;i<length;i++){
let index:number;
if(six){
index=read.read16();
}else{
index=read.read8();
}
if(index===0){
play2.push(read.readFloat32());
continue;
}
index--;
if(!play[index]) throw new Error("voice not found");
const [voice]=play[index];
let temp:AVoice;
if((voice.info.wave instanceof Function)){
temp=voice.clone(read.readFloat32(),read.readFloat32());
}else{
temp=voice.clone(read.readFloat32(),read.readFloat32(),read.readFloat32());
}
play2.push(temp);
}
return new Track(play2);
}
async play(){
for(const thing of this.seq){
if(thing instanceof AVoice){
thing.playL();
}else{
await new Promise((res)=>setTimeout(res,thing));
}
}
}
export class Track {
seq: (AVoice | number)[];
constructor(playing: (AVoice | number)[]) {
this.seq = playing;
}
static parse(read: BinRead, play: [AVoice, string][], six: boolean): Track {
const length = read.read16();
const play2: (AVoice | number)[] = [];
for (let i = 0; i < length; i++) {
let index: number;
if (six) {
index = read.read16();
} else {
index = read.read8();
}
if (index === 0) {
play2.push(read.readFloat32());
continue;
}
index--;
if (!play[index]) throw new Error("voice not found");
const [voice] = play[index];
let temp: AVoice;
if (voice.info.wave instanceof Function) {
temp = voice.clone(read.readFloat32(), read.readFloat32());
} else {
temp = voice.clone(read.readFloat32(), read.readFloat32(), read.readFloat32());
}
play2.push(temp);
}
return new Track(play2);
}
async play() {
for (const thing of this.seq) {
if (thing instanceof AVoice) {
thing.playL();
} else {
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;
info: { wave: string | Function; freq: number };
info: {wave: string | Function; freq: number};
playing: boolean;
myArrayBuffer: AudioBuffer;
gainNode: GainNode;
buffer: Float32Array;
source: AudioBufferSourceNode;
length=1;
constructor(wave: string | Function, freq: number, volume = 1,length=1000){
this.length=length;
length = 1;
constructor(wave: string | Function, freq: number, volume = 1, length = 1000) {
this.length = length;
this.audioCtx = new window.AudioContext();
this.info = { wave, freq };
this.info = {wave, freq};
this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer(
1,
this.audioCtx.sampleRate*length/1000,
this.audioCtx.sampleRate
(this.audioCtx.sampleRate * length) / 1000,
this.audioCtx.sampleRate,
);
this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume;
@ -29,193 +29,193 @@ class AVoice{
this.source.start();
this.updateWave();
}
clone(volume:number,freq:number,length=this.length){
return new AVoice(this.wave,freq,volume,length);
clone(volume: number, freq: number, length = this.length) {
return new AVoice(this.wave, freq, volume, length);
}
get wave(): string | Function{
get wave(): string | Function {
return this.info.wave;
}
get freq(): number{
get freq(): number {
return this.info.freq;
}
set wave(wave: string | Function){
set wave(wave: string | Function) {
this.info.wave = wave;
this.updateWave();
}
set freq(freq: number){
set freq(freq: number) {
this.info.freq = freq;
this.updateWave();
}
updateWave(): void{
updateWave(): void {
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);
}
}
waveFunction(): Function{
if(typeof this.wave === "function"){
waveFunction(): Function {
if (typeof this.wave === "function") {
return this.wave;
}
switch(this.wave){
case"sin":
return(t: number, freq: number)=>{
return Math.sin(t * Math.PI * 2 * freq);
};
case"triangle":
return(t: number, freq: number)=>{
return Math.abs(((4 * t * freq) % 4) - 2) - 1;
};
case"sawtooth":
return(t: number, freq: number)=>{
return((t * freq) % 1) * 2 - 1;
};
case"square":
return(t: number, freq: number)=>{
return(t * freq) % 2 < 1 ? 1 : -1;
};
case"white":
return(_t: number, _freq: number)=>{
return Math.random() * 2 - 1;
};
switch (this.wave) {
case "sin":
return (t: number, freq: number) => {
return Math.sin(t * Math.PI * 2 * freq);
};
case "triangle":
return (t: number, freq: number) => {
return Math.abs(((4 * t * freq) % 4) - 2) - 1;
};
case "sawtooth":
return (t: number, freq: number) => {
return ((t * freq) % 1) * 2 - 1;
};
case "square":
return (t: number, freq: number) => {
return (t * freq) % 2 < 1 ? 1 : -1;
};
case "white":
return (_t: number, _freq: number) => {
return Math.random() * 2 - 1;
};
}
return new Function();
}
play(): void{
if(this.playing){
play(): void {
if (this.playing) {
return;
}
this.source.connect(this.gainNode);
this.playing = true;
}
playL(){
playL() {
this.play();
setTimeout(()=>{
setTimeout(() => {
this.stop();
},this.length);
}, this.length);
}
stop(): void{
if(this.playing){
stop(): void {
if (this.playing) {
this.source.disconnect();
this.playing = false;
}
}
static noises(noise: string): void{
switch(noise){
case"three": {
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(_=>{
static noises(noise: string): void {
switch (noise) {
case "three": {
const voicy = new AVoice("sin", 800);
voicy.play();
}, 100);
setTimeout(_=>{
voicy.stop();
}, 150);
break;
}
case "join":{
const voicy = new AVoice("triangle", 600,.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,.5);
voicy.play();
setTimeout(_=>{
voicy.freq=700;
}, 100);
setTimeout(_=>{
voicy.stop();
voicy.freq=400;
}, 180);
setTimeout(_=>{
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();
}, 200);
setTimeout(_=>{
voicy.stop();
}, 250);
break;
}
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();
}, 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(){
return["three", "zip", "square", "beep"];
static get sounds() {
return ["three", "zip", "square", "beep"];
}
static getVoice(read:BinRead):[AVoice,string]{
static getVoice(read: BinRead): [AVoice, string] {
const name = read.readString8();
let length=read.readFloat32();
let special:Function|string
if(length!==0){
special=this.parseExpression(read);
}else{
special=name;
length=1;
let length = read.readFloat32();
let special: Function | string;
if (length !== 0) {
special = this.parseExpression(read);
} else {
special = name;
length = 1;
}
return [new AVoice(special,0,0,length),name]
return [new AVoice(special, 0, 0, length), name];
}
static parseExpression(read:BinRead):Function{
return new Function("t","f",`return ${this.PEHelper(read)};`);
static parseExpression(read: BinRead): Function {
return new Function("t", "f", `return ${this.PEHelper(read)};`);
}
static PEHelper(read:BinRead):string{
let state=read.read8();
switch(state){
static PEHelper(read: BinRead): string {
let state = read.read8();
switch (state) {
case 0:
return ""+read.readFloat32();
return "" + read.readFloat32();
case 1:
return "t";
case 2:
return "f";
case 3:
return `Math.PI`
return `Math.PI`;
case 4:
return `Math.sin(${this.PEHelper(read)})`;
case 5:
@ -238,9 +238,8 @@ class AVoice{
return `Math.cos(${this.PEHelper(read)})`;
default:
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";
class Contextmenu<x, y>{
import {iOS} from "./utils/utils.js";
class Contextmenu<x, y> {
static currentmenu: HTMLElement | "";
name: string;
buttons: [
string|(()=>string),
string | (() => string),
(this: x, arg: y, e: MouseEvent) => void,
string | null,
(this: x, arg: y) => boolean,
(this: x, arg: y) => boolean,
string
string,
][];
div!: HTMLDivElement;
static setup(){
static setup() {
Contextmenu.currentmenu = "";
document.addEventListener("click", event=>{
if(Contextmenu.currentmenu === ""){
document.addEventListener("click", (event) => {
if (Contextmenu.currentmenu === "") {
return;
}
if(!Contextmenu.currentmenu.contains(event.target as Node)){
if (!Contextmenu.currentmenu.contains(event.target as Node)) {
Contextmenu.currentmenu.remove();
Contextmenu.currentmenu = "";
}
});
}
constructor(name: string){
constructor(name: string) {
this.name = name;
this.buttons = [];
}
addbutton(
text: string|(()=>string),
text: string | (() => string),
onclick: (this: x, arg: y, e: MouseEvent) => void,
img: null | string = null,
shown: (this: x, arg: y) => boolean = _=>true,
enabled: (this: x, arg: y) => boolean = _=>true
){
shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = (_) => true,
) {
this.buttons.push([text, onclick, img, shown, enabled, "button"]);
return{};
return {};
}
addsubmenu(
text: string|(()=>string),
text: string | (() => string),
onclick: (this: x, arg: y, e: MouseEvent) => void,
img = null,
shown: (this: x, arg: y) => boolean = _=>true,
enabled: (this: x, arg: y) => boolean = _=>true
){
shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = (_) => true,
) {
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");
div.classList.add("contextmenu", "flexttb");
let visibleButtons = 0;
for(const thing of this.buttons){
if(!thing[3].call(addinfo, other))continue;
for (const thing of this.buttons) {
if (!thing[3].call(addinfo, other)) continue;
visibleButtons++;
const intext = document.createElement("button");
intext.disabled = !thing[4].call(addinfo, other);
intext.classList.add("contextbutton");
if(thing[0] instanceof Function){
if (thing[0] instanceof Function) {
intext.textContent = thing[0]();
}else{
} else {
intext.textContent = thing[0];
}
console.log(thing);
if(thing[5] === "button" || thing[5] === "submenu"){
intext.onclick = (e)=>{
if (thing[5] === "button" || thing[5] === "submenu") {
intext.onclick = (e) => {
div.remove();
thing[1].call(addinfo, other,e)
thing[1].call(addinfo, other, e);
};
}
div.appendChild(intext);
}
if(visibleButtons == 0)return;
if (visibleButtons == 0) return;
if(Contextmenu.currentmenu != ""){
if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove();
}
div.style.top = y + "px";
@ -87,64 +87,74 @@ class Contextmenu<x, y>{
Contextmenu.currentmenu = div;
return this.div;
}
bindContextmenu(obj: HTMLElement, addinfo: x, other: y,touchDrag:(x:number,y:number)=>unknown=()=>{},touchEnd:(x:number,y:number)=>unknown=()=>{}){
const func = (event: MouseEvent)=>{
bindContextmenu(
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.stopImmediatePropagation();
this.makemenu(event.clientX, event.clientY, addinfo, other);
};
obj.addEventListener("contextmenu", func);
if(iOS){
let hold:NodeJS.Timeout|undefined;
let x!:number;
let y!:number;
obj.addEventListener("touchstart",(event: TouchEvent)=>{
x=event.touches[0].pageX;
y=event.touches[0].pageY;
if(event.touches.length > 1){
event.preventDefault();
event.stopImmediatePropagation();
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
}else{
//
event.stopImmediatePropagation();
hold=setTimeout(()=>{
if(lastx**2+lasty**2>10**2) return;
if (iOS) {
let hold: NodeJS.Timeout | undefined;
let x!: number;
let y!: number;
obj.addEventListener(
"touchstart",
(event: TouchEvent) => {
x = event.touches[0].pageX;
y = event.touches[0].pageY;
if (event.touches.length > 1) {
event.preventDefault();
event.stopImmediatePropagation();
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
console.log(obj);
},500)
}
},{passive: false});
let lastx=0;
let lasty=0;
obj.addEventListener("touchend",()=>{
if(hold){
} else {
//
event.stopImmediatePropagation();
hold = setTimeout(() => {
if (lastx ** 2 + lasty ** 2 > 10 ** 2) return;
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
console.log(obj);
}, 500);
}
},
{passive: false},
);
let lastx = 0;
let lasty = 0;
obj.addEventListener("touchend", () => {
if (hold) {
clearTimeout(hold);
}
touchEnd(lastx,lasty);
touchEnd(lastx, lasty);
});
obj.addEventListener("touchmove",(event)=>{
lastx=event.touches[0].pageX-x;
lasty=event.touches[0].pageY-y;
touchDrag(lastx,lasty);
obj.addEventListener("touchmove", (event) => {
lastx = event.touches[0].pageX - x;
lasty = event.touches[0].pageY - y;
touchDrag(lastx, lasty);
});
}
return func;
}
static keepOnScreen(obj: HTMLElement){
static keepOnScreen(obj: HTMLElement) {
const html = document.documentElement.getBoundingClientRect();
const docheight = html.height;
const docwidth = html.width;
const box = obj.getBoundingClientRect();
console.log(box, docheight, docwidth);
if(box.right > docwidth){
if (box.right > docwidth) {
console.log("test");
obj.style.left = docwidth - box.width + "px";
}
if(box.bottom > docheight){
if (box.bottom > docheight) {
obj.style.top = docheight - box.height + "px";
}
}
}
Contextmenu.setup();
export{ Contextmenu };
export {Contextmenu};

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
import{ Message }from"./message.js";
import{ filejson }from"./jsontypes.js";
import { ImagesDisplay } from "./disimg.js";
import {Message} from "./message.js";
import {filejson} from "./jsontypes.js";
import {ImagesDisplay} from "./disimg.js";
class File{
class File {
owner: Message | null;
id: string;
filename: string;
@ -12,7 +12,7 @@ class File{
proxy_url: string | undefined;
url: string;
size: number;
constructor(fileJSON: filejson, owner: Message | null){
constructor(fileJSON: filejson, owner: Message | null) {
this.owner = owner;
this.id = fileJSON.id;
this.filename = fileJSON.filename;
@ -24,9 +24,9 @@ class File{
this.content_type = fileJSON.content_type;
this.size = fileJSON.size;
}
getHTML(temp: boolean = false): HTMLElement{
getHTML(temp: boolean = false): HTMLElement {
const src = this.proxy_url || this.url;
if(this.width && this.height){
if (this.width && this.height) {
let scale = 1;
const max = 96 * 3;
scale = Math.max(scale, this.width / max);
@ -34,35 +34,35 @@ class File{
this.width /= scale;
this.height /= scale;
}
if(this.content_type.startsWith("image/")){
if (this.content_type.startsWith("image/")) {
const div = document.createElement("div");
const img = document.createElement("img");
img.classList.add("messageimg");
div.classList.add("messageimgdiv");
img.onclick = function(){
img.onclick = function () {
const full = new ImagesDisplay([img.src]);
full.show();
};
img.src = src;
div.append(img);
if(this.width){
if (this.width) {
div.style.width = this.width + "px";
div.style.height = this.height + "px";
}
return div;
}else if(this.content_type.startsWith("video/")){
} else if (this.content_type.startsWith("video/")) {
const video = document.createElement("video");
const source = document.createElement("source");
source.src = src;
video.append(source);
source.type = this.content_type;
video.controls = !temp;
if(this.width && this.height){
if (this.width && this.height) {
video.width = this.width;
video.height = this.height;
}
return video;
}else if(this.content_type.startsWith("audio/")){
} else if (this.content_type.startsWith("audio/")) {
const audio = document.createElement("audio");
const source = document.createElement("source");
source.src = src;
@ -70,11 +70,11 @@ class File{
source.type = this.content_type;
audio.controls = !temp;
return audio;
}else{
} else {
return this.createunknown();
}
}
upHTML(files: Blob[], file: globalThis.File): HTMLElement{
upHTML(files: Blob[], file: globalThis.File): HTMLElement {
const div = document.createElement("div");
const contained = this.getHTML(true);
div.classList.add("containedFile");
@ -82,9 +82,9 @@ class File{
const controls = document.createElement("div");
const garbage = document.createElement("button");
const icon = document.createElement("span");
icon.classList.add("svgicon","svg-delete");
icon.classList.add("svgicon", "svg-delete");
garbage.append(icon);
garbage.onclick = _=>{
garbage.onclick = (_) => {
div.remove();
files.splice(files.indexOf(file), 1);
};
@ -93,7 +93,7 @@ class File{
controls.append(garbage);
return div;
}
static initFromBlob(file: globalThis.File){
static initFromBlob(file: globalThis.File) {
return new File(
{
filename: file.name,
@ -105,10 +105,10 @@ class File{
url: URL.createObjectURL(file),
proxy_url: undefined,
},
null
null,
);
}
createunknown(): HTMLElement{
createunknown(): HTMLElement {
console.log("🗎");
const src = this.proxy_url || this.url;
const div = document.createElement("table");
@ -121,12 +121,12 @@ class File{
fileicon.classList.add("fileicon");
fileicon.rowSpan = 2;
const nametd = document.createElement("td");
if(src){
if (src) {
const a = document.createElement("a");
a.href = src;
a.textContent = this.filename;
nametd.append(a);
}else{
} else {
nametd.textContent = this.filename;
}
@ -140,11 +140,13 @@ class File{
div.appendChild(sizetr);
return div;
}
static filesizehuman(fsize: number){
static filesizehuman(fsize: number) {
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
return(
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong
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
);
}
}
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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<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>
</head>
<body class="no-theme" style="overflow-y: scroll;">
<body class="no-theme" style="overflow-y: scroll">
<div id="titleDiv">
<img src="/logo.svg" width="40">
<img src="/logo.svg" width="40" />
<h1 id="pageTitle">Jank Client</h1>
<a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat"
class="TitleButtons">
<a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" class="TitleButtons">
Spacebar Guild
</a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
Github
</a>
<a href="/channels/@me" class="TitleButtons" id="openClient">
Open Client
</a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
<a href="/channels/@me" class="TitleButtons" id="openClient"> Open Client </a>
</div>
<div id="homePage">
<h1 class="pagehead" id="welcomeJank">Welcome to Jank Client</h1>
<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">
<li>Direct Messaging</li>
<li>Reactions support</li>
@ -47,18 +52,18 @@
</div>
<div class="pagebox">
<h2 id="compatableInstances">Spacebar-Compatible Instances:</h2>
<div id="instancebox">
</div>
<div id="instancebox"></div>
</div>
<div class="pagebox">
<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>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
Github
</a>
<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 />
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
</div>
</div>
</body>
<script src="/home.js" type="module"></script>
</html>

View file

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

View file

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

View file

@ -1,122 +1,118 @@
//@ts-ignore
import {langs} from "./translations/langs.js";
const langmap=new Map<string,string>();
for(const lang of Object.keys(langs) as string[]){
langmap.set(lang,langs[lang]);
const langmap = new Map<string, string>();
for (const lang of Object.keys(langs) as string[]) {
langmap.set(lang, langs[lang]);
}
console.log(langs);
type translation={
[key:string]:string|translation
type translation = {
[key: string]: string | translation;
};
let res:()=>unknown=()=>{};
class I18n{
static lang:string;
static translations:translation[]=[];
static done=new Promise<void>((res2,_reject)=>{
res=res2;
});
static async create(lang:string){
let res: () => unknown = () => {};
class I18n {
static lang: string;
static translations: translation[] = [];
static done = new Promise<void>((res2, _reject) => {
res = res2;
});
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;
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;
res();
}
static getTranslation(msg: string, ...params: string[]): string {
let str: string | undefined;
const path = msg.split(".");
for (const json of this.translations) {
let jsont: string | translation = json;
for (const thing of path) {
if (typeof jsont !== "string" && jsont !== undefined) {
jsont = jsont[thing];
} else {
jsont = json;
break;
}
}
res();
}
static getTranslation(msg:string,...params:string[]):string{
let str:string|undefined;
const path=msg.split(".");
for(const json of this.translations){
let jsont:string|translation=json;
for(const thing of path){
if(typeof jsont !== "string" && jsont!==undefined){
jsont=jsont[thing];
if (typeof jsont === "string") {
str = jsont;
break;
}
}
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;
});
}else{
jsont=json;
break;
}
}
if(typeof jsont === "string"){
str=jsont;
break;
}
}
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);
}
}
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);
let userLocale = navigator.language.slice(0,2) || "en";
if(I18n.options().indexOf(userLocale)===-1){
userLocale="en";
let userLocale = navigator.language.slice(0, 2) || "en";
if (I18n.options().indexOf(userLocale) === -1) {
userLocale = "en";
}
const storage=localStorage.getItem("lang");
if(storage){
userLocale=storage;
}else{
localStorage.setItem("lang",userLocale)
const storage = localStorage.getItem("lang");
if (storage) {
userLocale = storage;
} else {
localStorage.setItem("lang", userLocale);
}
I18n.create(userLocale);
export{I18n,langmap};
export {I18n, langmap};

View file

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

View file

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

View file

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

View file

@ -1,17 +1,26 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Invite" property="og:title">
<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 content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Invite" property="og:title" />
<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 content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<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>
</head>
<body class="no-theme">
<div>
@ -24,4 +33,4 @@
</div>
<script type="module" src="/invite.js"></script>
</body>
</html>
</html>

View file

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

View file

@ -63,7 +63,12 @@ type readyjson = {
};
user_guild_settings: {
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;
flags: number;
hide_muted_channels: boolean;
@ -149,12 +154,12 @@ type memberjson = {
id: string;
user: userjson | null;
guild_id: string;
avatar?:string;
banner?:string;
avatar?: string;
banner?: string;
guild: {
id: string;
} | null;
presence?:presencejson
presence?: presencejson;
nick?: string;
roles: string[];
joined_at: string;
@ -168,21 +173,20 @@ type emojijson = {
name: string;
id?: string;
animated?: boolean;
emoji?:string;
emoji?: string;
};
type emojipjson=emojijson&{
available: boolean,
guild_id:string,
user_id:string,
managed:boolean,
require_colons:boolean,
roles:string[],
groups:null//TODO figure out what this means lol
type emojipjson = emojijson & {
available: boolean;
guild_id: string;
user_id: string;
managed: boolean;
require_colons: boolean;
roles: string[];
groups: null; //TODO figure out what this means lol
};
type guildjson = {
application_command_counts: { [key: string]: number };
application_command_counts: {[key: string]: number};
channels: channeljson[];
data_mode: string;
emojis: emojipjson[];
@ -404,165 +408,178 @@ type messageCreateJson = {
s: number;
t: "MESSAGE_CREATE";
};
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;
}
| {
type roleCreate = {
op: 0;
t: "GUILD_ROLE_CREATE";
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;
role: rolesjson;
};
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
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;
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 = {
guild_id: string;
@ -573,134 +590,140 @@ type memberChunk = {
chunk_count: number;
not_found: string[];
};
type voiceupdate={
op: 0,
t: "VOICE_STATE_UPDATE",
type voiceupdate = {
op: 0;
t: "VOICE_STATE_UPDATE";
d: {
guild_id: string,
channel_id: string,
user_id: string,
member: memberjson,
session_id: string,
token: string,
deaf: boolean,
mute: boolean,
self_deaf: boolean,
self_mute: boolean,
self_video: boolean,
suppress: boolean
},
s: number
guild_id: string;
channel_id: string;
user_id: string;
member: memberjson;
session_id: string;
token: string;
deaf: boolean;
mute: boolean;
self_deaf: boolean;
self_mute: boolean;
self_video: boolean;
suppress: boolean;
};
s: number;
};
type voiceserverupdate={
op: 0,
t: "VOICE_SERVER_UPDATE",
type voiceserverupdate = {
op: 0;
t: "VOICE_SERVER_UPDATE";
d: {
token: string,
guild_id: string,
endpoint: string
},
s: 6
token: string;
guild_id: string;
endpoint: string;
};
s: 6;
};
type memberlistupdatejson={
op: 0,
s: number,
t: "GUILD_MEMBER_LIST_UPDATE",
type memberlistupdatejson = {
op: 0;
s: number;
t: "GUILD_MEMBER_LIST_UPDATE";
d: {
ops: [
{
items:({
group:{
count:number,
id:string
}
}|{
member:memberjson
})[]
op: "SYNC",
range: [
number,
number
]
}
],
online_count: number,
member_count: number,
id: string,
guild_id: string,
items: (
| {
group: {
count: number;
id: string;
};
}
| {
member: memberjson;
}
)[];
op: "SYNC";
range: [number, number];
},
];
online_count: number;
member_count: number;
id: string;
guild_id: string;
groups: {
count: number,
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
}
count: number;
id: string;
}[];
};
};
type sdpback={
op: 4,
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 = {
op: 4;
d: {
audioCodec: string,
videoCodec: string,
media_session_id: string,
sdp: string
}
audioCodec: string;
videoCodec: string;
media_session_id: string;
sdp: string;
};
};
type opRTC12={
op: 12,
type opRTC12 = {
op: 12;
d: {
user_id: string,
audio_ssrc: number,
video_ssrc: number,
user_id: string;
audio_ssrc: number;
video_ssrc: number;
streams: [
{
type: "video",
rid: "100",
ssrc: number,
active: boolean,
quality: 100,
rtx_ssrc: number,
max_bitrate: 2500000,
max_framerate: number,
type: "video";
rid: "100";
ssrc: number;
active: boolean;
quality: 100;
rtx_ssrc: number;
max_bitrate: 2500000;
max_framerate: number;
max_resolution: {
type: "fixed",
width: number,
height: number
}
}
]
}
}
export{
type: "fixed";
width: number;
height: number;
};
},
];
};
};
export {
readyjson,
dirrectjson,
startTypingjson,
@ -725,5 +748,5 @@ export{
webRTCSocket,
sdpback,
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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta
content="A spacebar client that has DMs, replying and more"
property="og:description"
>
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<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>
</head>
<body class="no-theme">
<div id="logindiv">
@ -29,25 +35,13 @@
id="instancein"
value=""
required
>
/>
<label for="uname" id="emailField"><b>Email:</b></label>
<input
type="text"
placeholder="Enter email address"
name="uname"
id="uname"
required
>
<input type="text" placeholder="Enter email address" name="uname" id="uname" required />
<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 />
<p class="wrongred" id="wrong"></p>
<div id="h-captcha"></div>

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,125 +1,127 @@
import{ User }from"./user.js";
import{ Role }from"./role.js";
import{ Guild }from"./guild.js";
import{ SnowFlake }from"./snowflake.js";
import{ memberjson, presencejson }from"./jsontypes.js";
import { I18n } from "./i18n.js";
import { Dialog, Options, Settings } from "./settings.js";
import {User} from "./user.js";
import {Role} from "./role.js";
import {Guild} from "./guild.js";
import {SnowFlake} from "./snowflake.js";
import {memberjson, presencejson} from "./jsontypes.js";
import {I18n} from "./i18n.js";
import {Dialog, Options, Settings} from "./settings.js";
class Member extends SnowFlake{
class Member extends SnowFlake {
static already = {};
owner: Guild;
user: User;
roles: Role[] = [];
nick!: string;
avatar:void|string=undefined;
banner:void|string=undefined;
private constructor(memberjson: memberjson, owner: Guild){
avatar: void | string = undefined;
banner: void | string = undefined;
private constructor(memberjson: memberjson, owner: Guild) {
super(memberjson.id);
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;
}else if(memberjson.user){
} else if (memberjson.user) {
this.user = new User(memberjson.user, owner.localuser);
}else{
} else {
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;
}
for(const key of Object.keys(memberjson)){
if(key === "guild" || key === "owner" || key === "user"){
for (const key of Object.keys(memberjson)) {
if (key === "guild" || key === "owner" || key === "user") {
continue;
}
if(key === "roles"){
for(const strrole of memberjson.roles){
if (key === "roles") {
for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole);
if(!role)continue;
if (!role) continue;
this.roles.push(role);
}
continue;
}
if(key === "presence"){
if (key === "presence") {
this.getPresence(memberjson.presence);
continue;
}
(this as any)[key] = (memberjson as any)[key];
}
if(!this.user.bot){
const everyone=this.guild.roleids.get(this.guild.id);
if(everyone&&(this.roles.indexOf(everyone)===-1)){
this.roles.push(everyone)
if (!this.user.bot) {
const everyone = this.guild.roleids.get(this.guild.id);
if (everyone && this.roles.indexOf(everyone) === -1) {
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);
});
}
remove(){
remove() {
this.user.members.delete(this.guild);
this.guild.members.delete(this);
}
getpfpsrc():string{
if(this.hypotheticalpfp&&this.avatar){
getpfpsrc(): string {
if (this.hypotheticalpfp && this.avatar) {
return this.avatar;
}
if(this.avatar !== undefined&&this.avatar!==null){
return`${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${
if (this.avatar !== undefined && this.avatar !== null) {
return `${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${
this.avatar
}.${this.avatar.startsWith("a_")?"gif":"png"}`;
}.${this.avatar.startsWith("a_") ? "gif" : "png"}`;
}
return this.user.getpfpsrc();
}
getBannerUrl():string|undefined{
if(this.hypotheticalbanner&&this.banner){
getBannerUrl(): string | undefined {
if (this.hypotheticalbanner && this.banner) {
return this.banner;
}
if(this.banner){
if (this.banner) {
return `${this.info.cdn}/banners/${this.guild.id}/${
this.banner
}.${this.banner.startsWith("a_")?"gif":"png"}`;;
}else{
this.banner
}.${this.banner.startsWith("a_") ? "gif" : "png"}`;
} else {
return undefined;
}
}
joined_at!:string;
premium_since!:string;
deaf!:boolean;
mute!:boolean;
pending!:boolean
clone(){
return new Member({
id:this.id+"#clone",
user:this.user.tojson(),
guild_id:this.guild.id,
guild:{id:this.guild.id},
avatar:this.avatar as (string|undefined),
banner:this.banner as (string|undefined),
//TODO presence
nick:this.nick,
roles:this.roles.map(_=>_.id),
joined_at:this.joined_at,
premium_since:this.premium_since,
deaf:this.deaf,
mute:this.mute,
pending:this.pending
},this.owner)
joined_at!: string;
premium_since!: string;
deaf!: boolean;
mute!: boolean;
pending!: boolean;
clone() {
return new Member(
{
id: this.id + "#clone",
user: this.user.tojson(),
guild_id: this.guild.id,
guild: {id: this.guild.id},
avatar: this.avatar as string | undefined,
banner: this.banner as string | undefined,
//TODO presence
nick: this.nick,
roles: this.roles.map((_) => _.id),
joined_at: this.joined_at,
premium_since: this.premium_since,
deaf: this.deaf,
mute: this.mute,
pending: this.pending,
},
this.owner,
);
}
pronouns?:string;
bio?:string;
hypotheticalpfp=false;
hypotheticalbanner=false;
accent_color?:number;
get headers(){
pronouns?: string;
bio?: string;
hypotheticalpfp = false;
hypotheticalbanner = false;
accent_color?: number;
get headers() {
return this.owner.headers;
}
updatepfp(file: Blob): void{
updatepfp(file: Blob): void {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ()=>{
reader.onload = () => {
fetch(this.info.api + `/guilds/${this.guild.id}/members/${this.id}/`, {
method: "PATCH",
headers: this.headers,
@ -129,11 +131,11 @@ class Member extends SnowFlake{
});
};
}
updatebanner(file: Blob | null): void{
if(file){
updatebanner(file: Blob | null): void {
if (file) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ()=>{
reader.onload = () => {
fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, {
method: "PATCH",
headers: this.headers,
@ -142,7 +144,7 @@ class Member extends SnowFlake{
}),
});
};
}else{
} else {
fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, {
method: "PATCH",
headers: this.headers,
@ -153,11 +155,7 @@ class Member extends SnowFlake{
}
}
updateProfile(json: {
bio?: string|null;
pronouns?: string|null;
nick?:string|null;
}){
updateProfile(json: {bio?: string | null; pronouns?: string | null; nick?: string | null}) {
console.log(JSON.stringify(json));
/*
if(json.bio===""){
@ -176,24 +174,26 @@ class Member extends SnowFlake{
body: JSON.stringify(json),
});
}
showEditProfile(){
const settings=new Settings("");
this.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true}));
showEditProfile() {
const settings = new Settings("");
this.editProfile(
settings.addButton(I18n.getTranslation("user.editServerProfile"), {ltr: true}),
);
settings.show();
}
editProfile(options:Options){
if(this.hasPermission("CHANGE_NICKNAME")){
editProfile(options: Options) {
if (this.hasPermission("CHANGE_NICKNAME")) {
const hypotheticalProfile = document.createElement("div");
let file: undefined | File | null;
let newpronouns: string | undefined;
let newbio: string | undefined;
let nick:string|undefined;
let nick: string | undefined;
const hypomember = this.clone();
let color: string;
async function regen(){
async function regen() {
hypotheticalProfile.textContent = "";
const hypoprofile = await hypomember.user.buildprofile(-1, -1,hypomember);
const hypoprofile = await hypomember.user.buildprofile(-1, -1, hypomember);
hypotheticalProfile.appendChild(hypoprofile);
}
@ -202,33 +202,33 @@ class Member extends SnowFlake{
const settingsRight = options.addOptions("");
settingsRight.addHTMLArea(hypotheticalProfile);
const nicky=settingsLeft.addTextInput(I18n.getTranslation("member.nick:"),()=>{},{
initText:this.nick||""
const nicky = settingsLeft.addTextInput(I18n.getTranslation("member.nick:"), () => {}, {
initText: this.nick || "",
});
nicky.watchForChange(_=>{
hypomember.nick=_;
nick=_;
nicky.watchForChange((_) => {
hypomember.nick = _;
nick = _;
regen();
})
});
const finput = settingsLeft.addFileInput(
I18n.getTranslation("uploadPfp"),
_=>{
if(file){
(_) => {
if (file) {
this.updatepfp(file);
}
},
{ clear: true }
{clear: true},
);
finput.watchForChange(_=>{
if(!_){
finput.watchForChange((_) => {
if (!_) {
file = null;
hypomember.avatar = undefined;
hypomember.hypotheticalpfp = true;
regen();
return;
}
if(_.length){
if (_.length) {
file = _[0];
const blob = URL.createObjectURL(file);
hypomember.avatar = blob;
@ -239,22 +239,22 @@ class Member extends SnowFlake{
let bfile: undefined | File | null;
const binput = settingsLeft.addFileInput(
I18n.getTranslation("uploadBanner"),
_=>{
if(bfile !== undefined){
(_) => {
if (bfile !== undefined) {
this.updatebanner(bfile);
}
},
{ clear: true }
{clear: true},
);
binput.watchForChange(_=>{
if(!_){
binput.watchForChange((_) => {
if (!_) {
bfile = null;
hypomember.banner = undefined;
hypomember.hypotheticalbanner = true;
regen();
return;
}
if(_.length){
if (_.length) {
bfile = _[0];
const blob = URL.createObjectURL(bfile);
hypomember.banner = blob;
@ -265,43 +265,43 @@ class Member extends SnowFlake{
let changed = false;
const pronounbox = settingsLeft.addTextInput(
I18n.getTranslation("pronouns"),
_=>{
if(newpronouns!==undefined||newbio!==undefined||changed!==undefined){
(_) => {
if (newpronouns !== undefined || newbio !== undefined || changed !== undefined) {
this.updateProfile({
pronouns: newpronouns,
bio: newbio,
//accent_color: Number.parseInt("0x" + color.substr(1), 16),
nick
nick,
});
}
},
{ initText: this.pronouns }
{initText: this.pronouns},
);
pronounbox.watchForChange(_=>{
pronounbox.watchForChange((_) => {
hypomember.pronouns = _;
newpronouns = _;
regen();
});
const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, {
const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), (_) => {}, {
initText: this.bio,
});
bioBox.watchForChange(_=>{
bioBox.watchForChange((_) => {
newbio = _;
hypomember.bio = _;
regen();
});
return;//Returns early to stop errors
if(this.accent_color){
return; //Returns early to stop errors
if (this.accent_color) {
color = "#" + this.accent_color.toString(16);
}else{
} else {
color = "transparent";
}
const colorPicker = settingsLeft.addColorInput(
I18n.getTranslation("profileColor"),
_=>{},
{ initColor: color }
(_) => {},
{initColor: color},
);
colorPicker.watchForChange(_=>{
colorPicker.watchForChange((_) => {
console.log();
color = _;
hypomember.accent_color = Number.parseInt("0x" + _.substr(1), 16);
@ -310,116 +310,116 @@ class Member extends SnowFlake{
});
}
}
update(memberjson: memberjson){
if(memberjson.roles){
this.roles=[];
update(memberjson: memberjson) {
if (memberjson.roles) {
this.roles = [];
}
for(const key of Object.keys(memberjson)){
if(key === "guild" || key === "owner" || key === "user"){
for (const key of Object.keys(memberjson)) {
if (key === "guild" || key === "owner" || key === "user") {
continue;
}
if(key === "roles"){
for(const strrole of memberjson.roles){
if (key === "roles") {
for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole);
if(!role)continue;
if (!role) continue;
this.roles.push(role);
}
if(!this.user.bot){
const everyone=this.guild.roleids.get(this.guild.id);
if(everyone&&(this.roles.indexOf(everyone)===-1)){
this.roles.push(everyone)
if (!this.user.bot) {
const everyone = this.guild.roleids.get(this.guild.id);
if (everyone && this.roles.indexOf(everyone) === -1) {
this.roles.push(everyone);
}
}
continue;
}
if(key === "presence"){
if (key === "presence") {
this.getPresence(memberjson.presence);
continue;
}
(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);
});
}
get guild(){
get guild() {
return this.owner;
}
get localuser(){
get localuser() {
return this.guild.localuser;
}
get info(){
get info() {
return this.owner.info;
}
static async new(
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined>{
static async new(memberjson: memberjson, owner: Guild): Promise<Member | undefined> {
let user: User;
if(owner.localuser.userMap.has(memberjson.id)){
if(memberjson.user){
if (owner.localuser.userMap.has(memberjson.id)) {
if (memberjson.user) {
new User(memberjson.user, owner.localuser);
}
user = owner.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){
} else if (memberjson.user) {
user = new User(memberjson.user, owner.localuser);
}else{
} else {
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);
if(memb === undefined){
if (memb === undefined) {
memb = new Member(memberjson, owner);
user.members.set(owner, memb);
owner.members.add(memb);
return memb;
}else if(memb instanceof Promise){
const member=await memb; //I should do something else, though for now this is "good enough";
if(member){
} else if (memb instanceof Promise) {
const member = await memb; //I should do something else, though for now this is "good enough";
if (member) {
member.update(memberjson);
}
return member;
}else{
if(memberjson.presence){
} else {
if (memberjson.presence) {
memb.getPresence(memberjson.presence);
}
memb.update(memberjson);
return memb;
}
}else{
} else {
const memb = new Member(memberjson, owner);
user.members.set(owner, memb);
owner.members.add(memb);
return memb;
}
}
compare(str:string){
function similar(str2:string|null|undefined){
if(!str2) return 0;
const strl=Math.max(str.length,1)
if(str2.includes(str)){
return strl/str2.length;
}else if(str2.toLowerCase().includes(str.toLowerCase())){
return strl/str2.length/1.2;
compare(str: string) {
function similar(str2: string | null | undefined) {
if (!str2) return 0;
const strl = Math.max(str.length, 1);
if (str2.includes(str)) {
return strl / str2.length;
} else if (str2.toLowerCase().includes(str.toLowerCase())) {
return strl / str2.length / 1.2;
}
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(
user: User,
guild: Guild
): Promise<Member | undefined>{
static async resolveMember(user: User, guild: Guild): Promise<Member | undefined> {
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 promise = new Promise<Member | undefined>(async res=>{
const promise = new Promise<Member | undefined>(async (res) => {
const membjson = await membpromise;
if(membjson === undefined){
if (membjson === undefined) {
return res(undefined);
}else{
} else {
const member = new Member(membjson, guild);
const map = guild.localuser.presences;
member.getPresence(map.get(member.id));
@ -429,62 +429,62 @@ class Member extends SnowFlake{
}
});
user.members.set(guild, promise);
const member=await promise;
if(member){
const member = await promise;
if (member) {
guild.members.add(member);
}
return member;
}
if(maybe instanceof Promise){
if (maybe instanceof Promise) {
return await maybe;
}else{
} else {
return maybe;
}
}
public getPresence(presence: presencejson | undefined){
public getPresence(presence: presencejson | undefined) {
this.user.getPresence(presence);
}
/**
* @todo
*/
highInfo(){
* @todo
*/
highInfo() {
fetch(
this.info.api +
"/users/" +
this.id +
"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" +
this.guild.id,
{ headers: this.guild.headers }
{headers: this.guild.headers},
);
}
hasRole(ID: string){
for(const thing of this.roles){
if(thing.id === ID){
hasRole(ID: string) {
for (const thing of this.roles) {
if (thing.id === ID) {
return true;
}
}
return false;
}
getColor(){
for(const thing of this.roles){
getColor() {
for (const thing of this.roles) {
const color = thing.getColor();
if(color){
if (color) {
return color;
}
}
return"";
return "";
}
isAdmin(){
for(const role of this.roles){
if(role.permissions.getPermission("ADMINISTRATOR")){
isAdmin() {
for (const role of this.roles) {
if (role.permissions.getPermission("ADMINISTRATOR")) {
return true;
}
}
return this.guild.properties.owner_id === this.user.id;
}
bind(html: HTMLElement){
if(html.tagName === "SPAN"){
if(!this){
bind(html: HTMLElement) {
if (html.tagName === "SPAN") {
if (!this) {
return;
}
/*
@ -497,23 +497,23 @@ class Member extends SnowFlake{
//this.profileclick(html);
}
profileclick(/* html: HTMLElement */){
profileclick(/* html: HTMLElement */) {
//to be implemented
}
get name(){
get name() {
return this.nick || this.user.username;
}
kick(){
kick() {
const menu = new Dialog("");
const form=menu.options.addForm("",((e:any)=>{
const form = menu.options.addForm("", (e: any) => {
this.kickAPI(e.reason);
menu.hide();
}));
form.addTitle(I18n.getTranslation("member.kick",this.name,this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"),"reason");
});
form.addTitle(I18n.getTranslation("member.kick", this.name, this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
menu.show();
}
kickAPI(reason: string){
kickAPI(reason: string) {
const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, {
@ -521,35 +521,35 @@ class Member extends SnowFlake{
headers,
});
}
ban(){
ban() {
const menu = new Dialog("");
const form=menu.options.addForm("",((e:any)=>{
const form = menu.options.addForm("", (e: any) => {
this.banAPI(e.reason);
menu.hide();
}));
form.addTitle(I18n.getTranslation("member.ban",this.name,this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"),"reason");
});
form.addTitle(I18n.getTranslation("member.ban", this.name, this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
menu.show();
}
addRole(role:Role){
const roles=this.roles.map(_=>_.id)
addRole(role: Role) {
const roles = this.roles.map((_) => _.id);
roles.push(role.id);
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
method:"PATCH",
headers:this.guild.headers,
body:JSON.stringify({roles})
})
fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
method: "PATCH",
headers: this.guild.headers,
body: JSON.stringify({roles}),
});
}
removeRole(role:Role){
let roles=this.roles.map(_=>_.id)
roles=roles.filter(_=>_!==role.id);
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
method:"PATCH",
headers:this.guild.headers,
body:JSON.stringify({roles})
})
removeRole(role: Role) {
let roles = this.roles.map((_) => _.id);
roles = roles.filter((_) => _ !== role.id);
fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
method: "PATCH",
headers: this.guild.headers,
body: JSON.stringify({roles}),
});
}
banAPI(reason: string){
banAPI(reason: string) {
const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, {
@ -557,16 +557,16 @@ class Member extends SnowFlake{
headers,
});
}
hasPermission(name: string): boolean{
if(this.isAdmin()){
hasPermission(name: string): boolean {
if (this.isAdmin()) {
return true;
}
for(const thing of this.roles){
if(thing.permissions.getPermission(name)){
for (const thing of this.roles) {
if (thing.permissions.getPermission(name)) {
return true;
}
}
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{ getapiurls }from"../utils/utils.js";
import { getBulkUsers, Specialuser } from "../utils/utils.js";
import { Permissions } from "../permissions.js";
type botjsonfetch={
guilds:{
id: string,
name: string,
icon: string,
mfa_level: number,
permissions: string
}[],
"user": {
id: string,
username: string,
avatar: string,
avatar_decoration?: string,
discriminator: string,
public_flags: number
},
application: {
id: string,
name: string,
icon: string|null,
description: string,
summary: string,
type: null,//not sure what this means :P
hook: boolean,
guild_id: null|string,
bot_public: boolean,
bot_require_code_grant: boolean,
verify_key: "IMPLEMENTME",//no clue what this is meant to be :P
flags: number
},
bot: {
id: string,
username: string,
avatar: string|null,
avatar_decoration: null|string,
discriminator: string,
public_flags: number,
bot: boolean,
approximated_guild_count: number
},
authorized: boolean
}
(async ()=>{
import {I18n} from "../i18n.js";
import {getapiurls} from "../utils/utils.js";
import {getBulkUsers, Specialuser} from "../utils/utils.js";
import {Permissions} from "../permissions.js";
type botjsonfetch = {
guilds: {
id: string;
name: string;
icon: string;
mfa_level: number;
permissions: string;
}[];
user: {
id: string;
username: string;
avatar: string;
avatar_decoration?: string;
discriminator: string;
public_flags: number;
};
application: {
id: string;
name: string;
icon: string | null;
description: string;
summary: string;
type: null; //not sure what this means :P
hook: boolean;
guild_id: null | string;
bot_public: boolean;
bot_require_code_grant: boolean;
verify_key: "IMPLEMENTME"; //no clue what this is meant to be :P
flags: number;
};
bot: {
id: string;
username: string;
avatar: string | null;
avatar_decoration: null | string;
discriminator: string;
public_flags: number;
bot: boolean;
approximated_guild_count: number;
};
authorized: boolean;
};
(async () => {
const users = getBulkUsers();
const params=new URLSearchParams(window.location.search);
const params = new URLSearchParams(window.location.search);
const well = params.get("instance");
const permstr=params.get("permissions");
const permstr = params.get("permissions");
const joinable: Specialuser[] = [];
for(const key in users.users){
if(Object.prototype.hasOwnProperty.call(users.users, key)){
for (const key in users.users) {
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key];
if(well && user.serverurls.wellknown.includes(well)){
if (well && user.serverurls.wellknown.includes(well)) {
joinable.push(user);
}
console.log(user);
}
}
let urls: { api: string; cdn: string } | undefined;
let urls: {api: string; cdn: string} | undefined;
if(!joinable.length && well){
if (!joinable.length && well) {
const out = await getapiurls(well);
if(out){
if (out) {
urls = out;
for(const key in users.users){
if(Object.prototype.hasOwnProperty.call(users.users, key)){
for (const key in users.users) {
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = users.users[key];
if(user.serverurls.api.includes(out.api)){
if (user.serverurls.api.includes(out.api)) {
joinable.push(user);
}
console.log(user);
}
}
}else{
throw new Error(
"Someone needs to handle the case where the servers don't exist"
);
} else {
throw new Error("Someone needs to handle the case where the servers don't exist");
}
}else{
} else {
urls = joinable[0].serverurls;
}
if(!joinable.length){
document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot";
if (!joinable.length) {
document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot";
}
await I18n.done;
function showGuilds(user:Specialuser){
if(!urls) return;
fetch(urls.api+"/oauth2/authorize/"+window.location.search,{
headers:{
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);
function showGuilds(user: Specialuser) {
if (!urls) return;
fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers: {
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);
});
}
function showAccounts(): void{
function showAccounts(): void {
const table = document.createElement("dialog");
for(const user of joinable){
for (const user of joinable) {
console.log(user.pfpsrc);
const userinfo = document.createElement("div");
@ -168,16 +168,14 @@ type botjsonfetch={
userDiv.append(document.createElement("br"));
const span = document.createElement("span");
span.textContent = user.serverurls.wellknown
.replace("https://", "")
.replace("http://", "");
span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
span.classList.add("serverURL");
userDiv.append(span);
userinfo.append(userDiv);
table.append(userinfo);
userinfo.addEventListener("click", ()=>{
userinfo.addEventListener("click", () => {
table.remove();
showGuilds(user);
});
@ -186,14 +184,14 @@ type botjsonfetch={
const td = document.createElement("div");
td.classList.add("switchtable");
td.textContent = "Login or create an account ⇌";
td.addEventListener("click", ()=>{
td.addEventListener("click", () => {
const l = new URLSearchParams("?");
l.set("goback", window.location.href);
l.set("instance", well!);
window.location.href = "/login?" + l.toString();
});
if(!joinable.length){
if (!joinable.length) {
const l = new URLSearchParams("?");
l.set("goback", window.location.href);
l.set("instance", well!);
@ -201,54 +199,56 @@ type botjsonfetch={
}
table.append(td);
table.classList.add("flexttb","accountSwitcher");
table.classList.add("flexttb", "accountSwitcher");
console.log(table);
document.body.append(table);
}
const user=joinable[0];
if(!user){
const user = joinable[0];
if (!user) {
return;
}
fetch(urls.api+"/oauth2/authorize/"+window.location.search,{
headers:{
Authorization:user.token
}
}).then(_=>_.json()).then((json:botjsonfetch)=>{
const title=document.getElementById("invitename");
if(title){
title.textContent=`Invite ${json.bot.username} to your servers`
}
const desc=document.getElementById("invitedescription");
if(desc){
desc.textContent=json.application.description;
}
const pfp=document.getElementById("inviteimg") as HTMLImageElement;
if(json.bot.avatar !== null){
pfp.src=`${urls.cdn}/avatars/${json.bot.id}/${json.bot.avatar}.png`;
}else{
const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
pfp.src=`${urls.cdn}/embed/avatars/${int}.png`;
}
const perms=document.getElementById("permissions") as HTMLDivElement;
fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers: {
Authorization: user.token,
},
})
.then((_) => _.json())
.then((json: botjsonfetch) => {
const title = document.getElementById("invitename");
if (title) {
title.textContent = `Invite ${json.bot.username} to your servers`;
}
const desc = document.getElementById("invitedescription");
if (desc) {
desc.textContent = json.application.description;
}
const pfp = document.getElementById("inviteimg") as HTMLImageElement;
if (json.bot.avatar !== null) {
pfp.src = `${urls.cdn}/avatars/${json.bot.id}/${json.bot.avatar}.png`;
} else {
const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
pfp.src = `${urls.cdn}/embed/avatars/${int}.png`;
}
const perms = document.getElementById("permissions") as HTMLDivElement;
if(perms&&permstr){
perms.children[0].textContent=I18n.getTranslation("htmlPages.idpermissions")
const permisions=new Permissions(permstr)
for(const perm of Permissions.info()){
if(permisions.hasPermission(perm.name,false)){
const div=document.createElement("div");
const h2=document.createElement("h2");
h2.textContent=perm.readableName;
div.append(h2,perm.description);
div.classList.add("flexttb");
perms.append(div);
if (perms && permstr) {
perms.children[0].textContent = I18n.getTranslation("htmlPages.idpermissions");
const permisions = new Permissions(permstr);
for (const perm of Permissions.info()) {
if (permisions.hasPermission(perm.name, false)) {
const div = document.createElement("div");
const h2 = document.createElement("h2");
h2.textContent = perm.readableName;
div.append(h2, perm.description);
div.classList.add("flexttb");
perms.append(div);
}
}
}
}
})
const AcceptInvite=document.getElementById("AcceptInvite");
if(AcceptInvite){
});
const AcceptInvite = document.getElementById("AcceptInvite");
if (AcceptInvite) {
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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Bot Invite" property="og:title">
<meta name="description" content="Invite this bot to your server!">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Bot Invite" property="og:title" />
<meta name="description" content="Invite this bot to your server!" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<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>
</head>
<body class="no-theme">
<div>
<div id="invitebody">
<img id="inviteimg" class="pfp"/>
<img id="inviteimg" class="pfp" />
<h1 id="invitename">Bot Name</h1>
<p id="invitedescription">Add Bot</p>
<div id="permissions"><h1>This will allow the bot to:</h1></div>
@ -24,4 +33,4 @@
</div>
<script type="module" src="/oauth2/auth.js"></script>
</body>
</html>
</html>

View file

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

View file

@ -1,59 +1,80 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<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>
</head>
<body class="no-theme">
<div id="logindiv">
<h1>Create an account</h1>
<form id="register" submit="registertry(e)">
<div>
<label for="instance" id="instanceField"><b>Instance:</b></label>
<label for="instance" id="instanceField"><b>Instance:</b></label>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<p class="wrongred" id="wrong"></p>
<div id="h-captcha">
</div>
<div id="h-captcha"></div>
<button type="submit" class="dontgrow" id="createAccount">Create account</button>
</form>
<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{ checkInstance }from"./utils/utils.js";
import {adduser} from"./login.js";
import { MarkDown } from "./markdown.js";
await I18n.done
import {I18n} from "./i18n.js";
import {checkInstance} from "./utils/utils.js";
import {adduser} from "./login.js";
import {MarkDown} from "./markdown.js";
await I18n.done;
const registerElement = document.getElementById("register");
if(registerElement){
if (registerElement) {
registerElement.addEventListener("submit", registertry);
}
(async ()=>{
(async () => {
await I18n.done;
const userField=document.getElementById("userField");
const pw2Field=document.getElementById("pw2Field");
const dobField=document.getElementById("dobField");
const createAccount=document.getElementById("createAccount");
const alreadyHave=document.getElementById("alreadyHave");
if(userField&&pw2Field&&alreadyHave&&createAccount&&dobField){
userField.textContent=I18n.getTranslation("htmlPages.userField")
pw2Field.textContent=I18n.getTranslation("htmlPages.pw2Field")
dobField.textContent=I18n.getTranslation("htmlPages.dobField")
createAccount.textContent=I18n.getTranslation("htmlPages.createAccount")
alreadyHave.textContent=I18n.getTranslation("htmlPages.alreadyHave")
const userField = document.getElementById("userField");
const pw2Field = document.getElementById("pw2Field");
const dobField = document.getElementById("dobField");
const createAccount = document.getElementById("createAccount");
const alreadyHave = document.getElementById("alreadyHave");
if (userField && pw2Field && alreadyHave && createAccount && dobField) {
userField.textContent = I18n.getTranslation("htmlPages.userField");
pw2Field.textContent = I18n.getTranslation("htmlPages.pw2Field");
dobField.textContent = I18n.getTranslation("htmlPages.dobField");
createAccount.textContent = I18n.getTranslation("htmlPages.createAccount");
alreadyHave.textContent = I18n.getTranslation("htmlPages.alreadyHave");
}
})()
async function registertry(e: Event){
})();
async function registertry(e: Event) {
e.preventDefault();
const elements = (e.target as HTMLFormElement)
.elements as HTMLFormControlsCollection;
const elements = (e.target as HTMLFormElement).elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value;
@ -34,15 +33,17 @@ async function registertry(e: Event){
const consent = (elements[6] as HTMLInputElement).checked;
const captchaKey = (elements[7] as HTMLInputElement)?.value;
if(password !== confirmPassword){
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation("localuser.PasswordsNoMatch");
if (password !== confirmPassword) {
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation(
"localuser.PasswordsNoMatch",
);
return;
}
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
try{
try {
const response = await fetch(apiurl + "/auth/register", {
body: JSON.stringify({
date_of_birth: dateofbirth,
@ -60,9 +61,9 @@ async function registertry(e: Event){
const data = await response.json();
if(data.captcha_sitekey){
if (data.captcha_sitekey) {
const capt = document.getElementById("h-captcha");
if(capt && !capt.children.length){
if (capt && !capt.children.length) {
const capty = document.createElement("div");
capty.classList.add("h-captcha");
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";
capt.append(script);
capt.append(capty);
}else{
} else {
eval("hcaptcha.reset()");
}
return;
}
if(!data.token){
if (!data.token) {
handleErrors(data.errors, elements);
}else{
} else {
adduser({
serverurls: instanceInfo,
email,
@ -88,74 +89,72 @@ async function registertry(e: Event){
const redir = new URLSearchParams(window.location.search).get("goback");
window.location.href = redir ? redir : "/channels/@me";
}
}catch(error){
} catch (error) {
console.error("Registration failed:", error);
}
}
function handleErrors(errors: any, elements: HTMLFormControlsCollection){
if(errors.consent){
function handleErrors(errors: any, elements: HTMLFormControlsCollection) {
if (errors.consent) {
showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
}else if(errors.password){
} else if (errors.password) {
showError(
elements[3] as HTMLElement,
I18n.getTranslation("register.passwordError",errors.password._errors[0].message)
elements[3] as HTMLElement,
I18n.getTranslation("register.passwordError", errors.password._errors[0].message),
);
}else if(errors.username){
} else if (errors.username) {
showError(
elements[2] as HTMLElement,
I18n.getTranslation("register.usernameError",errors.username._errors[0].message)
elements[2] as HTMLElement,
I18n.getTranslation("register.usernameError", errors.username._errors[0].message),
);
}else if(errors.email){
} else if (errors.email) {
showError(
elements[1] as HTMLElement,
I18n.getTranslation("register.emailError",errors.email._errors[0].message)
elements[1] as HTMLElement,
I18n.getTranslation("register.emailError", errors.email._errors[0].message),
);
}else if(errors.date_of_birth){
} else if (errors.date_of_birth) {
showError(
elements[5] as HTMLElement,
I18n.getTranslation("register.DOBError",errors.date_of_birth._errors[0].message)
elements[5] as HTMLElement,
I18n.getTranslation("register.DOBError", errors.date_of_birth._errors[0].message),
);
}else{
} else {
(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!;
let errorElement = parent.getElementsByClassName(
"suberror"
)[0] as HTMLElement;
if(!errorElement){
let errorElement = parent.getElementsByClassName("suberror")[0] as HTMLElement;
if (!errorElement) {
const div = document.createElement("div");
div.classList.add("suberror", "suberrora");
parent.append(div);
errorElement = div;
}else{
} else {
errorElement.classList.remove("suberror");
setTimeout(()=>{
setTimeout(() => {
errorElement.classList.add("suberror");
}, 100);
}
errorElement.textContent = message;
}
async function tosLogic(){
async function tosLogic() {
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
const urlstr=apiurl.toString();
const urlstr = apiurl.toString();
const response = await fetch(urlstr + (urlstr.endsWith("/") ? "" : "/") + "ping");
const data = await response.json();
const tosPage = data.instance.tosPage;
if(tosPage){
const box=document.getElementById("TOSbox");
if(!box) return;
box.innerHTML ="";
box.append(new MarkDown(I18n.getTranslation("register.agreeTOS",tosPage)).makeHTML());
}else{
document.getElementById("TOSbox")!.textContent =I18n.getTranslation("register.noTOS");
if (tosPage) {
const box = document.getElementById("TOSbox");
if (!box) return;
box.innerHTML = "";
box.append(new MarkDown(I18n.getTranslation("register.agreeTOS", tosPage)).makeHTML());
} else {
document.getElementById("TOSbox")!.textContent = I18n.getTranslation("register.noTOS");
}
console.log(tosPage);
}

View file

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

View file

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

@ -73,7 +73,7 @@
--secondary-bg: #9397bd;
--secondary-hover: #9ea5cc;
--servers-bg: #7a7aaa;
--channels-bg: #babdd2;
--channel-selected: #9c9fbf;
@ -93,7 +93,7 @@
.Dark-Accent-theme {
color-scheme: dark;
--primary-bg: color-mix(in srgb, #3f3f3f 65%, var(--accent-color));
--primary-hover: color-mix(in srgb, #373737 68%, var(--accent-color));
--primary-text: #ebebeb;
@ -112,7 +112,7 @@
--spoiler: color-mix(in srgb, #101010 72%, var(--accent-color));
--link: color-mix(in srgb, #99ccff 75%, var(--accent-color));
--black: color-mix(in srgb, #000000 90%, var(--accent-color));
--icon: color-mix(in srgb, #ffffff, var(--accent-color));
--dock-bg: color-mix(in srgb, #171717 68%, var(--accent-color));
@ -173,4 +173,4 @@ body {
--settings-panel-hover: color-mix(in srgb, var(--settings-panel-selected), transparent);
--loading-bg: var(--secondary-bg);
--loading-text: var(--secondary-text);
}
}

View file

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

View file

@ -1,88 +1,88 @@
class BinRead{
private i = 0;
private view:DataView;
constructor(buffer:ArrayBuffer){
this.view=new DataView(buffer, 0)
}
read16(){
const int = this.view.getUint16(this.i);
this.i += 2;
return int;
}
read8(){
const int = this.view.getUint8(this.i);
this.i += 1;
return int;
}
readString8(){
return this.readStringNo(this.read8());
}
readString16(){
return this.readStringNo(this.read16());
}
readFloat32(){
const float = this.view.getFloat32(this.i);
this.i += 4;
return float;
}
readStringNo(length: number){
const array = new Uint8Array(length);
for(let i = 0; i < length; i++){
array[i] = this.read8();
}
//console.log(array);
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
}
class BinRead {
private i = 0;
private view: DataView;
constructor(buffer: ArrayBuffer) {
this.view = new DataView(buffer, 0);
}
read16() {
const int = this.view.getUint16(this.i);
this.i += 2;
return int;
}
read8() {
const int = this.view.getUint8(this.i);
this.i += 1;
return int;
}
readString8() {
return this.readStringNo(this.read8());
}
readString16() {
return this.readStringNo(this.read16());
}
readFloat32() {
const float = this.view.getFloat32(this.i);
this.i += 4;
return float;
}
readStringNo(length: number) {
const array = new Uint8Array(length);
for (let i = 0; i < length; i++) {
array[i] = this.read8();
}
//console.log(array);
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
}
}
class BinWrite{
private view: DataView;
private buffer:ArrayBuffer;
private i=0;
constructor(maxSize:number=2**26){
this.buffer=new ArrayBuffer(maxSize);
this.view=new DataView(this.buffer, 0);
}
write32Float(numb:number){
this.view.setFloat32(this.i,numb);
this.i+=4;
}
write16(numb:number){
this.view.setUint16(this.i,numb);
this.i+=2;
}
write8(numb:number){
this.view.setUint8(this.i,numb);
this.i+=1;
}
writeString8(str:string){
const encode=new TextEncoder().encode(str);
this.write8(encode.length);
for(const thing of encode){
this.write8(thing);
}
}
writeString16(str:string){
const encode=new TextEncoder().encode(str);
this.write16(encode.length);
for(const thing of encode){
this.write8(thing);
}
}
writeStringNo(str:string){
const encode=new TextEncoder().encode(str);
for(const thing of encode){
this.write8(thing);
}
}
getBuffer(){
const buf=new ArrayBuffer(this.i);
const ar1=new Uint8Array(buf);
const ar2=new Uint8Array(this.buffer);
for(let i in ar1){
ar1[+i]=ar2[+i];
}
return buf;
}
class BinWrite {
private view: DataView;
private buffer: ArrayBuffer;
private i = 0;
constructor(maxSize: number = 2 ** 26) {
this.buffer = new ArrayBuffer(maxSize);
this.view = new DataView(this.buffer, 0);
}
write32Float(numb: number) {
this.view.setFloat32(this.i, numb);
this.i += 4;
}
write16(numb: number) {
this.view.setUint16(this.i, numb);
this.i += 2;
}
write8(numb: number) {
this.view.setUint8(this.i, numb);
this.i += 1;
}
writeString8(str: string) {
const encode = new TextEncoder().encode(str);
this.write8(encode.length);
for (const thing of encode) {
this.write8(thing);
}
}
writeString16(str: string) {
const encode = new TextEncoder().encode(str);
this.write16(encode.length);
for (const thing of encode) {
this.write8(thing);
}
}
writeStringNo(str: string) {
const encode = new TextEncoder().encode(str);
for (const thing of encode) {
this.write8(thing);
}
}
getBuffer() {
const buf = new ArrayBuffer(this.i);
const ar1 = new Uint8Array(buf);
const ar2 = new Uint8Array(this.buffer);
for (let i in ar1) {
ar1[+i] = ar2[+i];
}
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();
export function setTheme() {
let name = localStorage.getItem("theme");
if (!name) {
localStorage.setItem("theme", "Dark");
name = "Dark";
}
document.body.className = name + "-theme";
export function setTheme() {
let name = localStorage.getItem("theme");
if (!name) {
localStorage.setItem("theme", "Dark");
name = "Dark";
}
document.body.className = name + "-theme";
}
export function getBulkUsers() {
const json = getBulkInfo();
for (const thing in json.users) {
json.users[thing] = new Specialuser(json.users[thing]);
}
return json;
const json = getBulkInfo();
for (const thing in json.users) {
json.users[thing] = new Specialuser(json.users[thing]);
}
return json;
}
export function getBulkInfo() {
return JSON.parse(localStorage.getItem("userinfos") as string);
return JSON.parse(localStorage.getItem("userinfos") as string);
}
export function setDefaults() {
let userinfos = getBulkInfo();
if (!userinfos) {
localStorage.setItem(
"userinfos",
JSON.stringify({
currentuser: null,
users: {},
preferences: {
theme: "Dark",
notifications: false,
notisound: "three",
},
})
);
userinfos = getBulkInfo();
}
if (userinfos.users === undefined) {
userinfos.users = {};
}
if (userinfos.accent_color === undefined) {
userinfos.accent_color = "#3096f7";
}
document.documentElement.style.setProperty(
"--accent-color",
userinfos.accent_color
);
if (userinfos.preferences === undefined) {
userinfos.preferences = {
theme: "Dark",
notifications: false,
notisound: "three",
};
}
if (userinfos.preferences && userinfos.preferences.notisound === undefined) {
console.warn("uhoh");
userinfos.preferences.notisound = "three";
}
localStorage.setItem("userinfos", JSON.stringify(userinfos));
let userinfos = getBulkInfo();
if (!userinfos) {
localStorage.setItem(
"userinfos",
JSON.stringify({
currentuser: null,
users: {},
preferences: {
theme: "Dark",
notifications: false,
notisound: "three",
},
}),
);
userinfos = getBulkInfo();
}
if (userinfos.users === undefined) {
userinfos.users = {};
}
if (userinfos.accent_color === undefined) {
userinfos.accent_color = "#3096f7";
}
document.documentElement.style.setProperty("--accent-color", userinfos.accent_color);
if (userinfos.preferences === undefined) {
userinfos.preferences = {
theme: "Dark",
notifications: false,
notisound: "three",
};
}
if (userinfos.preferences && userinfos.preferences.notisound === undefined) {
console.warn("uhoh");
userinfos.preferences.notisound = "three";
}
localStorage.setItem("userinfos", JSON.stringify(userinfos));
}
setDefaults();
export class Specialuser {
serverurls: {
api: string;
cdn: string;
gateway: string;
wellknown: string;
login: string;
};
email: string;
token: string;
loggedin;
json;
constructor(json: any) {
if (json instanceof Specialuser) {
console.error("specialuser can't construct from another specialuser");
}
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;
serverurls: {
api: string;
cdn: string;
gateway: string;
login?: string;
wellknown: string;
login: string;
};
}[]
| null;
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;
cdn: string;
gateway: string;
login?: string;
};
}[]
| null;
const datalist = document.getElementById("instances");
console.warn(datalist);
const instancefetch=fetch("/instances.json")
.then(res=>res.json())
.then(
(json: {
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;
}
}[]
)=>{
instances = json;
if(datalist){
console.warn(json);
const instancein = document.getElementById("instancein") as HTMLInputElement;
if(instancein && instancein.value === ""){
instancein.value = json[0].name;
}
for(const instance of json){
if(instance.display === false){
continue;
}
const option = document.createElement("option");
option.disabled = !instance.online;
option.value = instance.name;
if(instance.url){
stringURLMap.set(option.value, instance.url);
if(instance.urls){
stringURLsMap.set(instance.url, instance.urls);
}
}else if(instance.urls){
stringURLsMap.set(option.value, instance.urls);
}else{
option.disabled = true;
}
if(instance.description){
option.label = instance.description;
}else{
option.label = instance.name;
}
datalist.append(option);
}
checkInstance("");
}
}
);
const instancefetch = fetch("/instances.json")
.then((res) => res.json())
.then(
(
json: {
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;
};
}[],
) => {
instances = json;
if (datalist) {
console.warn(json);
const instancein = document.getElementById("instancein") as HTMLInputElement;
if (instancein && instancein.value === "") {
instancein.value = json[0].name;
}
for (const instance of json) {
if (instance.display === false) {
continue;
}
const option = document.createElement("option");
option.disabled = !instance.online;
option.value = instance.name;
if (instance.url) {
stringURLMap.set(option.value, instance.url);
if (instance.urls) {
stringURLsMap.set(instance.url, instance.urls);
}
} else if (instance.urls) {
stringURLsMap.set(option.value, instance.urls);
} else {
option.disabled = true;
}
if (instance.description) {
option.label = instance.description;
} else {
option.label = instance.name;
}
datalist.append(option);
}
checkInstance("");
}
},
);
const stringURLMap = new Map<string, string>();
const stringURLsMap = new Map<
string,
{
wellknown: string;
api: string;
cdn: string;
gateway: string;
login?: string;
wellknown: string;
api: string;
cdn: string;
gateway: string;
login?: string;
}
>();
>();
export async function getapiurls(str: string): Promise<
{
api: string;
cdn: string;
gateway: string;
wellknown: string;
login: string;
}
| {
api: string;
cdn: string;
gateway: string;
wellknown: string;
login: string;
}
| false
>{
function appendApi(str:string){
return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api");
> {
function appendApi(str: string) {
return str.includes("api") ? "" : str.endsWith("/") ? "api" : "/api";
}
if(!URL.canParse(str)){
if (!URL.canParse(str)) {
const val = stringURLMap.get(str);
if(stringURLMap.size===0){
await new Promise<void>(res=>{
setInterval(()=>{
if(stringURLMap.size!==0){
if (stringURLMap.size === 0) {
await new Promise<void>((res) => {
setInterval(() => {
if (stringURLMap.size !== 0) {
res();
}
},100);
}, 100);
});
}
if(val){
if (val) {
str = val;
}else{
} else {
const val = stringURLsMap.get(str);
if(val){
const responce = await fetch(
val.api + (val.api.endsWith("/") ? "" : "/") + "ping"
);
if(responce.ok){
if(val.login){
if (val) {
const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
if (responce.ok) {
if (val.login) {
return val as {
wellknown: string;
api: string;
@ -278,7 +261,7 @@ export async function getapiurls(str: string): Promise<
gateway: string;
login: string;
};
}else{
} else {
val.login = val.api;
return val as {
wellknown: string;
@ -292,43 +275,38 @@ export async function getapiurls(str: string): Promise<
}
}
}
if(str.at(-1) !== "/"){
if (str.at(-1) !== "/") {
str += "/";
}
let api: string;
try{
const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json()
);
try {
const info = await fetch(`${str}.well-known/spacebar`).then((x) => x.json());
api = info.api;
}catch{
api=str;
} catch {
api = str;
}
if(!URL.canParse(api)){
if (!URL.canParse(api)) {
return false;
}
const url = new URL(api);
try{
try {
const info = await fetch(
`${api}${
url.pathname.includes("api") ? "" : "api"
}/policies/instance/domains`
).then(x=>x.json());
`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
).then((x) => x.json());
const apiurl = new URL(info.apiEndpoint);
return{
api: info.apiEndpoint+appendApi(apiurl.pathname),
return {
api: info.apiEndpoint + appendApi(apiurl.pathname),
gateway: info.gateway,
cdn: info.cdn,
wellknown: str,
login: info.apiEndpoint+appendApi(apiurl.pathname),
login: info.apiEndpoint + appendApi(apiurl.pathname),
};
}catch{
} catch {
const val = stringURLsMap.get(str);
if(val){
const responce = await fetch(
val.api + (val.api.endsWith("/") ? "" : "/") + "ping"
);
if(responce.ok){
if(val.login){
if (val) {
const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
if (responce.ok) {
if (val.login) {
return val as {
wellknown: string;
api: string;
@ -336,7 +314,7 @@ export async function getapiurls(str: string): Promise<
gateway: string;
login: string;
};
}else{
} else {
val.login = val.api;
return val as {
wellknown: string;
@ -351,10 +329,10 @@ export async function getapiurls(str: string): Promise<
return false;
}
}
export async function checkInstance(instance: string){
export async function checkInstance(instance: string) {
await instancefetch;
const verify = document.getElementById("verify");
try{
try {
verify!.textContent = I18n.getTranslation("login.checking");
const instanceValue = instance;
const instanceinfo = (await getapiurls(instanceValue)) as {
@ -365,72 +343,74 @@ export async function checkInstance(instance: string){
login: string;
value: string;
};
if(instanceinfo){
if (instanceinfo) {
instanceinfo.value = instanceValue;
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
verify!.textContent = I18n.getTranslation("login.allGood");
// @ts-ignore
if(checkInstance.alt){
// @ts-ignore
if (checkInstance.alt) {
// @ts-ignore
checkInstance.alt();
}
setTimeout((_: any)=>{
setTimeout((_: any) => {
console.log(verify!.textContent);
verify!.textContent = "";
verify!.textContent = "";
}, 3000);
}else{
} else {
verify!.textContent = I18n.getTranslation("login.invalid");
}
}catch{
} catch {
console.log("catch");
verify!.textContent = I18n.getTranslation("login.invalid");
}
}
export function getInstances(){
export function getInstances() {
return instances;
}
export class SW{
static worker:undefined|ServiceWorker;
static setMode(mode:"false"|"offlineOnly"|"true"){
localStorage.setItem("SWMode",mode);
if(this.worker){
this.worker.postMessage({data:mode,code:"setMode"});
export class SW {
static worker: undefined | ServiceWorker;
static setMode(mode: "false" | "offlineOnly" | "true") {
localStorage.setItem("SWMode", mode);
if (this.worker) {
this.worker.postMessage({data: mode, code: "setMode"});
}
}
static checkUpdate(){
if(this.worker){
this.worker.postMessage({code:"CheckUpdate"});
static checkUpdate() {
if (this.worker) {
this.worker.postMessage({code: "CheckUpdate"});
}
}
static forceClear(){
if(this.worker){
this.worker.postMessage({code:"ForceClear"});
static forceClear() {
if (this.worker) {
this.worker.postMessage({code: "ForceClear"});
}
}
}
if ("serviceWorker" in navigator){
navigator.serviceWorker.register("/service.js", {
scope: "/",
}).then((registration) => {
let serviceWorker:ServiceWorker|undefined;
if (registration.installing) {
serviceWorker = registration.installing;
console.log("installing");
} else if (registration.waiting) {
serviceWorker = registration.waiting;
console.log("waiting");
} else if (registration.active) {
serviceWorker = registration.active;
console.log("active");
}
SW.worker=serviceWorker;
SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true");
if (serviceWorker) {
console.log(serviceWorker.state);
serviceWorker.addEventListener("statechange", (_) => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/service.js", {
scope: "/",
})
.then((registration) => {
let serviceWorker: ServiceWorker | undefined;
if (registration.installing) {
serviceWorker = registration.installing;
console.log("installing");
} else if (registration.waiting) {
serviceWorker = registration.waiting;
console.log("waiting");
} else if (registration.active) {
serviceWorker = registration.active;
console.log("active");
}
SW.worker = serviceWorker;
SW.setMode(localStorage.getItem("SWMode") as "false" | "offlineOnly" | "true");
if (serviceWorker) {
console.log(serviceWorker.state);
});
}
})
serviceWorker.addEventListener("statechange", (_) => {
console.log(serviceWorker.state);
});
}
});
}

File diff suppressed because it is too large Load diff