lint and merge

This commit is contained in:
MathMan05 2024-09-19 12:49:50 -05:00
parent 49f2234e81
commit e12b99c38b
34 changed files with 10323 additions and 10330 deletions

View file

@ -267,8 +267,8 @@ module.exports = [
parser: tsParser,
globals: global,
},
files: ["webpage/*.ts"],
ignores: ["!*.js", "!*.ts"],
files: ["src/*.ts","src/**/*.ts",],
ignores: ["dist/", "node_modules/"],
plugins: {
unicorn,
sonarjs,

View file

@ -37,6 +37,6 @@
"gulp-copy": "^5.0.0",
"gulp-typescript": "^6.0.0-alpha.1",
"typescript": "^5.6.2",
"typescript-eslint": "^7.18.0"
"typescript-eslint": "^8.6.0"
}
}

View file

@ -1,13 +1,13 @@
#!/usr/bin/env node
import compression from "compression";
import express, { Request, Response } from "express";
import fs from "node:fs";
import fetch from "node-fetch";
import path from "path";
import { observe, uptime } from "./stats.js";
import { getApiUrls, inviteResponse } from "./utils.js";
import { fileURLToPath } from "url";
import compression from"compression";
import express, { Request, Response }from"express";
import fs from"node:fs";
import fetch from"node-fetch";
import path from"node:path";
import{ observe, uptime }from"./stats.js";
import{ getApiUrls, inviteResponse }from"./utils.js";
import{ fileURLToPath }from"node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -18,103 +18,103 @@ name: string;
}
const app = express();
import instances from "./webpage/instances.json" with { type: "json" };
import instances from"./webpage/instances.json" with { type: "json" };
const instanceNames = new Map<string, Instance>();
for (const instance of instances) {
for(const instance of instances){
instanceNames.set(instance.name, instance);
}
}
app.use(compression());
app.use(compression());
async function updateInstances(): Promise<void> {
try {
async function updateInstances(): Promise<void>{
try{
const response = await fetch(
"https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"
"https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"
);
const json = (await response.json()) as Instance[];
for (const instance of json) {
if (!instanceNames.has(instance.name)) {
instances.push(instance as any);
} else {
const existingInstance = instanceNames.get(instance.name);
if (existingInstance) {
for (const key of Object.keys(instance)) {
if (!existingInstance[key]) {
existingInstance[key] = instance[key];
}
}
}
}
for(const instance of json){
if(!instanceNames.has(instance.name)){
instances.push(instance as any);
}else{
const existingInstance = instanceNames.get(instance.name);
if(existingInstance){
for(const key of Object.keys(instance)){
if(!existingInstance[key]){
existingInstance[key] = instance[key];
}
}
}
}
}
observe(instances);
} catch (error) {
}catch(error){
console.error("Error updating instances:", error);
}
}
}
}
updateInstances();
updateInstances();
app.use("/getupdates", (_req: Request, res: Response) => {
try {
app.use("/getupdates", (_req: Request, res: Response)=>{
try{
const stats = fs.statSync(path.join(__dirname, "webpage"));
res.send(stats.mtimeMs.toString());
} catch (error) {
}catch(error){
console.error("Error getting updates:", error);
res.status(500).send("Error getting updates");
}
});
}
});
app.use("/services/oembed", (req: Request, res: Response) => {
inviteResponse(req, res);
});
app.use("/services/oembed", (req: Request, res: Response)=>{
inviteResponse(req, res);
});
app.use("/uptime", (req: Request, res: Response) => {
const instanceUptime = uptime[req.query.name as string];
res.send(instanceUptime);
});
app.use("/uptime", (req: Request, res: Response)=>{
const instanceUptime = uptime[req.query.name as string];
res.send(instanceUptime);
});
app.use("/", async (req: Request, res: Response) => {
const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`;
const ref = host + req.originalUrl;
app.use("/", async (req: Request, res: Response)=>{
const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`;
const ref = host + req.originalUrl;
if (host && ref) {
if(host && ref){
const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`;
res.set(
"Link",
`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`
"Link",
`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`
);
}
}
if (req.path === "/") {
if(req.path === "/"){
res.sendFile(path.join(__dirname, "webpage", "home.html"));
return;
}
}
if (req.path.startsWith("/instances.json")) {
if(req.path.startsWith("/instances.json")){
res.json(instances);
return;
}
}
if (req.path.startsWith("/invite/")) {
if(req.path.startsWith("/invite/")){
res.sendFile(path.join(__dirname, "webpage", "invite.html"));
return;
}
}
const filePath = path.join(__dirname, "webpage", req.path);
if (fs.existsSync(filePath)) {
const filePath = path.join(__dirname, "webpage", req.path);
if(fs.existsSync(filePath)){
res.sendFile(filePath);
} else if (fs.existsSync(`${filePath}.html`)) {
}else if(fs.existsSync(`${filePath}.html`)){
res.sendFile(`${filePath}.html`);
} else {
}else{
res.sendFile(path.join(__dirname, "webpage", "index.html"));
}
});
}
});
const PORT = process.env.PORT || Number(process.argv[2]) || 8080;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
const PORT = process.env.PORT || Number(process.argv[2]) || 8080;
app.listen(PORT, ()=>{
console.log(`Server running on port ${PORT}`);
});
export { getApiUrls };
export{ getApiUrls };

View file

@ -1,8 +1,8 @@
import fs from "node:fs";
import path from "path";
import fetch from "node-fetch";
import { getApiUrls } from "./utils.js";
import { fileURLToPath } from "url";
import fs from"node:fs";
import path from"node:path";
import fetch from"node-fetch";
import{ getApiUrls }from"./utils.js";
import{ fileURLToPath }from"node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -28,228 +28,227 @@ interface Instance {
};
}
let uptimeObject: UptimeObject = loadUptimeObject();
export { uptimeObject as uptime };
const uptimeObject: UptimeObject = loadUptimeObject();
export{ uptimeObject as uptime };
function loadUptimeObject(): UptimeObject {
const filePath = path.join(__dirname, "..", "uptime.json");
if (fs.existsSync(filePath)) {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (error) {
console.error("Error reading uptime.json:", error);
return {};
}
}
return {};
function loadUptimeObject(): UptimeObject{
const filePath = path.join(__dirname, "..", "uptime.json");
if(fs.existsSync(filePath)){
try{
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}catch(error){
console.error("Error reading uptime.json:", error);
return{};
}
}
return{};
}
function saveUptimeObject(): void {
fs.writeFile(
path.join(__dirname, "..", "uptime.json"),
JSON.stringify(uptimeObject),
(error) => {
if (error) {
console.error("Error saving uptime.json:", error);
}
}
);
function saveUptimeObject(): void{
fs.writeFile(
path.join(__dirname, "..", "uptime.json"),
JSON.stringify(uptimeObject),
error=>{
if(error){
console.error("Error saving uptime.json:", error);
}
}
);
}
function removeUndefinedKey(): void {
if (uptimeObject.undefined) {
delete uptimeObject.undefined;
saveUptimeObject();
}
function removeUndefinedKey(): void{
if(uptimeObject.undefined){
delete uptimeObject.undefined;
saveUptimeObject();
}
}
removeUndefinedKey();
export async function observe(instances: Instance[]): Promise<void> {
const activeInstances = new Set<string>();
const instancePromises = instances.map((instance) =>
resolveInstance(instance, activeInstances)
);
await Promise.allSettled(instancePromises);
updateInactiveInstances(activeInstances);
export async function observe(instances: Instance[]): Promise<void>{
const activeInstances = new Set<string>();
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 {
calcStats(instance);
const api = await getApiUrl(instance);
if (!api) {
handleUnresolvedApi(instance);
return;
}
activeInstances.add(instance.name);
await checkHealth(instance, api); // Ensure health is checked immediately
scheduleHealthCheck(instance, api);
} catch (error) {
console.error("Error resolving instance:", error);
}
instance: Instance,
activeInstances: Set<string>
): Promise<void>{
try{
calcStats(instance);
const api = await getApiUrl(instance);
if(!api){
handleUnresolvedApi(instance);
return;
}
activeInstances.add(instance.name);
await checkHealth(instance, api); // Ensure health is checked immediately
scheduleHealthCheck(instance, api);
}catch(error){
console.error("Error resolving instance:", error);
}
}
async function getApiUrl(instance: Instance): Promise<string | null> {
if (instance.urls) {
return instance.urls.api;
}
if (instance.url) {
const urls = await getApiUrls(instance.url);
return urls ? urls.api : null;
}
return null;
async function getApiUrl(instance: Instance): Promise<string | null>{
if(instance.urls){
return instance.urls.api;
}
if(instance.url){
const urls = await getApiUrls(instance.url);
return urls ? urls.api : null;
}
return null;
}
function handleUnresolvedApi(instance: Instance): void {
setStatus(instance, false);
console.warn(`${instance.name} does not resolve api URL`, instance);
setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30);
function handleUnresolvedApi(instance: Instance): void{
setStatus(instance, false);
console.warn(`${instance.name} does not resolve api URL`, instance);
setTimeout(()=>resolveInstance(instance, new Set()), 1000 * 60 * 30);
}
function scheduleHealthCheck(instance: Instance, api: string): void {
const checkInterval = 1000 * 60 * 30;
const initialDelay = Math.random() * 1000 * 60 * 10;
setTimeout(() => {
checkHealth(instance, api);
setInterval(() => checkHealth(instance, api), checkInterval);
}, initialDelay);
function scheduleHealthCheck(instance: Instance, api: string): void{
const checkInterval = 1000 * 60 * 30;
const initialDelay = Math.random() * 1000 * 60 * 10;
setTimeout(()=>{
checkHealth(instance, api);
setInterval(()=>checkHealth(instance, api), checkInterval);
}, initialDelay);
}
async function checkHealth(
instance: Instance,
api: string,
tries = 0
): Promise<void> {
try {
const response = await fetch(`${api}/ping`, { method: "HEAD" });
console.log(`Checking health for ${instance.name}: ${response.status}`);
if (response.ok || tries > 3) {
console.log(`Setting status for ${instance.name} to ${response.ok}`);
setStatus(instance, response.ok);
} else {
retryHealthCheck(instance, api, tries);
}
} catch (error) {
console.error(`Error checking health for ${instance.name}:`, error);
if (tries > 3) {
setStatus(instance, false);
} else {
retryHealthCheck(instance, api, tries);
}
}
instance: Instance,
api: string,
tries = 0
): Promise<void>{
try{
const response = await fetch(`${api}/ping`, { method: "HEAD" });
console.log(`Checking health for ${instance.name}: ${response.status}`);
if(response.ok || tries > 3){
console.log(`Setting status for ${instance.name} to ${response.ok}`);
setStatus(instance, response.ok);
}else{
retryHealthCheck(instance, api, tries);
}
}catch(error){
console.error(`Error checking health for ${instance.name}:`, error);
if(tries > 3){
setStatus(instance, false);
}else{
retryHealthCheck(instance, api, tries);
}
}
}
function retryHealthCheck(
instance: Instance,
api: string,
tries: number
): void {
setTimeout(() => checkHealth(instance, api, tries + 1), 30000);
instance: Instance,
api: string,
tries: number
): void{
setTimeout(()=>checkHealth(instance, api, tries + 1), 30000);
}
function updateInactiveInstances(activeInstances: Set<string>): void {
for (const key of Object.keys(uptimeObject)) {
if (!activeInstances.has(key)) {
setStatus(key, false);
}
}
function updateInactiveInstances(activeInstances: Set<string>): void{
for(const key of Object.keys(uptimeObject)){
if(!activeInstances.has(key)){
setStatus(key, false);
}
}
}
function calcStats(instance: Instance): void {
const obj = uptimeObject[instance.name];
if (!obj) return;
function calcStats(instance: Instance): void{
const obj = uptimeObject[instance.name];
if(!obj)return;
const now = Date.now();
const day = now - 1000 * 60 * 60 * 24;
const week = now - 1000 * 60 * 60 * 24 * 7;
const now = Date.now();
const day = now - 1000 * 60 * 60 * 24;
const week = now - 1000 * 60 * 60 * 24 * 7;
let totalTimePassed = 0;
let alltime = 0;
let daytime = 0;
let weektime = 0;
let online = false;
let totalTimePassed = 0;
let alltime = 0;
let daytime = 0;
let weektime = 0;
let online = false;
for (let i = 0; i < obj.length; i++) {
const entry = obj[i];
online = entry.online;
const stamp = entry.time;
const nextStamp = obj[i + 1]?.time || now;
const timePassed = nextStamp - stamp;
for(let i = 0; i < obj.length; i++){
const entry = obj[i];
online = entry.online;
const stamp = entry.time;
const nextStamp = obj[i + 1]?.time || now;
const timePassed = nextStamp - stamp;
totalTimePassed += timePassed;
alltime += Number(online) * timePassed;
totalTimePassed += timePassed;
alltime += Number(online) * timePassed;
if (stamp + timePassed > week) {
const weekTimePassed = Math.min(timePassed, nextStamp - week);
weektime += Number(online) * weekTimePassed;
if(stamp + timePassed > week){
const weekTimePassed = Math.min(timePassed, nextStamp - week);
weektime += Number(online) * weekTimePassed;
if (stamp + timePassed > day) {
const dayTimePassed = Math.min(weekTimePassed, nextStamp - day);
daytime += Number(online) * dayTimePassed;
}
}
}
if(stamp + timePassed > day){
const dayTimePassed = Math.min(weekTimePassed, nextStamp - day);
daytime += Number(online) * dayTimePassed;
}
}
}
instance.online = online;
instance.uptime = calculateUptimeStats(
totalTimePassed,
alltime,
daytime,
weektime,
online
);
instance.online = online;
instance.uptime = calculateUptimeStats(
totalTimePassed,
alltime,
daytime,
weektime,
online
);
}
function calculateUptimeStats(
totalTimePassed: number,
alltime: number,
daytime: number,
weektime: number,
online: boolean
): { daytime: number; weektime: number; alltime: number } {
const dayInMs = 1000 * 60 * 60 * 24;
const weekInMs = dayInMs * 7;
totalTimePassed: number,
alltime: number,
daytime: number,
weektime: number,
online: boolean
): { daytime: number; weektime: number; alltime: number }{
const dayInMs = 1000 * 60 * 60 * 24;
const weekInMs = dayInMs * 7;
alltime /= totalTimePassed;
alltime /= totalTimePassed;
if (totalTimePassed > dayInMs) {
daytime = daytime || (online ? dayInMs : 0);
daytime /= dayInMs;
if(totalTimePassed > dayInMs){
daytime = daytime || (online ? dayInMs : 0);
daytime /= dayInMs;
if (totalTimePassed > weekInMs) {
weektime = weektime || (online ? weekInMs : 0);
weektime /= weekInMs;
} else {
weektime = alltime;
}
} else {
weektime = alltime;
daytime = alltime;
}
if(totalTimePassed > weekInMs){
weektime = weektime || (online ? weekInMs : 0);
weektime /= weekInMs;
}else{
weektime = alltime;
}
}else{
weektime = alltime;
daytime = alltime;
}
return { daytime, weektime, alltime };
return{ daytime, weektime, alltime };
}
function setStatus(instance: string | Instance, status: boolean): void {
const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject[name];
function setStatus(instance: string | Instance, status: boolean): void{
const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject[name];
if (!obj) {
obj = [];
uptimeObject[name] = obj;
}
if(!obj){
obj = [];
uptimeObject[name] = obj;
}
const lastEntry = obj.at(-1);
if (!lastEntry || lastEntry.online !== status) {
obj.push({ time: Date.now(), online: status });
saveUptimeObject();
const lastEntry = obj.at(-1);
if(!lastEntry || lastEntry.online !== status){
obj.push({ time: Date.now(), online: status });
saveUptimeObject();
if (typeof instance !== "string") {
calcStats(instance);
}
}
if(typeof instance !== "string"){
calcStats(instance);
}
}
}

View file

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

View file

@ -1,164 +1,164 @@
import { getBulkInfo } from "./login.js";
import{ getBulkInfo }from"./login.js";
class Voice {
audioCtx: AudioContext;
info: { wave: string | Function; freq: number };
playing: boolean;
myArrayBuffer: AudioBuffer;
gainNode: GainNode;
buffer: Float32Array;
source: AudioBufferSourceNode;
constructor(wave: string | Function, freq: number, volume = 1) {
this.audioCtx = new window.AudioContext();
this.info = { wave, freq };
this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer(
1,
this.audioCtx.sampleRate,
this.audioCtx.sampleRate
);
this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume;
this.gainNode.connect(this.audioCtx.destination);
this.buffer = this.myArrayBuffer.getChannelData(0);
this.source = this.audioCtx.createBufferSource();
this.source.buffer = this.myArrayBuffer;
this.source.loop = true;
this.source.start();
this.updateWave();
class Voice{
audioCtx: AudioContext;
info: { wave: string | Function; freq: number };
playing: boolean;
myArrayBuffer: AudioBuffer;
gainNode: GainNode;
buffer: Float32Array;
source: AudioBufferSourceNode;
constructor(wave: string | Function, freq: number, volume = 1){
this.audioCtx = new window.AudioContext();
this.info = { wave, freq };
this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer(
1,
this.audioCtx.sampleRate,
this.audioCtx.sampleRate
);
this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume;
this.gainNode.connect(this.audioCtx.destination);
this.buffer = this.myArrayBuffer.getChannelData(0);
this.source = this.audioCtx.createBufferSource();
this.source.buffer = this.myArrayBuffer;
this.source.loop = true;
this.source.start();
this.updateWave();
}
get wave(): string | Function{
return this.info.wave;
}
get freq(): number{
return this.info.freq;
}
set wave(wave: string | Function){
this.info.wave = wave;
this.updateWave();
}
set freq(freq: number){
this.info.freq = freq;
this.updateWave();
}
updateWave(): void{
const func = this.waveFunction();
for(let i = 0; i < this.buffer.length; i++){
this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq);
}
}
waveFunction(): Function{
if(typeof this.wave === "function"){
return this.wave;
}
switch(this.wave){
case"sin":
return(t: number, freq: number)=>{
return Math.sin(t * Math.PI * 2 * freq);
};
case"triangle":
return(t: number, freq: number)=>{
return Math.abs(((4 * t * freq) % 4) - 2) - 1;
};
case"sawtooth":
return(t: number, freq: number)=>{
return((t * freq) % 1) * 2 - 1;
};
case"square":
return(t: number, freq: number)=>{
return(t * freq) % 2 < 1 ? 1 : -1;
};
case"white":
return(_t: number, _freq: number)=>{
return Math.random() * 2 - 1;
};
case"noise":
return(_t: number, _freq: number)=>{
return 0;
};
}
return new Function();
}
play(): void{
if(this.playing){
return;
}
this.source.connect(this.gainNode);
this.playing = true;
}
stop(): void{
if(this.playing){
this.source.disconnect();
this.playing = false;
}
}
static noises(noise: string): void{
switch(noise){
case"three": {
const voicy = new Voice("sin", 800);
voicy.play();
setTimeout(_=>{
voicy.freq = 1000;
}, 50);
setTimeout(_=>{
voicy.freq = 1300;
}, 100);
setTimeout(_=>{
voicy.stop();
}, 150);
break;
}
case"zip": {
const voicy = new Voice((t: number, freq: number)=>{
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
}, 700);
voicy.play();
setTimeout(_=>{
voicy.stop();
}, 150);
break;
}
case"square": {
const voicy = new Voice("square", 600, 0.4);
voicy.play();
setTimeout(_=>{
voicy.freq = 800;
}, 50);
setTimeout(_=>{
voicy.freq = 1000;
}, 100);
setTimeout(_=>{
voicy.stop();
}, 150);
break;
}
case"beep": {
const voicy = new Voice("sin", 800);
voicy.play();
setTimeout(_=>{
voicy.stop();
}, 50);
setTimeout(_=>{
voicy.play();
}, 100);
setTimeout(_=>{
voicy.stop();
}, 150);
break;
}
}
}
static get sounds(){
return["three", "zip", "square", "beep"];
}
static setNotificationSound(sound: string){
const userinfos = getBulkInfo();
userinfos.preferences.notisound = sound;
localStorage.setItem("userinfos", JSON.stringify(userinfos));
}
static getNotificationSound(){
const userinfos = getBulkInfo();
return userinfos.preferences.notisound;
}
}
get wave(): string | Function {
return this.info.wave;
}
get freq(): number {
return this.info.freq;
}
set wave(wave: string | Function) {
this.info.wave = wave;
this.updateWave();
}
set freq(freq: number) {
this.info.freq = freq;
this.updateWave();
}
updateWave(): void {
const func = this.waveFunction();
for (let i = 0; i < this.buffer.length; i++) {
this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq);
}
}
waveFunction(): Function {
if (typeof this.wave === "function") {
return this.wave;
}
switch (this.wave) {
case "sin":
return (t: number, freq: number) => {
return Math.sin(t * Math.PI * 2 * freq);
};
case "triangle":
return (t: number, freq: number) => {
return Math.abs(((4 * t * freq) % 4) - 2) - 1;
};
case "sawtooth":
return (t: number, freq: number) => {
return ((t * freq) % 1) * 2 - 1;
};
case "square":
return (t: number, freq: number) => {
return (t * freq) % 2 < 1 ? 1 : -1;
};
case "white":
return (_t: number, _freq: number) => {
return Math.random() * 2 - 1;
};
case "noise":
return (_t: number, _freq: number) => {
return 0;
};
}
return new Function();
}
play(): void {
if (this.playing) {
return;
}
this.source.connect(this.gainNode);
this.playing = true;
}
stop(): void {
if (this.playing) {
this.source.disconnect();
this.playing = false;
}
}
static noises(noise: string): void {
switch (noise) {
case "three": {
const voicy = new Voice("sin", 800);
voicy.play();
setTimeout((_) => {
voicy.freq = 1000;
}, 50);
setTimeout((_) => {
voicy.freq = 1300;
}, 100);
setTimeout((_) => {
voicy.stop();
}, 150);
break;
}
case "zip": {
const voicy = new Voice((t: number, freq: number) => {
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
}, 700);
voicy.play();
setTimeout((_) => {
voicy.stop();
}, 150);
break;
}
case "square": {
const voicy = new Voice("square", 600, 0.4);
voicy.play();
setTimeout((_) => {
voicy.freq = 800;
}, 50);
setTimeout((_) => {
voicy.freq = 1000;
}, 100);
setTimeout((_) => {
voicy.stop();
}, 150);
break;
}
case "beep": {
const voicy = new Voice("sin", 800);
voicy.play();
setTimeout((_) => {
voicy.stop();
}, 50);
setTimeout((_) => {
voicy.play();
}, 100);
setTimeout((_) => {
voicy.stop();
}, 150);
break;
}
}
}
static get sounds() {
return ["three", "zip", "square", "beep"];
}
static setNotificationSound(sound: string) {
const userinfos = getBulkInfo();
userinfos.preferences.notisound = sound;
localStorage.setItem("userinfos", JSON.stringify(userinfos));
}
static getNotificationSound() {
const userinfos = getBulkInfo();
return userinfos.preferences.notisound;
}
}
export { Voice };
export{ Voice };

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
class Contextmenu<x, y> {
class Contextmenu<x, y>{
static currentmenu: HTMLElement | "";
name: string;
buttons: [
@ -10,98 +10,98 @@ class Contextmenu<x, y> {
string
][];
div!: HTMLDivElement;
static setup() {
Contextmenu.currentmenu = "";
document.addEventListener("click", (event) => {
if (Contextmenu.currentmenu === "") {
return;
static setup(){
Contextmenu.currentmenu = "";
document.addEventListener("click", event=>{
if(Contextmenu.currentmenu === ""){
return;
}
if(!Contextmenu.currentmenu.contains(event.target as Node)){
Contextmenu.currentmenu.remove();
Contextmenu.currentmenu = "";
}
});
}
if (!Contextmenu.currentmenu.contains(event.target as Node)) {
Contextmenu.currentmenu.remove();
Contextmenu.currentmenu = "";
}
});
}
constructor(name: string) {
this.name = name;
this.buttons = [];
constructor(name: string){
this.name = name;
this.buttons = [];
}
addbutton(
text: string,
onclick: (this: x, arg: y, e: MouseEvent) => void,
img: null | string = null,
shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = (_) => true
) {
this.buttons.push([text, onclick, img, shown, enabled, "button"]);
return {};
text: string,
onclick: (this: x, arg: y, e: MouseEvent) => void,
img: null | string = null,
shown: (this: x, arg: y) => boolean = _=>true,
enabled: (this: x, arg: y) => boolean = _=>true
){
this.buttons.push([text, onclick, img, shown, enabled, "button"]);
return{};
}
addsubmenu(
text: string,
onclick: (this: x, arg: y, e: MouseEvent) => void,
img = null,
shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = (_) => true
) {
this.buttons.push([text, onclick, img, shown, enabled, "submenu"]);
return {};
text: string,
onclick: (this: x, arg: y, e: MouseEvent) => void,
img = null,
shown: (this: x, arg: y) => boolean = _=>true,
enabled: (this: x, arg: y) => boolean = _=>true
){
this.buttons.push([text, onclick, img, shown, enabled, "submenu"]);
return{};
}
private makemenu(x: number, y: number, addinfo: x, other: y) {
const div = document.createElement("div");
div.classList.add("contextmenu", "flexttb");
private makemenu(x: number, y: number, addinfo: x, other: y){
const div = document.createElement("div");
div.classList.add("contextmenu", "flexttb");
let visibleButtons = 0;
for (const thing of this.buttons) {
if (!thing[3].bind(addinfo).call(addinfo, other)) continue;
visibleButtons++;
let visibleButtons = 0;
for(const thing of this.buttons){
if(!thing[3].bind(addinfo).call(addinfo, other))continue;
visibleButtons++;
const intext = document.createElement("button");
intext.disabled = !thing[4].bind(addinfo).call(addinfo, other);
intext.classList.add("contextbutton");
intext.textContent = thing[0];
console.log(thing);
if (thing[5] === "button" || thing[5] === "submenu") {
intext.onclick = thing[1].bind(addinfo, other);
}
const intext = document.createElement("button");
intext.disabled = !thing[4].bind(addinfo).call(addinfo, other);
intext.classList.add("contextbutton");
intext.textContent = thing[0];
console.log(thing);
if(thing[5] === "button" || thing[5] === "submenu"){
intext.onclick = thing[1].bind(addinfo, other);
}
div.appendChild(intext);
}
if (visibleButtons == 0) return;
div.appendChild(intext);
}
if(visibleButtons == 0)return;
if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove();
if(Contextmenu.currentmenu != ""){
Contextmenu.currentmenu.remove();
}
div.style.top = y + "px";
div.style.left = x + "px";
document.body.appendChild(div);
Contextmenu.keepOnScreen(div);
console.log(div);
Contextmenu.currentmenu = div;
return this.div;
}
div.style.top = y + "px";
div.style.left = x + "px";
document.body.appendChild(div);
Contextmenu.keepOnScreen(div);
console.log(div);
Contextmenu.currentmenu = div;
return this.div;
bindContextmenu(obj: HTMLElement, addinfo: x, other: y){
const func = (event: MouseEvent)=>{
event.preventDefault();
event.stopImmediatePropagation();
this.makemenu(event.clientX, event.clientY, addinfo, other);
};
obj.addEventListener("contextmenu", func);
return func;
}
bindContextmenu(obj: HTMLElement, addinfo: x, other: y) {
const func = (event: MouseEvent) => {
event.preventDefault();
event.stopImmediatePropagation();
this.makemenu(event.clientX, event.clientY, addinfo, other);
};
obj.addEventListener("contextmenu", func);
return func;
static keepOnScreen(obj: HTMLElement){
const html = document.documentElement.getBoundingClientRect();
const docheight = html.height;
const docwidth = html.width;
const box = obj.getBoundingClientRect();
console.log(box, docheight, docwidth);
if(box.right > docwidth){
console.log("test");
obj.style.left = docwidth - box.width + "px";
}
if(box.bottom > docheight){
obj.style.top = docheight - box.height + "px";
}
}
static keepOnScreen(obj: HTMLElement) {
const html = document.documentElement.getBoundingClientRect();
const docheight = html.height;
const docwidth = html.width;
const box = obj.getBoundingClientRect();
console.log(box, docheight, docwidth);
if (box.right > docwidth) {
console.log("test");
obj.style.left = docwidth - box.width + "px";
}
if (box.bottom > docheight) {
obj.style.top = docheight - box.height + "px";
}
}
}
Contextmenu.setup();
export { Contextmenu };
}
Contextmenu.setup();
export{ Contextmenu };

View file

@ -19,255 +19,255 @@ string[],
number
]
| ["tabs", [string, dialogjson][]];
class Dialog {
layout: dialogjson;
onclose: Function;
onopen: Function;
html: HTMLDivElement;
background!: HTMLDivElement;
constructor(
layout: dialogjson,
onclose = (_: any) => {},
onopen = (_: any) => {}
) {
this.layout = layout;
this.onclose = onclose;
this.onopen = onopen;
const div = document.createElement("div");
div.appendChild(this.tohtml(layout));
this.html = div;
this.html.classList.add("centeritem");
if (!(layout[0] === "img")) {
this.html.classList.add("nonimagecenter");
}
}
tohtml(array: dialogjson): HTMLElement {
switch (array[0]) {
case "img":
const img = document.createElement("img");
img.src = array[1];
if (array[2] != undefined) {
if (array[2].length === 2) {
img.width = array[2][0];
img.height = array[2][1];
} else if (array[2][0] === "fit") {
img.classList.add("imgfit");
}
}
return img;
case "hdiv":
const hdiv = document.createElement("div");
hdiv.classList.add("flexltr");
class Dialog{
layout: dialogjson;
onclose: Function;
onopen: Function;
html: HTMLDivElement;
background!: HTMLDivElement;
constructor(
layout: dialogjson,
onclose = (_: any)=>{},
onopen = (_: any)=>{}
){
this.layout = layout;
this.onclose = onclose;
this.onopen = onopen;
const div = document.createElement("div");
div.appendChild(this.tohtml(layout));
this.html = div;
this.html.classList.add("centeritem");
if(!(layout[0] === "img")){
this.html.classList.add("nonimagecenter");
}
}
tohtml(array: dialogjson): HTMLElement{
switch(array[0]){
case"img":
const img = document.createElement("img");
img.src = array[1];
if(array[2] != undefined){
if(array[2].length === 2){
img.width = array[2][0];
img.height = array[2][1];
}else if(array[2][0] === "fit"){
img.classList.add("imgfit");
}
}
return img;
case"hdiv":
const hdiv = document.createElement("div");
hdiv.classList.add("flexltr");
for (const thing of array) {
if (thing === "hdiv") {
continue;
}
hdiv.appendChild(this.tohtml(thing));
}
return hdiv;
case "vdiv":
const vdiv = document.createElement("div");
vdiv.classList.add("flexttb");
for (const thing of array) {
if (thing === "vdiv") {
continue;
}
vdiv.appendChild(this.tohtml(thing));
}
return vdiv;
case "checkbox": {
const div = document.createElement("div");
const checkbox = document.createElement("input");
div.appendChild(checkbox);
const label = document.createElement("span");
checkbox.checked = array[2];
label.textContent = array[1];
div.appendChild(label);
checkbox.addEventListener("change", array[3]);
checkbox.type = "checkbox";
return div;
}
case "button": {
const div = document.createElement("div");
const input = document.createElement("button");
for(const thing of array){
if(thing === "hdiv"){
continue;
}
hdiv.appendChild(this.tohtml(thing));
}
return hdiv;
case"vdiv":
const vdiv = document.createElement("div");
vdiv.classList.add("flexttb");
for(const thing of array){
if(thing === "vdiv"){
continue;
}
vdiv.appendChild(this.tohtml(thing));
}
return vdiv;
case"checkbox": {
const div = document.createElement("div");
const checkbox = document.createElement("input");
div.appendChild(checkbox);
const label = document.createElement("span");
checkbox.checked = array[2];
label.textContent = array[1];
div.appendChild(label);
checkbox.addEventListener("change", array[3]);
checkbox.type = "checkbox";
return div;
}
case"button": {
const div = document.createElement("div");
const input = document.createElement("button");
const label = document.createElement("span");
input.textContent = array[2];
label.textContent = array[1];
div.appendChild(label);
div.appendChild(input);
input.addEventListener("click", array[3]);
return div;
}
case "mdbox": {
const div = document.createElement("div");
const input = document.createElement("textarea");
input.value = array[2];
const label = document.createElement("span");
label.textContent = array[1];
input.addEventListener("input", array[3]);
div.appendChild(label);
div.appendChild(document.createElement("br"));
div.appendChild(input);
return div;
}
case "textbox": {
const div = document.createElement("div");
const input = document.createElement("input");
input.value = array[2];
input.type = "text";
const label = document.createElement("span");
label.textContent = array[1];
console.log(array[3]);
input.addEventListener("input", array[3]);
div.appendChild(label);
div.appendChild(input);
return div;
}
case "fileupload": {
const div = document.createElement("div");
const input = document.createElement("input");
input.type = "file";
const label = document.createElement("span");
label.textContent = array[1];
div.appendChild(label);
div.appendChild(input);
input.addEventListener("change", array[2]);
console.log(array);
return div;
}
case "text": {
const span = document.createElement("span");
span.textContent = array[1];
return span;
}
case "title": {
const span = document.createElement("span");
span.classList.add("title");
span.textContent = array[1];
return span;
}
case "radio": {
const div = document.createElement("div");
const fieldset = document.createElement("fieldset");
fieldset.addEventListener("change", () => {
let i = -1;
for (const thing of Array.from(fieldset.children)) {
i++;
if (i === 0) {
continue;
}
const checkbox = thing.children[0].children[0] as HTMLInputElement;
if (checkbox.checked) {
array[3](checkbox.value);
}
}
});
const legend = document.createElement("legend");
legend.textContent = array[1];
fieldset.appendChild(legend);
let i = 0;
for (const thing of array[2]) {
const div = document.createElement("div");
const input = document.createElement("input");
input.classList.add("radio");
input.type = "radio";
input.name = array[1];
input.value = thing;
if (i === array[4]) {
input.checked = true;
}
const label = document.createElement("label");
const label = document.createElement("span");
input.textContent = array[2];
label.textContent = array[1];
div.appendChild(label);
div.appendChild(input);
input.addEventListener("click", array[3]);
return div;
}
case"mdbox": {
const div = document.createElement("div");
const input = document.createElement("textarea");
input.value = array[2];
const label = document.createElement("span");
label.textContent = array[1];
input.addEventListener("input", array[3]);
div.appendChild(label);
div.appendChild(document.createElement("br"));
div.appendChild(input);
return div;
}
case"textbox": {
const div = document.createElement("div");
const input = document.createElement("input");
input.value = array[2];
input.type = "text";
const label = document.createElement("span");
label.textContent = array[1];
console.log(array[3]);
input.addEventListener("input", array[3]);
div.appendChild(label);
div.appendChild(input);
return div;
}
case"fileupload": {
const div = document.createElement("div");
const input = document.createElement("input");
input.type = "file";
const label = document.createElement("span");
label.textContent = array[1];
div.appendChild(label);
div.appendChild(input);
input.addEventListener("change", array[2]);
console.log(array);
return div;
}
case"text": {
const span = document.createElement("span");
span.textContent = array[1];
return span;
}
case"title": {
const span = document.createElement("span");
span.classList.add("title");
span.textContent = array[1];
return span;
}
case"radio": {
const div = document.createElement("div");
const fieldset = document.createElement("fieldset");
fieldset.addEventListener("change", ()=>{
let i = -1;
for(const thing of Array.from(fieldset.children)){
i++;
if(i === 0){
continue;
}
const checkbox = thing.children[0].children[0] as HTMLInputElement;
if(checkbox.checked){
array[3](checkbox.value);
}
}
});
const legend = document.createElement("legend");
legend.textContent = array[1];
fieldset.appendChild(legend);
let i = 0;
for(const thing of array[2]){
const div = document.createElement("div");
const input = document.createElement("input");
input.classList.add("radio");
input.type = "radio";
input.name = array[1];
input.value = thing;
if(i === array[4]){
input.checked = true;
}
const label = document.createElement("label");
label.appendChild(input);
const span = document.createElement("span");
span.textContent = thing;
label.appendChild(span);
div.appendChild(label);
fieldset.appendChild(div);
i++;
}
div.appendChild(fieldset);
return div;
}
case "html":
return array[1];
label.appendChild(input);
const span = document.createElement("span");
span.textContent = thing;
label.appendChild(span);
div.appendChild(label);
fieldset.appendChild(div);
i++;
}
div.appendChild(fieldset);
return div;
}
case"html":
return array[1];
case "select": {
const div = document.createElement("div");
const label = document.createElement("label");
const select = document.createElement("select");
case"select": {
const div = document.createElement("div");
const label = document.createElement("label");
const select = document.createElement("select");
label.textContent = array[1];
div.append(label);
div.appendChild(select);
for (const thing of array[2]) {
const option = document.createElement("option");
option.textContent = thing;
select.appendChild(option);
}
select.selectedIndex = array[4];
select.addEventListener("change", array[3]);
return div;
}
case "tabs": {
const table = document.createElement("div");
table.classList.add("flexttb");
const tabs = document.createElement("div");
tabs.classList.add("flexltr");
tabs.classList.add("tabbed-head");
table.appendChild(tabs);
const content = document.createElement("div");
content.classList.add("tabbed-content");
table.appendChild(content);
label.textContent = array[1];
div.append(label);
div.appendChild(select);
for(const thing of array[2]){
const option = document.createElement("option");
option.textContent = thing;
select.appendChild(option);
}
select.selectedIndex = array[4];
select.addEventListener("change", array[3]);
return div;
}
case"tabs": {
const table = document.createElement("div");
table.classList.add("flexttb");
const tabs = document.createElement("div");
tabs.classList.add("flexltr");
tabs.classList.add("tabbed-head");
table.appendChild(tabs);
const content = document.createElement("div");
content.classList.add("tabbed-content");
table.appendChild(content);
let shown: HTMLElement | undefined;
for (const thing of array[1]) {
const button = document.createElement("button");
button.textContent = thing[0];
tabs.appendChild(button);
let shown: HTMLElement | undefined;
for(const thing of array[1]){
const button = document.createElement("button");
button.textContent = thing[0];
tabs.appendChild(button);
const html = this.tohtml(thing[1]);
content.append(html);
if (!shown) {
shown = html;
} else {
html.style.display = "none";
const html = this.tohtml(thing[1]);
content.append(html);
if(!shown){
shown = html;
}else{
html.style.display = "none";
}
button.addEventListener("click", _=>{
if(shown){
shown.style.display = "none";
}
html.style.display = "";
shown = html;
});
}
return table;
}
default:
console.error(
"can't find element:" + array[0],
" full element:",
array
);
return document.createElement("span");
}
}
show(){
this.onopen();
console.log("fullscreen");
this.background = document.createElement("div");
this.background.classList.add("background");
document.body.appendChild(this.background);
document.body.appendChild(this.html);
this.background.onclick = _=>{
this.hide();
};
}
hide(){
document.body.removeChild(this.background);
document.body.removeChild(this.html);
}
}
button.addEventListener("click", (_) => {
if (shown) {
shown.style.display = "none";
}
html.style.display = "";
shown = html;
});
}
return table;
}
default:
console.error(
"can't find element:" + array[0],
" full element:",
array
);
return document.createElement("span");
}
}
show() {
this.onopen();
console.log("fullscreen");
this.background = document.createElement("div");
this.background.classList.add("background");
document.body.appendChild(this.background);
document.body.appendChild(this.html);
this.background.onclick = (_) => {
this.hide();
};
}
hide() {
document.body.removeChild(this.background);
document.body.removeChild(this.html);
}
}
export { Dialog };
export{ Dialog };

View file

@ -1,78 +1,78 @@
import { Guild } from "./guild.js";
import { Channel } from "./channel.js";
import { Message } from "./message.js";
import { Localuser } from "./localuser.js";
import { User } from "./user.js";
import {
channeljson,
dirrectjson,
memberjson,
messagejson,
} from "./jsontypes.js";
import { Permissions } from "./permissions.js";
import { SnowFlake } from "./snowflake.js";
import { Contextmenu } from "./contextmenu.js";
import{ Guild }from"./guild.js";
import{ Channel }from"./channel.js";
import{ Message }from"./message.js";
import{ Localuser }from"./localuser.js";
import{ User }from"./user.js";
import{
channeljson,
dirrectjson,
memberjson,
messagejson,
}from"./jsontypes.js";
import{ Permissions }from"./permissions.js";
import{ SnowFlake }from"./snowflake.js";
import{ Contextmenu }from"./contextmenu.js";
class Direct extends Guild {
declare channelids: { [key: string]: Group };
getUnixTime(): number {
throw new Error("Do not call this for Direct, it does not make sense");
}
constructor(json: dirrectjson[], owner: Localuser) {
super(-1, owner, null);
this.message_notifications = 0;
this.owner = owner;
if (!this.localuser) {
console.error("Owner was not included, please fix");
}
this.headers = this.localuser.headers;
this.channels = [];
this.channelids = {};
// @ts-ignore
this.properties = {};
this.roles = [];
this.roleids = new Map();
this.prevchannel = undefined;
this.properties.name = "Direct Messages";
for (const thing of json) {
const temp = new Group(thing, this);
this.channels.push(temp);
this.channelids[temp.id] = temp;
}
this.headchannels = this.channels;
}
createChannelpac(json: any) {
const thischannel = new Group(json, this);
this.channelids[thischannel.id] = thischannel;
this.channels.push(thischannel);
this.sortchannels();
this.printServers();
return thischannel;
}
delChannel(json: channeljson) {
const channel = this.channelids[json.id];
super.delChannel(json);
if (channel) {
channel.del();
}
}
giveMember(_member: memberjson) {
console.error("not a real guild, can't give member object");
}
getRole(/* ID: string */) {
return null;
}
hasRole(/* r: string */) {
return false;
}
isAdmin() {
return false;
}
unreaddms() {
for (const thing of this.channels) {
(thing as Group).unreads();
}
}
class Direct extends Guild{
declare channelids: { [key: string]: Group };
getUnixTime(): number{
throw new Error("Do not call this for Direct, it does not make sense");
}
constructor(json: dirrectjson[], owner: Localuser){
super(-1, owner, null);
this.message_notifications = 0;
this.owner = owner;
if(!this.localuser){
console.error("Owner was not included, please fix");
}
this.headers = this.localuser.headers;
this.channels = [];
this.channelids = {};
// @ts-ignore
this.properties = {};
this.roles = [];
this.roleids = new Map();
this.prevchannel = undefined;
this.properties.name = "Direct Messages";
for(const thing of json){
const temp = new Group(thing, this);
this.channels.push(temp);
this.channelids[temp.id] = temp;
}
this.headchannels = this.channels;
}
createChannelpac(json: any){
const thischannel = new Group(json, this);
this.channelids[thischannel.id] = thischannel;
this.channels.push(thischannel);
this.sortchannels();
this.printServers();
return thischannel;
}
delChannel(json: channeljson){
const channel = this.channelids[json.id];
super.delChannel(json);
if(channel){
channel.del();
}
}
giveMember(_member: memberjson){
console.error("not a real guild, can't give member object");
}
getRole(/* ID: string */){
return null;
}
hasRole(/* r: string */){
return false;
}
isAdmin(){
return false;
}
unreaddms(){
for(const thing of this.channels){
(thing as Group).unreads();
}
}
}
const dmPermissions = new Permissions("0");
@ -99,37 +99,37 @@ dmPermissions.setPermission("STREAM", 1);
dmPermissions.setPermission("USE_VAD", 1);
// @ts-ignore
class Group extends Channel {
user: User;
static contextmenu = new Contextmenu<Group, undefined>("channel menu");
static setupcontextmenu() {
this.contextmenu.addbutton("Copy DM id", function (this: Group) {
navigator.clipboard.writeText(this.id);
});
class Group extends Channel{
user: User;
static contextmenu = new Contextmenu<Group, undefined>("channel menu");
static setupcontextmenu(){
this.contextmenu.addbutton("Copy DM id", function(this: Group){
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Mark as read", function (this: Group) {
this.readbottom();
});
this.contextmenu.addbutton("Mark as read", function(this: Group){
this.readbottom();
});
this.contextmenu.addbutton("Close DM", function (this: Group) {
this.deleteChannel();
});
this.contextmenu.addbutton("Close DM", function(this: Group){
this.deleteChannel();
});
this.contextmenu.addbutton("Copy user ID", function () {
navigator.clipboard.writeText(this.user.id);
});
this.contextmenu.addbutton("Copy user ID", function(){
navigator.clipboard.writeText(this.user.id);
});
}
constructor(json: dirrectjson, owner: Direct) {
super(-1, owner, json.id);
this.owner = owner;
this.headers = this.guild.headers;
this.name = json.recipients[0]?.username;
if (json.recipients[0]) {
this.user = new User(json.recipients[0], this.localuser);
} else {
this.user = this.localuser.user;
}
this.name ??= this.localuser.user.username;
constructor(json: dirrectjson, owner: Direct){
super(-1, owner, json.id);
this.owner = owner;
this.headers = this.guild.headers;
this.name = json.recipients[0]?.username;
if(json.recipients[0]){
this.user = new User(json.recipients[0], this.localuser);
}else{
this.user = this.localuser.user;
}
this.name ??= this.localuser.user.username;
this.parent_id!;
this.parent!;
this.children = [];
@ -140,139 +140,139 @@ static contextmenu = new Contextmenu<Group, undefined>("channel menu");
this.setUpInfiniteScroller();
this.updatePosition();
}
updatePosition() {
if (this.lastmessageid) {
this.position = SnowFlake.stringToUnixTime(this.lastmessageid);
} else {
this.position = 0;
updatePosition(){
if(this.lastmessageid){
this.position = SnowFlake.stringToUnixTime(this.lastmessageid);
}else{
this.position = 0;
}
this.position = -Math.max(this.position, this.getUnixTime());
}
this.position = -Math.max(this.position, this.getUnixTime());
}
createguildHTML() {
const div = document.createElement("div");
Group.contextmenu.bindContextmenu(div, this, undefined);
this.html = new WeakRef(div);
div.classList.add("channeleffects");
const myhtml = document.createElement("span");
myhtml.textContent = this.name;
div.appendChild(this.user.buildpfp());
div.appendChild(myhtml);
(div as any)["myinfo"] = this;
div.onclick = (_) => {
this.getHTML();
};
createguildHTML(){
const div = document.createElement("div");
Group.contextmenu.bindContextmenu(div, this);
this.html = new WeakRef(div);
div.classList.add("channeleffects");
const myhtml = document.createElement("span");
myhtml.textContent = this.name;
div.appendChild(this.user.buildpfp());
div.appendChild(myhtml);
(div as any).myinfo = this;
div.onclick = _=>{
this.getHTML();
};
return div;
return div;
}
async getHTML() {
const id = ++Channel.genid;
if (this.localuser.channelfocus) {
this.localuser.channelfocus.infinite.delete();
}
if (this.guild !== this.localuser.lookingguild) {
this.guild.loadGuild();
}
this.guild.prevchannel = this;
this.localuser.channelfocus = this;
const prom = this.infinite.delete();
history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id);
this.localuser.pageTitle("@" + this.name);
(document.getElementById("channelTopic") as HTMLElement).setAttribute(
"hidden",
""
);
async getHTML(){
const id = ++Channel.genid;
if(this.localuser.channelfocus){
this.localuser.channelfocus.infinite.delete();
}
if(this.guild !== this.localuser.lookingguild){
this.guild.loadGuild();
}
this.guild.prevchannel = this;
this.localuser.channelfocus = this;
const prom = this.infinite.delete();
history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id);
this.localuser.pageTitle("@" + this.name);
(document.getElementById("channelTopic") as HTMLElement).setAttribute(
"hidden",
""
);
const loading = document.getElementById("loadingdiv") as HTMLDivElement;
Channel.regenLoadingMessages();
loading.classList.add("loading");
this.rendertyping();
await this.putmessages();
await prom;
if (id !== Channel.genid) {
return;
}
this.buildmessages();
(document.getElementById("typebox") as HTMLDivElement).contentEditable =
const loading = document.getElementById("loadingdiv") as HTMLDivElement;
Channel.regenLoadingMessages();
loading.classList.add("loading");
this.rendertyping();
await this.putmessages();
await prom;
if(id !== Channel.genid){
return;
}
this.buildmessages();
(document.getElementById("typebox") as HTMLDivElement).contentEditable =
"" + true;
}
messageCreate(messagep: { d: messagejson }) {
const messagez = new Message(messagep.d, this);
if (this.lastmessageid) {
this.idToNext.set(this.lastmessageid, messagez.id);
this.idToPrev.set(messagez.id, this.lastmessageid);
}
this.lastmessageid = messagez.id;
if (messagez.author === this.localuser.user) {
this.lastreadmessageid = messagez.id;
if (this.myhtml) {
this.myhtml.classList.remove("cunread");
}
} else {
if (this.myhtml) {
this.myhtml.classList.add("cunread");
}
}
this.unreads();
this.updatePosition();
this.infinite.addedBottom();
this.guild.sortchannels();
if (this.myhtml) {
const parrent = this.myhtml.parentElement as HTMLElement;
parrent.prepend(this.myhtml);
}
if (this === this.localuser.channelfocus) {
if (!this.infinitefocus) {
this.tryfocusinfinate();
}
this.infinite.addedBottom();
}
this.unreads();
if (messagez.author === this.localuser.user) {
return;
}
if (
this.localuser.lookingguild?.prevchannel === this &&
messageCreate(messagep: { d: messagejson }){
const messagez = new Message(messagep.d, this);
if(this.lastmessageid){
this.idToNext.set(this.lastmessageid, messagez.id);
this.idToPrev.set(messagez.id, this.lastmessageid);
}
this.lastmessageid = messagez.id;
if(messagez.author === this.localuser.user){
this.lastreadmessageid = messagez.id;
if(this.myhtml){
this.myhtml.classList.remove("cunread");
}
}else{
if(this.myhtml){
this.myhtml.classList.add("cunread");
}
}
this.unreads();
this.updatePosition();
this.infinite.addedBottom();
this.guild.sortchannels();
if(this.myhtml){
const parrent = this.myhtml.parentElement as HTMLElement;
parrent.prepend(this.myhtml);
}
if(this === this.localuser.channelfocus){
if(!this.infinitefocus){
this.tryfocusinfinate();
}
this.infinite.addedBottom();
}
this.unreads();
if(messagez.author === this.localuser.user){
return;
}
if(
this.localuser.lookingguild?.prevchannel === this &&
document.hasFocus()
) {
return;
}
if (this.notification === "all") {
this.notify(messagez);
} else if (
this.notification === "mentions" &&
){
return;
}
if(this.notification === "all"){
this.notify(messagez);
}else if(
this.notification === "mentions" &&
messagez.mentionsuser(this.localuser.user)
) {
this.notify(messagez);
){
this.notify(messagez);
}
}
notititle(message: Message){
return message.author.username;
}
notititle(message: Message) {
return message.author.username;
}
readbottom() {
super.readbottom();
this.unreads();
readbottom(){
super.readbottom();
this.unreads();
}
all: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
del() {
const all = this.all.deref();
if (all) {
noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
del(){
const all = this.all.deref();
if(all){
all.remove();
}
if (this.myhtml) {
}
if(this.myhtml){
this.myhtml.remove();
}
}
unreads() {
const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime
const current = this.all.deref();
if (this.hasunreads) {
}
}
unreads(){
const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime
const current = this.all.deref();
if(this.hasunreads){
{
const noti = this.noti.deref();
if (noti) {
noti.textContent = this.mentions + "";
return;
}
const noti = this.noti.deref();
if(noti){
noti.textContent = this.mentions + "";
return;
}
}
const div = document.createElement("div");
div.classList.add("servernoti");
@ -286,21 +286,21 @@ static contextmenu = new Contextmenu<Group, undefined>("channel menu");
buildpfp.classList.add("mentioned");
div.append(buildpfp);
sentdms.append(div);
div.onclick = (_) => {
this.guild.loadGuild();
this.getHTML();
div.onclick = _=>{
this.guild.loadGuild();
this.getHTML();
};
} else if (current) {
}else if(current){
current.remove();
} else {
}
}
isAdmin(): boolean {
return false;
}
hasPermission(name: string): boolean {
return dmPermissions.hasPermission(name);
}
}
export { Direct, Group };
Group.setupcontextmenu();
}else{
}
}
isAdmin(): boolean{
return false;
}
hasPermission(name: string): boolean{
return dmPermissions.hasPermission(name);
}
}
export{ Direct, Group };
Group.setupcontextmenu();

View file

@ -1,411 +1,409 @@
import { Dialog } from "./dialog.js";
import { Message } from "./message.js";
import { MarkDown } from "./markdown.js";
import { embedjson, invitejson } from "./jsontypes.js";
import { getapiurls, getInstances } from "./login.js";
import { Guild } from "./guild.js";
import{ Dialog }from"./dialog.js";
import{ Message }from"./message.js";
import{ MarkDown }from"./markdown.js";
import{ embedjson, invitejson }from"./jsontypes.js";
import{ getapiurls, getInstances }from"./login.js";
import{ Guild }from"./guild.js";
class Embed {
type: string;
owner: Message;
json: embedjson;
constructor(json: embedjson, owner: Message) {
this.type = this.getType(json);
this.owner = owner;
this.json = json;
}
getType(json: embedjson) {
const instances = getInstances();
if (
instances &&
class Embed{
type: string;
owner: Message;
json: embedjson;
constructor(json: embedjson, owner: Message){
this.type = this.getType(json);
this.owner = owner;
this.json = json;
}
getType(json: embedjson){
const instances = getInstances();
if(
instances &&
json.type === "link" &&
json.url &&
URL.canParse(json.url)
) {
const Url = new URL(json.url);
for (const instance of instances) {
if (instance.url && URL.canParse(instance.url)) {
const IUrl = new URL(instance.url);
const params = new URLSearchParams(Url.search);
let host: string;
if (params.has("instance")) {
const url = params.get("instance") as string;
if (URL.canParse(url)) {
host = new URL(url).host;
} else {
host = Url.host;
}
} else {
host = Url.host;
}
if (IUrl.host === host) {
const code =
){
const Url = new URL(json.url);
for(const instance of instances){
if(instance.url && URL.canParse(instance.url)){
const IUrl = new URL(instance.url);
const params = new URLSearchParams(Url.search);
let host: string;
if(params.has("instance")){
const url = params.get("instance") as string;
if(URL.canParse(url)){
host = new URL(url).host;
}else{
host = Url.host;
}
}else{
host = Url.host;
}
if(IUrl.host === host){
const code =
Url.pathname.split("/")[Url.pathname.split("/").length - 1];
json.invite = {
url: instance.url,
code,
};
return "invite";
}
}
}
}
return json.type || "rich";
}
generateHTML() {
switch (this.type) {
case "rich":
return this.generateRich();
case "image":
return this.generateImage();
case "invite":
return this.generateInvite();
case "link":
return this.generateLink();
case "video":
case "article":
return this.generateArticle();
default:
console.warn(
`unsupported embed type ${this.type}, please add support dev :3`,
this.json
);
return document.createElement("div"); //prevent errors by giving blank div
}
}
get message() {
return this.owner;
}
get channel() {
return this.message.channel;
}
get guild() {
return this.channel.guild;
}
get localuser() {
return this.guild.localuser;
}
generateRich() {
const div = document.createElement("div");
if (this.json.color) {
div.style.backgroundColor = "#" + this.json.color.toString(16);
}
div.classList.add("embed-color");
json.invite = {
url: instance.url,
code,
};
return"invite";
}
}
}
}
return json.type || "rich";
}
generateHTML(){
switch(this.type){
case"rich":
return this.generateRich();
case"image":
return this.generateImage();
case"invite":
return this.generateInvite();
case"link":
return this.generateLink();
case"video":
case"article":
return this.generateArticle();
default:
console.warn(
`unsupported embed type ${this.type}, please add support dev :3`,
this.json
);
return document.createElement("div"); //prevent errors by giving blank div
}
}
get message(){
return this.owner;
}
get channel(){
return this.message.channel;
}
get guild(){
return this.channel.guild;
}
get localuser(){
return this.guild.localuser;
}
generateRich(){
const div = document.createElement("div");
if(this.json.color){
div.style.backgroundColor = "#" + this.json.color.toString(16);
}
div.classList.add("embed-color");
const embed = document.createElement("div");
embed.classList.add("embed");
div.append(embed);
const embed = document.createElement("div");
embed.classList.add("embed");
div.append(embed);
if (this.json.author) {
const authorline = document.createElement("div");
if (this.json.author.icon_url) {
const img = document.createElement("img");
img.classList.add("embedimg");
img.src = this.json.author.icon_url;
authorline.append(img);
}
const a = document.createElement("a");
a.textContent = this.json.author.name as string;
if (this.json.author.url) {
MarkDown.safeLink(a, this.json.author.url);
}
a.classList.add("username");
authorline.append(a);
embed.append(authorline);
}
if (this.json.title) {
const title = document.createElement("a");
title.append(new MarkDown(this.json.title, this.channel).makeHTML());
if (this.json.url) {
MarkDown.safeLink(title, this.json.url);
}
title.classList.add("embedtitle");
embed.append(title);
}
if (this.json.description) {
const p = document.createElement("p");
p.append(new MarkDown(this.json.description, this.channel).makeHTML());
embed.append(p);
}
if(this.json.author){
const authorline = document.createElement("div");
if(this.json.author.icon_url){
const img = document.createElement("img");
img.classList.add("embedimg");
img.src = this.json.author.icon_url;
authorline.append(img);
}
const a = document.createElement("a");
a.textContent = this.json.author.name as string;
if(this.json.author.url){
MarkDown.safeLink(a, this.json.author.url);
}
a.classList.add("username");
authorline.append(a);
embed.append(authorline);
}
if(this.json.title){
const title = document.createElement("a");
title.append(new MarkDown(this.json.title, this.channel).makeHTML());
if(this.json.url){
MarkDown.safeLink(title, this.json.url);
}
title.classList.add("embedtitle");
embed.append(title);
}
if(this.json.description){
const p = document.createElement("p");
p.append(new MarkDown(this.json.description, this.channel).makeHTML());
embed.append(p);
}
embed.append(document.createElement("br"));
if (this.json.fields) {
for (const thing of this.json.fields) {
const div = document.createElement("div");
const b = document.createElement("b");
b.textContent = thing.name;
div.append(b);
const p = document.createElement("p");
p.append(new MarkDown(thing.value, this.channel).makeHTML());
p.classList.add("embedp");
div.append(p);
embed.append(document.createElement("br"));
if(this.json.fields){
for(const thing of this.json.fields){
const div = document.createElement("div");
const b = document.createElement("b");
b.textContent = thing.name;
div.append(b);
const p = document.createElement("p");
p.append(new MarkDown(thing.value, this.channel).makeHTML());
p.classList.add("embedp");
div.append(p);
if (thing.inline) {
div.classList.add("inline");
}
embed.append(div);
}
}
if (this.json.footer || this.json.timestamp) {
const footer = document.createElement("div");
if (this.json?.footer?.icon_url) {
const img = document.createElement("img");
img.src = this.json.footer.icon_url;
img.classList.add("embedicon");
footer.append(img);
}
if (this.json?.footer?.text) {
const span = document.createElement("span");
span.textContent = this.json.footer.text;
span.classList.add("spaceright");
footer.append(span);
}
if (this.json?.footer && this.json?.timestamp) {
const span = document.createElement("span");
span.textContent = "•";
span.classList.add("spaceright");
footer.append(span);
}
if (this.json?.timestamp) {
const span = document.createElement("span");
span.textContent = new Date(this.json.timestamp).toLocaleString();
footer.append(span);
}
embed.append(footer);
}
return div;
}
generateImage() {
const img = document.createElement("img");
img.classList.add("messageimg");
img.onclick = function () {
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
img.src = this.json.thumbnail.proxy_url;
if (this.json.thumbnail.width) {
let scale = 1;
const max = 96 * 3;
scale = Math.max(scale, this.json.thumbnail.width / max);
scale = Math.max(scale, this.json.thumbnail.height / max);
this.json.thumbnail.width /= scale;
this.json.thumbnail.height /= scale;
}
img.style.width = this.json.thumbnail.width + "px";
img.style.height = this.json.thumbnail.height + "px";
console.log(this.json, "Image fix");
return img;
}
generateLink() {
const table = document.createElement("table");
table.classList.add("embed", "linkembed");
const trtop = document.createElement("tr");
table.append(trtop);
if (this.json.url && this.json.title) {
const td = document.createElement("td");
const a = document.createElement("a");
MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.title;
td.append(a);
trtop.append(td);
}
{
const td = document.createElement("td");
const img = document.createElement("img");
if (this.json.thumbnail) {
img.classList.add("embedimg");
img.onclick = function () {
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
img.src = this.json.thumbnail.proxy_url;
td.append(img);
}
trtop.append(td);
}
const bottomtr = document.createElement("tr");
const td = document.createElement("td");
if (this.json.description) {
const span = document.createElement("span");
span.textContent = this.json.description;
td.append(span);
}
bottomtr.append(td);
table.append(bottomtr);
return table;
}
invcache: [invitejson, { cdn: string; api: string }] | undefined;
generateInvite() {
if (this.invcache && (!this.json.invite || !this.localuser)) {
return this.generateLink();
}
const div = document.createElement("div");
div.classList.add("embed", "inviteEmbed", "flexttb");
const json1 = this.json.invite;
(async () => {
let json: invitejson;
let info: { cdn: string; api: string };
if (!this.invcache) {
if (!json1) {
div.append(this.generateLink());
return;
}
const tempinfo = await getapiurls(json1.url);
if(thing.inline){
div.classList.add("inline");
}
embed.append(div);
}
}
if(this.json.footer || this.json.timestamp){
const footer = document.createElement("div");
if(this.json?.footer?.icon_url){
const img = document.createElement("img");
img.src = this.json.footer.icon_url;
img.classList.add("embedicon");
footer.append(img);
}
if(this.json?.footer?.text){
const span = document.createElement("span");
span.textContent = this.json.footer.text;
span.classList.add("spaceright");
footer.append(span);
}
if(this.json?.footer && this.json?.timestamp){
const span = document.createElement("span");
span.textContent = "•";
span.classList.add("spaceright");
footer.append(span);
}
if(this.json?.timestamp){
const span = document.createElement("span");
span.textContent = new Date(this.json.timestamp).toLocaleString();
footer.append(span);
}
embed.append(footer);
}
return div;
}
generateImage(){
const img = document.createElement("img");
img.classList.add("messageimg");
img.onclick = function(){
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
img.src = this.json.thumbnail.proxy_url;
if(this.json.thumbnail.width){
let scale = 1;
const max = 96 * 3;
scale = Math.max(scale, this.json.thumbnail.width / max);
scale = Math.max(scale, this.json.thumbnail.height / max);
this.json.thumbnail.width /= scale;
this.json.thumbnail.height /= scale;
}
img.style.width = this.json.thumbnail.width + "px";
img.style.height = this.json.thumbnail.height + "px";
console.log(this.json, "Image fix");
return img;
}
generateLink(){
const table = document.createElement("table");
table.classList.add("embed", "linkembed");
const trtop = document.createElement("tr");
table.append(trtop);
if(this.json.url && this.json.title){
const td = document.createElement("td");
const a = document.createElement("a");
MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.title;
td.append(a);
trtop.append(td);
}
{
const td = document.createElement("td");
const img = document.createElement("img");
if(this.json.thumbnail){
img.classList.add("embedimg");
img.onclick = function(){
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
img.src = this.json.thumbnail.proxy_url;
td.append(img);
}
trtop.append(td);
}
const bottomtr = document.createElement("tr");
const td = document.createElement("td");
if(this.json.description){
const span = document.createElement("span");
span.textContent = this.json.description;
td.append(span);
}
bottomtr.append(td);
table.append(bottomtr);
return table;
}
invcache: [invitejson, { cdn: string; api: string }] | undefined;
generateInvite(){
if(this.invcache && (!this.json.invite || !this.localuser)){
return this.generateLink();
}
const div = document.createElement("div");
div.classList.add("embed", "inviteEmbed", "flexttb");
const json1 = this.json.invite;
(async ()=>{
let json: invitejson;
let info: { cdn: string; api: string };
if(!this.invcache){
if(!json1){
div.append(this.generateLink());
return;
}
const tempinfo = await getapiurls(json1.url);
if (!tempinfo) {
div.append(this.generateLink());
return;
}
info = tempinfo;
const res = await fetch(info.api + "/invites/" + json1.code);
if (!res.ok) {
div.append(this.generateLink());
}
json = (await res.json()) as invitejson;
this.invcache = [json, info];
} else {
[json, info] = this.invcache;
}
if (!json) {
div.append(this.generateLink());
return;
}
if (json.guild.banner) {
const banner = document.createElement("img");
banner.src =
if(!tempinfo){
div.append(this.generateLink());
return;
}
info = tempinfo;
const res = await fetch(info.api + "/invites/" + json1.code);
if(!res.ok){
div.append(this.generateLink());
}
json = (await res.json()) as invitejson;
this.invcache = [json, info];
}else{
[json, info] = this.invcache;
}
if(!json){
div.append(this.generateLink());
return;
}
if(json.guild.banner){
const banner = document.createElement("img");
banner.src =
this.localuser.info.cdn +
"/icons/" +
json.guild.id +
"/" +
json.guild.banner +
".png?size=256";
banner.classList.add("banner");
div.append(banner);
}
const guild: invitejson["guild"] & { info?: { cdn: string } } =
banner.classList.add("banner");
div.append(banner);
}
const guild: invitejson["guild"] & { info?: { cdn: string } } =
json.guild;
guild.info = info;
const icon = Guild.generateGuildIcon(
guild.info = info;
const icon = Guild.generateGuildIcon(
guild as invitejson["guild"] & { info: { cdn: string } }
);
const iconrow = document.createElement("div");
iconrow.classList.add("flexltr", "flexstart");
iconrow.append(icon);
{
const guildinfo = document.createElement("div");
guildinfo.classList.add("flexttb", "invguildinfo");
const name = document.createElement("b");
name.textContent = guild.name;
guildinfo.append(name);
);
const iconrow = document.createElement("div");
iconrow.classList.add("flexltr", "flexstart");
iconrow.append(icon);
{
const guildinfo = document.createElement("div");
guildinfo.classList.add("flexttb", "invguildinfo");
const name = document.createElement("b");
name.textContent = guild.name;
guildinfo.append(name);
const members = document.createElement("span");
members.innerText =
const members = document.createElement("span");
members.innerText =
"#" + json.channel.name + " • Members: " + guild.member_count;
guildinfo.append(members);
members.classList.add("subtext");
iconrow.append(guildinfo);
}
guildinfo.append(members);
members.classList.add("subtext");
iconrow.append(guildinfo);
}
div.append(iconrow);
const h2 = document.createElement("h2");
h2.textContent = `You've been invited by ${json.inviter.username}`;
div.append(h2);
const button = document.createElement("button");
button.textContent = "Accept";
if (this.localuser.info.api.startsWith(info.api)) {
if (this.localuser.guildids.has(guild.id)) {
button.textContent = "Already joined";
button.disabled = true;
}
}
button.classList.add("acceptinvbutton");
div.append(button);
button.onclick = (_) => {
if (this.localuser.info.api.startsWith(info.api)) {
fetch(this.localuser.info.api + "/invites/" + json.code, {
method: "POST",
headers: this.localuser.headers,
})
.then((r) => r.json())
.then((_) => {
if (_.message) {
alert(_.message);
}
});
} else {
if (this.json.invite) {
const params = new URLSearchParams("");
params.set("instance", this.json.invite.url);
const encoded = params.toString();
const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`;
window.open(url, "_blank");
}
}
};
})();
return div;
}
generateArticle() {
const colordiv = document.createElement("div");
colordiv.style.backgroundColor = "#000000";
colordiv.classList.add("embed-color");
div.append(iconrow);
const h2 = document.createElement("h2");
h2.textContent = `You've been invited by ${json.inviter.username}`;
div.append(h2);
const button = document.createElement("button");
button.textContent = "Accept";
if(this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)){
button.textContent = "Already joined";
button.disabled = true;
}
button.classList.add("acceptinvbutton");
div.append(button);
button.onclick = _=>{
if(this.localuser.info.api.startsWith(info.api)){
fetch(this.localuser.info.api + "/invites/" + json.code, {
method: "POST",
headers: this.localuser.headers,
})
.then(r=>r.json())
.then(_=>{
if(_.message){
alert(_.message);
}
});
}else{
if(this.json.invite){
const params = new URLSearchParams("");
params.set("instance", this.json.invite.url);
const encoded = params.toString();
const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`;
window.open(url, "_blank");
}
}
};
})();
return div;
}
generateArticle(){
const colordiv = document.createElement("div");
colordiv.style.backgroundColor = "#000000";
colordiv.classList.add("embed-color");
const div = document.createElement("div");
div.classList.add("embed");
if (this.json.provider) {
const provider = document.createElement("p");
provider.classList.add("provider");
provider.textContent = this.json.provider.name;
div.append(provider);
const div = document.createElement("div");
div.classList.add("embed");
if(this.json.provider){
const provider = document.createElement("p");
provider.classList.add("provider");
provider.textContent = this.json.provider.name;
div.append(provider);
}
const a = document.createElement("a");
if(this.json.url && this.json.url){
MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.url;
div.append(a);
}
if(this.json.description){
const description = document.createElement("p");
description.textContent = this.json.description;
div.append(description);
}
if(this.json.thumbnail){
const img = document.createElement("img");
if(this.json.thumbnail.width && this.json.thumbnail.width){
let scale = 1;
const inch = 96;
scale = Math.max(scale, this.json.thumbnail.width / inch / 4);
scale = Math.max(scale, this.json.thumbnail.height / inch / 3);
this.json.thumbnail.width /= scale;
this.json.thumbnail.height /= scale;
img.style.width = this.json.thumbnail.width + "px";
img.style.height = this.json.thumbnail.height + "px";
}
img.classList.add("bigembedimg");
if(this.json.video){
img.onclick = async ()=>{
if(this.json.video){
img.remove();
const iframe = document.createElement("iframe");
iframe.src = this.json.video.url + "?autoplay=1";
if(this.json.thumbnail.width && this.json.thumbnail.width){
iframe.style.width = this.json.thumbnail.width + "px";
iframe.style.height = this.json.thumbnail.height + "px";
}
div.append(iframe);
}
};
}else{
img.onclick = async ()=>{
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
}
img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url;
div.append(img);
}
colordiv.append(div);
return colordiv;
}
}
const a = document.createElement("a");
if (this.json.url && this.json.url) {
MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.url;
div.append(a);
}
if (this.json.description) {
const description = document.createElement("p");
description.textContent = this.json.description;
div.append(description);
}
if (this.json.thumbnail) {
const img = document.createElement("img");
if (this.json.thumbnail.width && this.json.thumbnail.width) {
let scale = 1;
const inch = 96;
scale = Math.max(scale, this.json.thumbnail.width / inch / 4);
scale = Math.max(scale, this.json.thumbnail.height / inch / 3);
this.json.thumbnail.width /= scale;
this.json.thumbnail.height /= scale;
img.style.width = this.json.thumbnail.width + "px";
img.style.height = this.json.thumbnail.height + "px";
}
img.classList.add("bigembedimg");
if (this.json.video) {
img.onclick = async () => {
if (this.json.video) {
img.remove();
const iframe = document.createElement("iframe");
iframe.src = this.json.video.url + "?autoplay=1";
if (this.json.thumbnail.width && this.json.thumbnail.width) {
iframe.style.width = this.json.thumbnail.width + "px";
iframe.style.height = this.json.thumbnail.height + "px";
}
div.append(iframe);
}
};
} else {
img.onclick = async () => {
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
}
img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url;
div.append(img);
}
colordiv.append(div);
return colordiv;
}
}
export { Embed };
export{ Embed };

View file

@ -1,50 +1,49 @@
import { Contextmenu } from "./contextmenu.js";
import { Guild } from "./guild.js";
import { Localuser } from "./localuser.js";
import{ Contextmenu }from"./contextmenu.js";
import{ Guild }from"./guild.js";
import{ Localuser }from"./localuser.js";
class Emoji {
static emojis: {
class Emoji{
static emojis: {
name: string;
emojis: {
name: string;
emoji: string;
}[];
}[];
name: string;
id: string;
animated: boolean;
owner: Guild | Localuser;
get guild() {
if (this.owner instanceof Guild) {
return this.owner;
}
return;
}
get localuser() {
if (this.owner instanceof Guild) {
return this.owner.localuser;
} else {
return this.owner;
}
}
get info() {
return this.owner.info;
}
constructor(
json: { name: string; id: string; animated: boolean },
owner: Guild | Localuser
) {
this.name = json.name;
this.id = json.id;
this.animated = json.animated;
this.owner = owner;
}
getHTML(bigemoji: boolean = false) {
const emojiElem = document.createElement("img");
emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
emojiElem.crossOrigin = "anonymous";
emojiElem.src =
name: string;
id: string;
animated: boolean;
owner: Guild | Localuser;
get guild(){
if(this.owner instanceof Guild){
return this.owner;
}
}
get localuser(){
if(this.owner instanceof Guild){
return this.owner.localuser;
}else{
return this.owner;
}
}
get info(){
return this.owner.info;
}
constructor(
json: { name: string; id: string; animated: boolean },
owner: Guild | Localuser
){
this.name = json.name;
this.id = json.id;
this.animated = json.animated;
this.owner = owner;
}
getHTML(bigemoji: boolean = false){
const emojiElem = document.createElement("img");
emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
emojiElem.crossOrigin = "anonymous";
emojiElem.src =
this.info.cdn +
"/emojis/" +
this.id +
@ -52,208 +51,208 @@ class Emoji {
(this.animated ? "gif" : "png") +
"?size=32";
emojiElem.alt = this.name;
emojiElem.loading = "lazy";
return emojiElem;
}
static decodeEmojiList(buffer: ArrayBuffer) {
const view = new DataView(buffer, 0);
let i = 0;
function read16() {
const int = view.getUint16(i);
i += 2;
return int;
}
function read8() {
const int = view.getUint8(i);
i += 1;
return int;
}
function readString8() {
return readStringNo(read8());
}
function readString16() {
return readStringNo(read16());
}
function readStringNo(length: number) {
const array = new Uint8Array(length);
emojiElem.alt = this.name;
emojiElem.loading = "lazy";
return emojiElem;
}
static decodeEmojiList(buffer: ArrayBuffer){
const view = new DataView(buffer, 0);
let i = 0;
function read16(){
const int = view.getUint16(i);
i += 2;
return int;
}
function read8(){
const int = view.getUint8(i);
i += 1;
return int;
}
function readString8(){
return readStringNo(read8());
}
function readString16(){
return readStringNo(read16());
}
function readStringNo(length: number){
const array = new Uint8Array(length);
for (let i = 0; i < length; i++) {
array[i] = read8();
}
//console.log(array);
return new TextDecoder("utf-8").decode(array.buffer);
}
const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
for(let i = 0; i < length; i++){
array[i] = read8();
}
//console.log(array);
return new TextDecoder("utf-8").decode(array.buffer);
}
const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
[];
let cats = read16();
let cats = read16();
for (; cats !== 0; cats--) {
const name = readString16();
const emojis: {
for(; cats !== 0; cats--){
const name = readString16();
const emojis: {
name: string;
skin_tone_support: boolean;
emoji: string;
}[] = [];
let emojinumber = read16();
for (; emojinumber !== 0; emojinumber--) {
//console.log(emojis);
const name = readString8();
const len = read8();
const skin_tone_support = len > 127;
const emoji = readStringNo(len - Number(skin_tone_support) * 128);
emojis.push({
name,
skin_tone_support,
emoji,
});
}
build.push({
name,
emojis,
});
}
this.emojis = build;
console.log(build);
}
static grabEmoji() {
fetch("/emoji.bin")
.then((e) => {
return e.arrayBuffer();
})
.then((e) => {
Emoji.decodeEmojiList(e);
});
}
static async emojiPicker(
x: number,
y: number,
localuser: Localuser
): Promise<Emoji | string> {
let res: (r: Emoji | string) => void;
const promise: Promise<Emoji | string> = new Promise((r) => {
res = r;
});
const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
menu.style.top = y + "px";
menu.style.left = x + "px";
let emojinumber = read16();
for(; emojinumber !== 0; emojinumber--){
//console.log(emojis);
const name = readString8();
const len = read8();
const skin_tone_support = len > 127;
const emoji = readStringNo(len - Number(skin_tone_support) * 128);
emojis.push({
name,
skin_tone_support,
emoji,
});
}
build.push({
name,
emojis,
});
}
this.emojis = build;
console.log(build);
}
static grabEmoji(){
fetch("/emoji.bin")
.then(e=>{
return e.arrayBuffer();
})
.then(e=>{
Emoji.decodeEmojiList(e);
});
}
static async emojiPicker(
x: number,
y: number,
localuser: Localuser
): Promise<Emoji | string>{
let res: (r: Emoji | string) => void;
const promise: Promise<Emoji | string> = new Promise(r=>{
res = r;
});
const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
menu.style.top = y + "px";
menu.style.left = x + "px";
const title = document.createElement("h2");
title.textContent = Emoji.emojis[0].name;
title.classList.add("emojiTitle");
menu.append(title);
const selection = document.createElement("div");
selection.classList.add("flexltr", "dontshrink", "emojirow");
const body = document.createElement("div");
body.classList.add("emojiBody");
const title = document.createElement("h2");
title.textContent = Emoji.emojis[0].name;
title.classList.add("emojiTitle");
menu.append(title);
const selection = document.createElement("div");
selection.classList.add("flexltr", "dontshrink", "emojirow");
const body = document.createElement("div");
body.classList.add("emojiBody");
let isFirst = true;
localuser.guilds
.filter((guild) => guild.id != "@me" && guild.emojis.length > 0)
.forEach((guild) => {
const select = document.createElement("div");
select.classList.add("emojiSelect");
let isFirst = true;
localuser.guilds
.filter(guild=>guild.id != "@me" && guild.emojis.length > 0)
.forEach(guild=>{
const select = document.createElement("div");
select.classList.add("emojiSelect");
if (guild.properties.icon) {
const img = document.createElement("img");
img.classList.add("pfp", "servericon", "emoji-server");
img.crossOrigin = "anonymous";
img.src =
if(guild.properties.icon){
const img = document.createElement("img");
img.classList.add("pfp", "servericon", "emoji-server");
img.crossOrigin = "anonymous";
img.src =
localuser.info.cdn +
"/icons/" +
guild.properties.id +
"/" +
guild.properties.icon +
".png?size=48";
img.alt = "Server: " + guild.properties.name;
select.appendChild(img);
} else {
const div = document.createElement("span");
div.textContent = guild.properties.name
.replace(/'s /g, " ")
.replace(/\w+/g, (word) => word[0])
.replace(/\s/g, "");
select.append(div);
}
img.alt = "Server: " + guild.properties.name;
select.appendChild(img);
}else{
const div = document.createElement("span");
div.textContent = guild.properties.name
.replace(/'s /g, " ")
.replace(/\w+/g, word=>word[0])
.replace(/\s/g, "");
select.append(div);
}
selection.append(select);
selection.append(select);
const clickEvent = () => {
title.textContent = guild.properties.name;
body.innerHTML = "";
for (const emojit of guild.emojis) {
const emojiElem = document.createElement("div");
emojiElem.classList.add("emojiSelect");
const clickEvent = ()=>{
title.textContent = guild.properties.name;
body.innerHTML = "";
for(const emojit of guild.emojis){
const emojiElem = document.createElement("div");
emojiElem.classList.add("emojiSelect");
const emojiClass = new Emoji(
{
id: emojit.id as string,
name: emojit.name,
animated: emojit.animated as boolean,
},
localuser
);
emojiElem.append(emojiClass.getHTML());
body.append(emojiElem);
const emojiClass = new Emoji(
{
id: emojit.id as string,
name: emojit.name,
animated: emojit.animated as boolean,
},
localuser
);
emojiElem.append(emojiClass.getHTML());
body.append(emojiElem);
emojiElem.addEventListener("click", () => {
res(emojiClass);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
});
}
};
emojiElem.addEventListener("click", ()=>{
res(emojiClass);
if(Contextmenu.currentmenu !== ""){
Contextmenu.currentmenu.remove();
}
});
}
};
select.addEventListener("click", clickEvent);
if (isFirst) {
clickEvent();
isFirst = false;
}
});
select.addEventListener("click", clickEvent);
if(isFirst){
clickEvent();
isFirst = false;
}
});
setTimeout(() => {
if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove();
}
document.body.append(menu);
Contextmenu.currentmenu = menu;
Contextmenu.keepOnScreen(menu);
}, 10);
setTimeout(()=>{
if(Contextmenu.currentmenu != ""){
Contextmenu.currentmenu.remove();
}
document.body.append(menu);
Contextmenu.currentmenu = menu;
Contextmenu.keepOnScreen(menu);
}, 10);
let i = 0;
for (const thing of Emoji.emojis) {
const select = document.createElement("div");
select.textContent = thing.emojis[0].emoji;
select.classList.add("emojiSelect");
selection.append(select);
const clickEvent = () => {
title.textContent = thing.name;
body.innerHTML = "";
for (const emojit of thing.emojis) {
const emoji = document.createElement("div");
emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji;
body.append(emoji);
emoji.onclick = (_) => {
res(emojit.emoji);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
};
}
};
select.onclick = clickEvent;
if (i === 0) {
clickEvent();
}
i++;
}
menu.append(selection);
menu.append(body);
return promise;
}
let i = 0;
for(const thing of Emoji.emojis){
const select = document.createElement("div");
select.textContent = thing.emojis[0].emoji;
select.classList.add("emojiSelect");
selection.append(select);
const clickEvent = ()=>{
title.textContent = thing.name;
body.innerHTML = "";
for(const emojit of thing.emojis){
const emoji = document.createElement("div");
emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji;
body.append(emoji);
emoji.onclick = _=>{
res(emojit.emoji);
if(Contextmenu.currentmenu !== ""){
Contextmenu.currentmenu.remove();
}
};
}
};
select.onclick = clickEvent;
if(i === 0){
clickEvent();
}
i++;
}
menu.append(selection);
menu.append(body);
return promise;
}
}
Emoji.grabEmoji();
export { Emoji };
export{ Emoji };

View file

@ -1,152 +1,152 @@
import { Message } from "./message.js";
import { Dialog } from "./dialog.js";
import { filejson } from "./jsontypes.js";
import{ Message }from"./message.js";
import{ Dialog }from"./dialog.js";
import{ filejson }from"./jsontypes.js";
class File {
owner: Message | null;
id: string;
filename: string;
content_type: string;
width: number | undefined;
height: number | undefined;
proxy_url: string | undefined;
url: string;
size: number;
constructor(fileJSON: filejson, owner: Message | null) {
this.owner = owner;
this.id = fileJSON.id;
this.filename = fileJSON.filename;
this.content_type = fileJSON.content_type;
this.width = fileJSON.width;
this.height = fileJSON.height;
this.url = fileJSON.url;
this.proxy_url = fileJSON.proxy_url;
this.content_type = fileJSON.content_type;
this.size = fileJSON.size;
}
getHTML(temp: boolean = false): HTMLElement {
const src = this.proxy_url || this.url;
if (this.width && this.height) {
let scale = 1;
const max = 96 * 3;
scale = Math.max(scale, this.width / max);
scale = Math.max(scale, this.height / max);
this.width /= scale;
this.height /= scale;
}
if (this.content_type.startsWith("image/")) {
const div = document.createElement("div");
const img = document.createElement("img");
img.classList.add("messageimg");
div.classList.add("messageimgdiv");
img.onclick = function () {
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
img.src = src;
div.append(img);
if (this.width) {
div.style.width = this.width + "px";
div.style.height = this.height + "px";
}
console.log(img);
console.log(this.width, this.height);
return div;
} else if (this.content_type.startsWith("video/")) {
const video = document.createElement("video");
const source = document.createElement("source");
source.src = src;
video.append(source);
source.type = this.content_type;
video.controls = !temp;
if (this.width && this.height) {
video.width = this.width;
video.height = this.height;
}
return video;
} else if (this.content_type.startsWith("audio/")) {
const audio = document.createElement("audio");
const source = document.createElement("source");
source.src = src;
audio.append(source);
source.type = this.content_type;
audio.controls = !temp;
return audio;
} else {
return this.createunknown();
}
}
upHTML(files: Blob[], file: globalThis.File): HTMLElement {
const div = document.createElement("div");
const contained = this.getHTML(true);
div.classList.add("containedFile");
div.append(contained);
const controls = document.createElement("div");
const garbage = document.createElement("button");
garbage.textContent = "🗑";
garbage.onclick = (_) => {
div.remove();
files.splice(files.indexOf(file), 1);
};
controls.classList.add("controls");
div.append(controls);
controls.append(garbage);
return div;
}
static initFromBlob(file: globalThis.File) {
return new File(
{
filename: file.name,
size: file.size,
id: "null",
content_type: file.type,
width: undefined,
height: undefined,
url: URL.createObjectURL(file),
proxy_url: undefined,
},
null
);
}
createunknown(): HTMLElement {
console.log("🗎");
const src = this.proxy_url || this.url;
const div = document.createElement("table");
div.classList.add("unknownfile");
const nametr = document.createElement("tr");
div.append(nametr);
const fileicon = document.createElement("td");
nametr.append(fileicon);
fileicon.append("🗎");
fileicon.classList.add("fileicon");
fileicon.rowSpan = 2;
const nametd = document.createElement("td");
if (src) {
const a = document.createElement("a");
a.href = src;
a.textContent = this.filename;
nametd.append(a);
} else {
nametd.textContent = this.filename;
}
class File{
owner: Message | null;
id: string;
filename: string;
content_type: string;
width: number | undefined;
height: number | undefined;
proxy_url: string | undefined;
url: string;
size: number;
constructor(fileJSON: filejson, owner: Message | null){
this.owner = owner;
this.id = fileJSON.id;
this.filename = fileJSON.filename;
this.content_type = fileJSON.content_type;
this.width = fileJSON.width;
this.height = fileJSON.height;
this.url = fileJSON.url;
this.proxy_url = fileJSON.proxy_url;
this.content_type = fileJSON.content_type;
this.size = fileJSON.size;
}
getHTML(temp: boolean = false): HTMLElement{
const src = this.proxy_url || this.url;
if(this.width && this.height){
let scale = 1;
const max = 96 * 3;
scale = Math.max(scale, this.width / max);
scale = Math.max(scale, this.height / max);
this.width /= scale;
this.height /= scale;
}
if(this.content_type.startsWith("image/")){
const div = document.createElement("div");
const img = document.createElement("img");
img.classList.add("messageimg");
div.classList.add("messageimgdiv");
img.onclick = function(){
const full = new Dialog(["img", img.src, ["fit"]]);
full.show();
};
img.src = src;
div.append(img);
if(this.width){
div.style.width = this.width + "px";
div.style.height = this.height + "px";
}
console.log(img);
console.log(this.width, this.height);
return div;
}else if(this.content_type.startsWith("video/")){
const video = document.createElement("video");
const source = document.createElement("source");
source.src = src;
video.append(source);
source.type = this.content_type;
video.controls = !temp;
if(this.width && this.height){
video.width = this.width;
video.height = this.height;
}
return video;
}else if(this.content_type.startsWith("audio/")){
const audio = document.createElement("audio");
const source = document.createElement("source");
source.src = src;
audio.append(source);
source.type = this.content_type;
audio.controls = !temp;
return audio;
}else{
return this.createunknown();
}
}
upHTML(files: Blob[], file: globalThis.File): HTMLElement{
const div = document.createElement("div");
const contained = this.getHTML(true);
div.classList.add("containedFile");
div.append(contained);
const controls = document.createElement("div");
const garbage = document.createElement("button");
garbage.textContent = "🗑";
garbage.onclick = _=>{
div.remove();
files.splice(files.indexOf(file), 1);
};
controls.classList.add("controls");
div.append(controls);
controls.append(garbage);
return div;
}
static initFromBlob(file: globalThis.File){
return new File(
{
filename: file.name,
size: file.size,
id: "null",
content_type: file.type,
width: undefined,
height: undefined,
url: URL.createObjectURL(file),
proxy_url: undefined,
},
null
);
}
createunknown(): HTMLElement{
console.log("🗎");
const src = this.proxy_url || this.url;
const div = document.createElement("table");
div.classList.add("unknownfile");
const nametr = document.createElement("tr");
div.append(nametr);
const fileicon = document.createElement("td");
nametr.append(fileicon);
fileicon.append("🗎");
fileicon.classList.add("fileicon");
fileicon.rowSpan = 2;
const nametd = document.createElement("td");
if(src){
const a = document.createElement("a");
a.href = src;
a.textContent = this.filename;
nametd.append(a);
}else{
nametd.textContent = this.filename;
}
nametd.classList.add("filename");
nametr.append(nametd);
const sizetr = document.createElement("tr");
const size = document.createElement("td");
sizetr.append(size);
size.textContent = "Size:" + File.filesizehuman(this.size);
size.classList.add("filesize");
div.appendChild(sizetr);
return div;
}
static filesizehuman(fsize: number) {
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
return (
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 +
nametd.classList.add("filename");
nametr.append(nametd);
const sizetr = document.createElement("tr");
const size = document.createElement("td");
sizetr.append(size);
size.textContent = "Size:" + File.filesizehuman(this.size);
size.classList.add("filesize");
div.appendChild(sizetr);
return div;
}
static filesizehuman(fsize: number){
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
return(
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 +
" " +
["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i]
);
);
}
}
}
export { File };
export{ File };

File diff suppressed because it is too large Load diff

View file

@ -1,61 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<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">
</head>
<head>
<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">
</head>
<body class="Dark-theme">
<div id="titleDiv">
<img src="/logo.svg" width="40">
<h1 id="pageTitle">Jank Client</h1>
<a href="https://sb-jankclient.vanillaminigames.net/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat"
class="TitleButtons">
<h1>Spacebar Guild</h1>
</a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
<h1>Github</h1>
</a>
</div>
<div class="flexttb">
<div class="flexttb pagehead">
<h1>Welcome to Jank Client</h1>
</div>
<div class="pagebox">
<p>Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including:
</p>
<ul>
<li>Direct Messaging</li>
<li>Reactions support</li>
<li>Invites</li>
<li>Account switching</li>
<li>User settings</li>
</ul>
</div>
<div class="pagebox">
<h2>Spacebar compatible Instances:</h2>
<div id="instancebox">
</div>
</div>
<div class="pagebox">
<h2>Contribute to Jank Client</h2>
<p>We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out
some typos.</p><br>
</a><a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
<body class="Dark-theme">
<div id="titleDiv">
<img src="/logo.svg" width="40">
<h1 id="pageTitle">Jank Client</h1>
<a href="https://sb-jankclient.vanillaminigames.net/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat"
class="TitleButtons">
<h1>Spacebar Guild</h1>
</a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
<h1>Github</h1>
</a>
</div>
</div>
</body>
<script src="/home.js" type="module"></script>
<div class="flexttb">
<div class="flexttb pagehead">
<h1>Welcome to Jank Client</h1>
</div>
<div class="pagebox">
<p>Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including:
</p>
<ul>
<li>Direct Messaging</li>
<li>Reactions support</li>
<li>Invites</li>
<li>Account switching</li>
<li>User settings</li>
</ul>
</div>
<div class="pagebox">
<h2>Spacebar compatible Instances:</h2>
<div id="instancebox">
</div>
</div>
<div class="pagebox">
<h2>Contribute to Jank Client</h2>
<p>We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out
some typos.</p><br>
</a><a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
<h1>Github</h1>
</a>
</div>
</div>
</body>
<script src="/home.js" type="module"></script>
</html>

View file

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

View file

@ -1,82 +1,82 @@
<!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">
<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">
<head>
<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">
<link rel="manifest" href="/manifest.json">
</head>
<link rel="manifest" href="/manifest.json">
</head>
<body class="Dark-theme">
<script src="/index.js" type="module"></script>
<body class="Dark-theme">
<script src="/index.js" type="module"></script>
<div id="loading" class="loading">
<div id="centerdiv">
<img src="/logo.svg" style="width:3in;height:3in;">
<h1>Jank Client is loading</h1>
<h2 id="load-desc">This shouldn't take long</h2>
<h1 id="switchaccounts">Switch Accounts</h1>
</div>
</div>
<div class="flexltr" id="page">
<div id="neunence">
<div id="servers"></div>
</div>
<div class="flexttb channelflex">
<div class="servertd" id="servertd">
<h2 id="serverName">Server Name</h2>
<div id="loading" class="loading">
<div id="centerdiv">
<img src="/logo.svg" style="width:3in;height:3in;">
<h1>Jank Client is loading</h1>
<h2 id="load-desc">This shouldn't take long</h2>
<h1 id="switchaccounts">Switch Accounts</h1>
</div>
<div id="channels"></div>
<div class="flexltr" id="userdock">
<div class="flexltr" id="userinfo">
<img id="userpfp" class="pfp">
</div>
<div class="flexltr" id="page">
<div id="neunence">
<div id="servers"></div>
</div>
<div class="flexttb channelflex">
<div class="servertd" id="servertd">
<h2 id="serverName">Server Name</h2>
</div>
<div id="channels"></div>
<div class="flexltr" id="userdock">
<div class="flexltr" id="userinfo">
<img id="userpfp" class="pfp">
<div class="userflex">
<p id="username">USERNAME</p>
<p id="status">STATUS</p>
<div class="userflex">
<p id="username">USERNAME</p>
<p id="status">STATUS</p>
</div>
</div>
<div id="user-actions">
<span id="settings" class="svgtheme svg-settings"></span>
</div>
</div>
<div id="user-actions">
<span id="settings" class="svgtheme svg-settings"></span>
</div>
</div>
</div>
<div class="flexttb messageflex">
<div class="servertd channelnamediv">
<span id="mobileback" hidden></span>
<span id="channelname">Channel name</span>
<span id="channelTopic" hidden>Channel topic</span>
</div>
<div id="channelw">
<div id="loadingdiv">
<div class="flexttb messageflex">
<div class="servertd channelnamediv">
<span id="mobileback" hidden></span>
<span id="channelname">Channel name</span>
<span id="channelTopic" hidden>Channel topic</span>
</div>
</div>
<div id="pasteimage"></div>
<div id="replybox" class="hideReplyBox"></div>
<div id="typediv">
<div id="realbox">
<div id="typebox" contentEditable="true"></div>
<div id="channelw">
<div id="loadingdiv">
</div>
</div>
<div id="typing" class="hidden">
<p id="typingtext">typing</p>
<div class="loading-indicator">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div id="pasteimage"></div>
<div id="replybox" class="hideReplyBox"></div>
<div id="typediv">
<div id="realbox">
<div id="typebox" contentEditable="true"></div>
</div>
<div id="typing" class="hidden">
<p id="typingtext">typing</p>
<div class="loading-indicator">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</body>
</html>

View file

@ -1,146 +1,146 @@
import { Localuser } from "./localuser.js";
import { Contextmenu } from "./contextmenu.js";
import { mobile, getBulkUsers, setTheme, Specialuser } from "./login.js";
import { MarkDown } from "./markdown.js";
import { Message } from "./message.js";
import { File } from "./file.js";
import{ Localuser }from"./localuser.js";
import{ Contextmenu }from"./contextmenu.js";
import{ mobile, getBulkUsers, setTheme, Specialuser }from"./login.js";
import{ MarkDown }from"./markdown.js";
import{ Message }from"./message.js";
import{ File }from"./file.js";
(async () => {
async function waitForLoad(): Promise<void> {
return new Promise((resolve) => {
document.addEventListener("DOMContentLoaded", (_) => resolve());
});
(async ()=>{
async function waitForLoad(): Promise<void>{
return new Promise(resolve=>{
document.addEventListener("DOMContentLoaded", _=>resolve());
});
}
await waitForLoad();
const users = getBulkUsers();
if (!users.currentuser) {
window.location.href = "/login.html";
return;
if(!users.currentuser){
window.location.href = "/login.html";
return;
}
function showAccountSwitcher(): void {
const table = document.createElement("div");
table.classList.add("accountSwitcher");
function showAccountSwitcher(): void{
const table = document.createElement("div");
table.classList.add("accountSwitcher");
for (const user of Object.values(users.users)) {
const specialUser = user as Specialuser;
const userInfo = document.createElement("div");
userInfo.classList.add("flexltr", "switchtable");
for(const user of Object.values(users.users)){
const specialUser = user as Specialuser;
const userInfo = document.createElement("div");
userInfo.classList.add("flexltr", "switchtable");
const pfp = document.createElement("img");
pfp.src = specialUser.pfpsrc;
pfp.classList.add("pfp");
userInfo.append(pfp);
const pfp = document.createElement("img");
pfp.src = specialUser.pfpsrc;
pfp.classList.add("pfp");
userInfo.append(pfp);
const userDiv = document.createElement("div");
userDiv.classList.add("userinfo");
userDiv.textContent = specialUser.username;
userDiv.append(document.createElement("br"));
const userDiv = document.createElement("div");
userDiv.classList.add("userinfo");
userDiv.textContent = specialUser.username;
userDiv.append(document.createElement("br"));
const span = document.createElement("span");
span.textContent = specialUser.serverurls.wellknown
.replace("https://", "")
.replace("http://", "");
span.classList.add("serverURL");
userDiv.append(span);
const span = document.createElement("span");
span.textContent = specialUser.serverurls.wellknown
.replace("https://", "")
.replace("http://", "");
span.classList.add("serverURL");
userDiv.append(span);
userInfo.append(userDiv);
table.append(userInfo);
userInfo.append(userDiv);
table.append(userInfo);
userInfo.addEventListener("click", () => {
thisUser.unload();
thisUser.swapped = true;
const loading = document.getElementById("loading") as HTMLDivElement;
loading.classList.remove("doneloading");
loading.classList.add("loading");
userInfo.addEventListener("click", ()=>{
thisUser.unload();
thisUser.swapped = true;
const loading = document.getElementById("loading") as HTMLDivElement;
loading.classList.remove("doneloading");
loading.classList.add("loading");
thisUser = new Localuser(specialUser);
users.currentuser = specialUser.uid;
localStorage.setItem("userinfos", JSON.stringify(users));
thisUser = new Localuser(specialUser);
users.currentuser = specialUser.uid;
localStorage.setItem("userinfos", JSON.stringify(users));
thisUser.initwebsocket().then(() => {
thisUser.loaduser();
thisUser.init();
loading.classList.add("doneloading");
loading.classList.remove("loading");
console.log("done loading");
});
thisUser.initwebsocket().then(()=>{
thisUser.loaduser();
thisUser.init();
loading.classList.add("doneloading");
loading.classList.remove("loading");
console.log("done loading");
});
userInfo.remove();
});
}
userInfo.remove();
});
}
const switchAccountDiv = document.createElement("div");
switchAccountDiv.classList.add("switchtable");
switchAccountDiv.textContent = "Switch accounts ⇌";
switchAccountDiv.addEventListener("click", () => {
window.location.href = "/login.html";
});
table.append(switchAccountDiv);
const switchAccountDiv = document.createElement("div");
switchAccountDiv.classList.add("switchtable");
switchAccountDiv.textContent = "Switch accounts ⇌";
switchAccountDiv.addEventListener("click", ()=>{
window.location.href = "/login.html";
});
table.append(switchAccountDiv);
if (Contextmenu.currentmenu) {
Contextmenu.currentmenu.remove();
}
Contextmenu.currentmenu = table;
document.body.append(table);
if(Contextmenu.currentmenu){
Contextmenu.currentmenu.remove();
}
Contextmenu.currentmenu = table;
document.body.append(table);
}
const userInfoElement = document.getElementById("userinfo") as HTMLDivElement;
userInfoElement.addEventListener("click", (event) => {
event.stopImmediatePropagation();
showAccountSwitcher();
userInfoElement.addEventListener("click", event=>{
event.stopImmediatePropagation();
showAccountSwitcher();
});
const switchAccountsElement = document.getElementById(
"switchaccounts"
"switchaccounts"
) as HTMLDivElement;
switchAccountsElement.addEventListener("click", (event) => {
event.stopImmediatePropagation();
showAccountSwitcher();
switchAccountsElement.addEventListener("click", event=>{
event.stopImmediatePropagation();
showAccountSwitcher();
});
let thisUser: Localuser;
try {
console.log(users.users, users.currentuser);
thisUser = new Localuser(users.users[users.currentuser]);
thisUser.initwebsocket().then(() => {
thisUser.loaduser();
thisUser.init();
const loading = document.getElementById("loading") as HTMLDivElement;
loading.classList.add("doneloading");
loading.classList.remove("loading");
console.log("done loading");
});
} catch (e) {
console.error(e);
(document.getElementById("load-desc") as HTMLSpanElement).textContent =
try{
console.log(users.users, users.currentuser);
thisUser = new Localuser(users.users[users.currentuser]);
thisUser.initwebsocket().then(()=>{
thisUser.loaduser();
thisUser.init();
const loading = document.getElementById("loading") as HTMLDivElement;
loading.classList.add("doneloading");
loading.classList.remove("loading");
console.log("done loading");
});
}catch(e){
console.error(e);
(document.getElementById("load-desc") as HTMLSpanElement).textContent =
"Account unable to start";
thisUser = new Localuser(-1);
thisUser = new Localuser(-1);
}
const menu = new Contextmenu("create rightclick");
menu.addbutton(
"Create channel",
() => {
if (thisUser.lookingguild) {
thisUser.lookingguild.createchannels();
}
},
null,
() => thisUser.isAdmin()
"Create channel",
()=>{
if(thisUser.lookingguild){
thisUser.lookingguild.createchannels();
}
},
null,
()=>thisUser.isAdmin()
);
menu.addbutton(
"Create category",
() => {
if (thisUser.lookingguild) {
thisUser.lookingguild.createcategory();
}
},
null,
() => thisUser.isAdmin()
"Create category",
()=>{
if(thisUser.lookingguild){
thisUser.lookingguild.createcategory();
}
},
null,
()=>thisUser.isAdmin()
);
menu.bindContextmenu(
@ -150,51 +150,51 @@ async function waitForLoad(): Promise<void> {
);
const pasteImageElement = document.getElementById(
"pasteimage"
"pasteimage"
) as HTMLDivElement;
let replyingTo: Message | null = null;
async function handleEnter(event: KeyboardEvent): Promise<void> {
async function handleEnter(event: KeyboardEvent): Promise<void>{
const channel = thisUser.channelfocus;
if (!channel) return;
if(!channel)return;
channel.typingstart();
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
if(event.key === "Enter" && !event.shiftKey){
event.preventDefault();
if (channel.editing) {
channel.editing.edit(markdown.rawString);
channel.editing = null;
} else {
replyingTo = thisUser.channelfocus
? thisUser.channelfocus.replyingto
: null;
if (replyingTo?.div) {
replyingTo.div.classList.remove("replying");
}
if (thisUser.channelfocus) {
thisUser.channelfocus.replyingto = null;
}
channel.sendMessage(markdown.rawString, {
attachments: images,
// @ts-ignore This is valid according to the API
embeds: [], // Add an empty array for the embeds property
replyingto: replyingTo,
});
if (thisUser.channelfocus) {
thisUser.channelfocus.makereplybox();
}
}
if(channel.editing){
channel.editing.edit(markdown.rawString);
channel.editing = null;
}else{
replyingTo = thisUser.channelfocus
? thisUser.channelfocus.replyingto
: null;
if(replyingTo?.div){
replyingTo.div.classList.remove("replying");
}
if(thisUser.channelfocus){
thisUser.channelfocus.replyingto = null;
}
channel.sendMessage(markdown.rawString, {
attachments: images,
// @ts-ignore This is valid according to the API
embeds: [], // Add an empty array for the embeds property
replyingto: replyingTo,
});
if(thisUser.channelfocus){
thisUser.channelfocus.makereplybox();
}
}
while (images.length) {
images.pop();
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);
}
while(images.length){
images.pop();
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);
}
typebox.innerHTML = "";
}
typebox.innerHTML = "";
}
}
interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown;
@ -204,56 +204,56 @@ async function waitForLoad(): Promise<void> {
const markdown = new MarkDown("", thisUser);
typebox.markdown = markdown;
typebox.addEventListener("keyup", handleEnter);
typebox.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
typebox.addEventListener("keydown", event=>{
if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
});
markdown.giveBox(typebox);
const images: Blob[] = [];
const imagesHtml: HTMLElement[] = [];
document.addEventListener("paste", async (e: ClipboardEvent) => {
if (!e.clipboardData) return;
document.addEventListener("paste", async (e: ClipboardEvent)=>{
if(!e.clipboardData)return;
for (const file of Array.from(e.clipboardData.files)) {
const fileInstance = File.initFromBlob(file);
e.preventDefault();
const html = fileInstance.upHTML(images, file);
pasteImageElement.appendChild(html);
images.push(file);
imagesHtml.push(html);
}
for(const file of Array.from(e.clipboardData.files)){
const fileInstance = File.initFromBlob(file);
e.preventDefault();
const html = fileInstance.upHTML(images, file);
pasteImageElement.appendChild(html);
images.push(file);
imagesHtml.push(html);
}
});
setTheme();
function userSettings(): void {
thisUser.showusersettings();
function userSettings(): void{
thisUser.showusersettings();
}
(document.getElementById("settings") as HTMLImageElement).onclick =
userSettings;
if (mobile) {
const channelWrapper = document.getElementById(
"channelw"
) as HTMLDivElement;
channelWrapper.onclick = () => {
(
if(mobile){
const channelWrapper = document.getElementById(
"channelw"
) as HTMLDivElement;
channelWrapper.onclick = ()=>{
(
document.getElementById("channels")!.parentNode as HTMLElement
).classList.add("collapse");
).classList.add("collapse");
document.getElementById("servertd")!.classList.add("collapse");
document.getElementById("servers")!.classList.add("collapse");
};
};
const mobileBack = document.getElementById("mobileback") as HTMLDivElement;
mobileBack.textContent = "#";
mobileBack.onclick = () => {
(
const mobileBack = document.getElementById("mobileback") as HTMLDivElement;
mobileBack.textContent = "#";
mobileBack.onclick = ()=>{
(
document.getElementById("channels")!.parentNode as HTMLElement
).classList.remove("collapse");
).classList.remove("collapse");
document.getElementById("servertd")!.classList.remove("collapse");
document.getElementById("servers")!.classList.remove("collapse");
};
};
}
})();
})();

View file

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

View file

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

View file

@ -479,23 +479,23 @@ chunk_index: number;
chunk_count: number;
not_found: string[];
};
export {
readyjson,
dirrectjson,
startTypingjson,
channeljson,
guildjson,
rolesjson,
userjson,
memberjson,
mainuserjson,
messagejson,
filejson,
embedjson,
emojijson,
presencejson,
wsjson,
messageCreateJson,
memberChunk,
invitejson,
export{
readyjson,
dirrectjson,
startTypingjson,
channeljson,
guildjson,
rolesjson,
userjson,
memberjson,
mainuserjson,
messagejson,
filejson,
embedjson,
emojijson,
presencejson,
wsjson,
messageCreateJson,
memberChunk,
invitejson,
};

File diff suppressed because it is too large Load diff

View file

@ -1,62 +1,62 @@
<body class="Dark-theme">
<head>
<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" />
</head>
<div id="logindiv">
<h1>Login</h1>
<br />
<form id="form" submit="check(e)">
<label for="instance"><b>Instance:</b></label
><br />
<p id="verify"></p>
<input
type="search"
list="instances"
placeholder="Instance URL"
name="instance"
id="instancein"
value=""
id="instancein"
required
/><br /><br />
<head>
<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">
</head>
<div id="logindiv">
<h1>Login</h1>
<br >
<form id="form" submit="check(e)">
<label for="instance"><b>Instance:</b></label
><br >
<p id="verify"></p>
<input
type="search"
list="instances"
placeholder="Instance URL"
name="instance"
id="instancein"
value=""
id="instancein"
required
><br ><br >
<label for="uname"><b>Email:</b></label
><br />
<input
type="text"
placeholder="Enter email address"
name="uname"
id="uname"
required
/><br /><br />
<label for="uname"><b>Email:</b></label
><br >
<input
type="text"
placeholder="Enter email address"
name="uname"
id="uname"
required
><br ><br >
<label for="psw"><b>Password:</b></label
><br />
<input
type="password"
placeholder="Enter Password"
name="psw"
id="psw"
required
/><br /><br /><br /><br />
<p class="wrongred" id="wrong"></p>
<label for="psw"><b>Password:</b></label
><br >
<input
type="password"
placeholder="Enter Password"
name="psw"
id="psw"
required
><br ><br ><br ><br >
<p class="wrongred" id="wrong"></p>
<div id="h-captcha"></div>
<button type="submit">Login</button>
</form>
<a href="/register.html" id="switch">Don't have an account?</a>
</div>
<datalist id="instances"></datalist>
<script src="/login.js" type="module"></script>
<div id="h-captcha"></div>
<button type="submit">Login</button>
</form>
<a href="/register.html" id="switch">Don't have an account?</a>
</div>
<datalist id="instances"></datalist>
<script src="/login.js" type="module"></script>
</body>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,256 +1,256 @@
import { User } from "./user.js";
import { Role } from "./role.js";
import { Guild } from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { memberjson, presencejson } from "./jsontypes.js";
import { Dialog } from "./dialog.js";
import{ User }from"./user.js";
import{ Role }from"./role.js";
import{ Guild }from"./guild.js";
import{ SnowFlake }from"./snowflake.js";
import{ memberjson, presencejson }from"./jsontypes.js";
import{ Dialog }from"./dialog.js";
class Member extends SnowFlake {
static already = {};
owner: Guild;
user: User;
roles: Role[] = [];
nick!: string;
class Member extends SnowFlake{
static already = {};
owner: Guild;
user: User;
roles: Role[] = [];
nick!: string;
[key: string]: any;
private constructor(memberjson: memberjson, owner: Guild) {
super(memberjson.id);
this.owner = owner;
if (this.localuser.userMap.has(memberjson.id)) {
this.user = this.localuser.userMap.get(memberjson.id) as User;
} else if (memberjson.user) {
this.user = new User(memberjson.user, owner.localuser);
} else {
throw new Error("Missing user object of this member");
}
private constructor(memberjson: memberjson, owner: Guild){
super(memberjson.id);
this.owner = owner;
if(this.localuser.userMap.has(memberjson.id)){
this.user = this.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){
this.user = new User(memberjson.user, owner.localuser);
}else{
throw new Error("Missing user object of this member");
}
for (const key of Object.keys(memberjson)) {
if (key === "guild" || key === "owner") {
continue;
}
for(const key of Object.keys(memberjson)){
if(key === "guild" || key === "owner"){
continue;
}
if (key === "roles") {
for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole);
if (!role) continue;
this.roles.push(role);
if(key === "roles"){
for(const strrole of memberjson.roles){
const role = this.guild.roleids.get(strrole);
if(!role)continue;
this.roles.push(role);
}
continue;
}
(this as any)[key] = (memberjson as any)[key];
}
if(this.localuser.userMap.has(this?.id)){
this.user = this.localuser.userMap.get(this?.id) as User;
}
this.roles.sort((a, b)=>{
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
});
}
continue;
get guild(){
return this.owner;
}
(this as any)[key] = (memberjson as any)[key];
get localuser(){
return this.guild.localuser;
}
if (this.localuser.userMap.has(this?.id)) {
this.user = this.localuser.userMap.get(this?.id) as User;
}
this.roles.sort((a, b) => {
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
});
}
get guild() {
return this.owner;
}
get localuser() {
return this.guild.localuser;
}
get info() {
return this.owner.info;
get info(){
return this.owner.info;
}
static async new(
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined> {
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined>{
let user: User;
if (owner.localuser.userMap.has(memberjson.id)) {
user = owner.localuser.userMap.get(memberjson.id) as User;
} else if (memberjson.user) {
user = new User(memberjson.user, owner.localuser);
} else {
throw new Error("missing user object of this member");
if(owner.localuser.userMap.has(memberjson.id)){
user = owner.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){
user = new User(memberjson.user, owner.localuser);
}else{
throw new Error("missing user object of this member");
}
if (user.members.has(owner)) {
let memb = user.members.get(owner);
if (memb === undefined) {
memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
} else if (memb instanceof Promise) {
return await memb; //I should do something else, though for now this is "good enough"
} else {
return memb;
if(user.members.has(owner)){
let memb = user.members.get(owner);
if(memb === undefined){
memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
}else if(memb instanceof Promise){
return await memb; //I should do something else, though for now this is "good enough"
}else{
return memb;
}
}else{
const memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
}
} else {
const memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
}
}
static async resolveMember(
}
static async resolveMember(
user: User,
guild: Guild
): Promise<Member | undefined> {
const maybe = user.members.get(guild);
if (!user.members.has(guild)) {
): Promise<Member | undefined>{
const maybe = user.members.get(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) {
return res(undefined);
} else {
const member = new Member(membjson, guild);
const map = guild.localuser.presences;
member.getPresence(map.get(member.id));
map.delete(member.id);
res(member);
return member;
if(membjson === undefined){
return res();
}else{
const member = new Member(membjson, guild);
const map = guild.localuser.presences;
member.getPresence(map.get(member.id));
map.delete(member.id);
res(member);
return member;
}
});
user.members.set(guild, promise);
}
if (maybe instanceof Promise) {
return await maybe;
} else {
return maybe;
}
}
public getPresence(presence: presencejson | undefined) {
this.user.getPresence(presence);
}
/**
});
user.members.set(guild, promise);
}
if(maybe instanceof Promise){
return await maybe;
}else{
return maybe;
}
}
public getPresence(presence: presencejson | undefined){
this.user.getPresence(presence);
}
/**
* @todo
*/
highInfo() {
fetch(
this.info.api +
highInfo(){
fetch(
this.info.api +
"/users/" +
this.id +
"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" +
this.guild.id,
{ headers: this.guild.headers }
);
}
hasRole(ID: string) {
console.log(this.roles, ID);
for (const thing of this.roles) {
if (thing.id === ID) {
{ headers: this.guild.headers }
);
}
hasRole(ID: string){
console.log(this.roles, ID);
for(const thing of this.roles){
if(thing.id === ID){
return true;
}
}
return false;
}
getColor() {
for (const thing of this.roles) {
const color = thing.getColor();
if (color) {
}
}
return false;
}
getColor(){
for(const thing of this.roles){
const color = thing.getColor();
if(color){
return color;
}
}
return "";
}
isAdmin() {
for (const role of this.roles) {
if (role.permissions.getPermission("ADMINISTRATOR")) {
}
}
return"";
}
isAdmin(){
for(const role of this.roles){
if(role.permissions.getPermission("ADMINISTRATOR")){
return true;
}
}
return this.guild.properties.owner_id === this.user.id;
}
bind(html: HTMLElement) {
if (html.tagName === "SPAN") {
if (!this) {
}
}
return this.guild.properties.owner_id === this.user.id;
}
bind(html: HTMLElement){
if(html.tagName === "SPAN"){
if(!this){
return;
}
/*
}
/*
if(this.error){
}
*/
html.style.color = this.getColor();
}
html.style.color = this.getColor();
}
//this.profileclick(html);
}
profileclick(/* html: HTMLElement */) {
//to be implemented
}
get name() {
return this.nick || this.user.username;
}
kick() {
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Kick " + this.name + " from " + this.guild.properties.name],
[
//this.profileclick(html);
}
profileclick(/* html: HTMLElement */){
//to be implemented
}
get name(){
return this.nick || this.user.username;
}
kick(){
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Kick " + this.name + " from " + this.guild.properties.name],
[
"textbox",
"Reason:",
"",
function (e: Event) {
reason = (e.target as HTMLInputElement).value;
function(e: Event){
reason = (e.target as HTMLInputElement).value;
},
],
[
],
[
"button",
"",
"submit",
() => {
this.kickAPI(reason);
menu.hide();
()=>{
this.kickAPI(reason);
menu.hide();
},
],
]);
menu.show();
}
kickAPI(reason: string) {
const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, {
method: "DELETE",
headers,
});
}
ban() {
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Ban " + this.name + " from " + this.guild.properties.name],
[
],
]);
menu.show();
}
kickAPI(reason: string){
const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, {
method: "DELETE",
headers,
});
}
ban(){
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Ban " + this.name + " from " + this.guild.properties.name],
[
"textbox",
"Reason:",
"",
function (e: Event) {
reason = (e.target as HTMLInputElement).value;
function(e: Event){
reason = (e.target as HTMLInputElement).value;
},
],
[
],
[
"button",
"",
"submit",
() => {
this.banAPI(reason);
menu.hide();
()=>{
this.banAPI(reason);
menu.hide();
},
],
]);
menu.show();
}
banAPI(reason: string) {
const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, {
method: "PUT",
headers,
});
}
hasPermission(name: string): boolean {
if (this.isAdmin()) {
],
]);
menu.show();
}
banAPI(reason: string){
const headers = structuredClone(this.guild.headers);
(headers as any)["x-audit-log-reason"] = reason;
fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, {
method: "PUT",
headers,
});
}
hasPermission(name: string): boolean{
if(this.isAdmin()){
return true;
}
for(const thing of this.roles){
if(thing.permissions.getPermission(name)){
return true;
}
for (const thing of this.roles) {
if (thing.permissions.getPermission(name)) {
return true;
}
}
return false;
}
}
export { Member };
}
}
return false;
}
}
export{ Member };

File diff suppressed because it is too large Load diff

View file

@ -1,347 +1,347 @@
class Permissions {
allow: bigint;
deny: bigint;
readonly hasDeny: boolean;
constructor(allow: string, deny: string = "") {
this.hasDeny = Boolean(deny);
try {
this.allow = BigInt(allow);
this.deny = BigInt(deny);
} catch {
this.allow = 0n;
this.deny = 0n;
console.error(
`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`
);
}
}
getPermissionbit(b: number, big: bigint): boolean {
return Boolean((big >> BigInt(b)) & 1n);
}
setPermissionbit(b: number, state: boolean, big: bigint): bigint {
const bit = 1n << BigInt(b);
return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
}
static map: {
class Permissions{
allow: bigint;
deny: bigint;
readonly hasDeny: boolean;
constructor(allow: string, deny: string = ""){
this.hasDeny = Boolean(deny);
try{
this.allow = BigInt(allow);
this.deny = BigInt(deny);
}catch{
this.allow = 0n;
this.deny = 0n;
console.error(
`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`
);
}
}
getPermissionbit(b: number, big: bigint): boolean{
return Boolean((big >> BigInt(b)) & 1n);
}
setPermissionbit(b: number, state: boolean, big: bigint): bigint{
const bit = 1n << BigInt(b);
return(big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
}
static map: {
[key: number | string]:
| { name: string; readableName: string; description: string }
| number;
};
static info: { name: string; readableName: string; description: string }[];
static makeMap() {
Permissions.info = [
//for people in the future, do not reorder these, the creation of the map realize on the order
{
name: "CREATE_INSTANT_INVITE",
readableName: "Create invite",
description: "Allows the user to create invites for the guild",
},
{
name: "KICK_MEMBERS",
readableName: "Kick members",
description: "Allows the user to kick members from the guild",
},
{
name: "BAN_MEMBERS",
readableName: "Ban members",
description: "Allows the user to ban members from the guild",
},
{
name: "ADMINISTRATOR",
readableName: "Administrator",
description:
static info: { name: string; readableName: string; description: string }[];
static makeMap(){
Permissions.info = [
//for people in the future, do not reorder these, the creation of the map realize on the order
{
name: "CREATE_INSTANT_INVITE",
readableName: "Create invite",
description: "Allows the user to create invites for the guild",
},
{
name: "KICK_MEMBERS",
readableName: "Kick members",
description: "Allows the user to kick members from the guild",
},
{
name: "BAN_MEMBERS",
readableName: "Ban members",
description: "Allows the user to ban members from the guild",
},
{
name: "ADMINISTRATOR",
readableName: "Administrator",
description:
"Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!",
},
{
name: "MANAGE_CHANNELS",
readableName: "Manage channels",
description: "Allows the user to manage and edit channels",
},
{
name: "MANAGE_GUILD",
readableName: "Manage guild",
description: "Allows management and editing of the guild",
},
{
name: "ADD_REACTIONS",
readableName: "Add reactions",
description: "Allows user to add reactions to messages",
},
{
name: "VIEW_AUDIT_LOG",
readableName: "View audit log",
description: "Allows the user to view the audit log",
},
{
name: "PRIORITY_SPEAKER",
readableName: "Priority speaker",
description: "Allows for using priority speaker in a voice channel",
},
{
name: "STREAM",
readableName: "Video",
description: "Allows the user to stream",
},
{
name: "VIEW_CHANNEL",
readableName: "View channels",
description: "Allows the user to view the channel",
},
{
name: "SEND_MESSAGES",
readableName: "Send messages",
description: "Allows user to send messages",
},
{
name: "SEND_TTS_MESSAGES",
readableName: "Send text-to-speech messages",
description: "Allows the user to send text-to-speech messages",
},
{
name: "MANAGE_MESSAGES",
readableName: "Manage messages",
description: "Allows the user to delete messages that aren't their own",
},
{
name: "EMBED_LINKS",
readableName: "Embed links",
description: "Allow links sent by this user to auto-embed",
},
{
name: "ATTACH_FILES",
readableName: "Attach files",
description: "Allows the user to attach files",
},
{
name: "READ_MESSAGE_HISTORY",
readableName: "Read message history",
description: "Allows user to read the message history",
},
{
name: "MENTION_EVERYONE",
readableName: "Mention @everyone, @here and all roles",
description: "Allows the user to mention everyone",
},
{
name: "USE_EXTERNAL_EMOJIS",
readableName: "Use external emojis",
description: "Allows the user to use external emojis",
},
{
name: "VIEW_GUILD_INSIGHTS",
readableName: "View guild insights",
description: "Allows the user to see guild insights",
},
{
name: "CONNECT",
readableName: "Connect",
description: "Allows the user to connect to a voice channel",
},
{
name: "SPEAK",
readableName: "Speak",
description: "Allows the user to speak in a voice channel",
},
{
name: "MUTE_MEMBERS",
readableName: "Mute members",
description: "Allows user to mute other members",
},
{
name: "DEAFEN_MEMBERS",
readableName: "Deafen members",
description: "Allows user to deafen other members",
},
{
name: "MOVE_MEMBERS",
readableName: "Move members",
description: "Allows the user to move members between voice channels",
},
{
name: "USE_VAD",
readableName: "Use voice activity detection",
description:
},
{
name: "MANAGE_CHANNELS",
readableName: "Manage channels",
description: "Allows the user to manage and edit channels",
},
{
name: "MANAGE_GUILD",
readableName: "Manage guild",
description: "Allows management and editing of the guild",
},
{
name: "ADD_REACTIONS",
readableName: "Add reactions",
description: "Allows user to add reactions to messages",
},
{
name: "VIEW_AUDIT_LOG",
readableName: "View audit log",
description: "Allows the user to view the audit log",
},
{
name: "PRIORITY_SPEAKER",
readableName: "Priority speaker",
description: "Allows for using priority speaker in a voice channel",
},
{
name: "STREAM",
readableName: "Video",
description: "Allows the user to stream",
},
{
name: "VIEW_CHANNEL",
readableName: "View channels",
description: "Allows the user to view the channel",
},
{
name: "SEND_MESSAGES",
readableName: "Send messages",
description: "Allows user to send messages",
},
{
name: "SEND_TTS_MESSAGES",
readableName: "Send text-to-speech messages",
description: "Allows the user to send text-to-speech messages",
},
{
name: "MANAGE_MESSAGES",
readableName: "Manage messages",
description: "Allows the user to delete messages that aren't their own",
},
{
name: "EMBED_LINKS",
readableName: "Embed links",
description: "Allow links sent by this user to auto-embed",
},
{
name: "ATTACH_FILES",
readableName: "Attach files",
description: "Allows the user to attach files",
},
{
name: "READ_MESSAGE_HISTORY",
readableName: "Read message history",
description: "Allows user to read the message history",
},
{
name: "MENTION_EVERYONE",
readableName: "Mention @everyone, @here and all roles",
description: "Allows the user to mention everyone",
},
{
name: "USE_EXTERNAL_EMOJIS",
readableName: "Use external emojis",
description: "Allows the user to use external emojis",
},
{
name: "VIEW_GUILD_INSIGHTS",
readableName: "View guild insights",
description: "Allows the user to see guild insights",
},
{
name: "CONNECT",
readableName: "Connect",
description: "Allows the user to connect to a voice channel",
},
{
name: "SPEAK",
readableName: "Speak",
description: "Allows the user to speak in a voice channel",
},
{
name: "MUTE_MEMBERS",
readableName: "Mute members",
description: "Allows user to mute other members",
},
{
name: "DEAFEN_MEMBERS",
readableName: "Deafen members",
description: "Allows user to deafen other members",
},
{
name: "MOVE_MEMBERS",
readableName: "Move members",
description: "Allows the user to move members between voice channels",
},
{
name: "USE_VAD",
readableName: "Use voice activity detection",
description:
"Allows users to speak in a voice channel by simply talking",
},
{
name: "CHANGE_NICKNAME",
readableName: "Change nickname",
description: "Allows the user to change their own nickname",
},
{
name: "MANAGE_NICKNAMES",
readableName: "Manage nicknames",
description: "Allows user to change nicknames of other members",
},
{
name: "MANAGE_ROLES",
readableName: "Manage roles",
description: "Allows user to edit and manage roles",
},
{
name: "MANAGE_WEBHOOKS",
readableName: "Manage webhooks",
description: "Allows management and editing of webhooks",
},
{
name: "MANAGE_GUILD_EXPRESSIONS",
readableName: "Manage expressions",
description: "Allows for managing emoji, stickers, and soundboards",
},
{
name: "USE_APPLICATION_COMMANDS",
readableName: "Use application commands",
description: "Allows the user to use application commands",
},
{
name: "REQUEST_TO_SPEAK",
readableName: "Request to speak",
description: "Allows user to request to speak in stage channel",
},
{
name: "MANAGE_EVENTS",
readableName: "Manage events",
description: "Allows user to edit and manage events",
},
{
name: "MANAGE_THREADS",
readableName: "Manage threads",
description:
},
{
name: "CHANGE_NICKNAME",
readableName: "Change nickname",
description: "Allows the user to change their own nickname",
},
{
name: "MANAGE_NICKNAMES",
readableName: "Manage nicknames",
description: "Allows user to change nicknames of other members",
},
{
name: "MANAGE_ROLES",
readableName: "Manage roles",
description: "Allows user to edit and manage roles",
},
{
name: "MANAGE_WEBHOOKS",
readableName: "Manage webhooks",
description: "Allows management and editing of webhooks",
},
{
name: "MANAGE_GUILD_EXPRESSIONS",
readableName: "Manage expressions",
description: "Allows for managing emoji, stickers, and soundboards",
},
{
name: "USE_APPLICATION_COMMANDS",
readableName: "Use application commands",
description: "Allows the user to use application commands",
},
{
name: "REQUEST_TO_SPEAK",
readableName: "Request to speak",
description: "Allows user to request to speak in stage channel",
},
{
name: "MANAGE_EVENTS",
readableName: "Manage events",
description: "Allows user to edit and manage events",
},
{
name: "MANAGE_THREADS",
readableName: "Manage threads",
description:
"Allows the user to delete and archive threads and view all private threads",
},
{
name: "CREATE_PUBLIC_THREADS",
readableName: "Create public threads",
description: "Allows the user to create public threads",
},
{
name: "CREATE_PRIVATE_THREADS",
readableName: "Create private threads",
description: "Allows the user to create private threads",
},
{
name: "USE_EXTERNAL_STICKERS",
readableName: "Use external stickers",
description: "Allows user to use external stickers",
},
{
name: "SEND_MESSAGES_IN_THREADS",
readableName: "Send messages in threads",
description: "Allows the user to send messages in threads",
},
{
name: "USE_EMBEDDED_ACTIVITIES",
readableName: "Use activities",
description: "Allows the user to use embedded activities",
},
{
name: "MODERATE_MEMBERS",
readableName: "Timeout members",
description:
},
{
name: "CREATE_PUBLIC_THREADS",
readableName: "Create public threads",
description: "Allows the user to create public threads",
},
{
name: "CREATE_PRIVATE_THREADS",
readableName: "Create private threads",
description: "Allows the user to create private threads",
},
{
name: "USE_EXTERNAL_STICKERS",
readableName: "Use external stickers",
description: "Allows user to use external stickers",
},
{
name: "SEND_MESSAGES_IN_THREADS",
readableName: "Send messages in threads",
description: "Allows the user to send messages in threads",
},
{
name: "USE_EMBEDDED_ACTIVITIES",
readableName: "Use activities",
description: "Allows the user to use embedded activities",
},
{
name: "MODERATE_MEMBERS",
readableName: "Timeout members",
description:
"Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels",
},
{
name: "VIEW_CREATOR_MONETIZATION_ANALYTICS",
readableName: "View creator monetization analytics",
description: "Allows for viewing role subscription insights",
},
{
name: "USE_SOUNDBOARD",
readableName: "Use soundboard",
description: "Allows for using soundboard in a voice channel",
},
{
name: "CREATE_GUILD_EXPRESSIONS",
readableName: "Create expressions",
description:
},
{
name: "VIEW_CREATOR_MONETIZATION_ANALYTICS",
readableName: "View creator monetization analytics",
description: "Allows for viewing role subscription insights",
},
{
name: "USE_SOUNDBOARD",
readableName: "Use soundboard",
description: "Allows for using soundboard in a voice channel",
},
{
name: "CREATE_GUILD_EXPRESSIONS",
readableName: "Create expressions",
description:
"Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.",
},
{
name: "CREATE_EVENTS",
readableName: "Create events",
description:
},
{
name: "CREATE_EVENTS",
readableName: "Create events",
description:
"Allows for creating scheduled events, and editing and deleting those created by the current user.",
},
{
name: "USE_EXTERNAL_SOUNDS",
readableName: "Use external sounds",
description:
},
{
name: "USE_EXTERNAL_SOUNDS",
readableName: "Use external sounds",
description:
"Allows the usage of custom soundboard sounds from other servers",
},
{
name: "SEND_VOICE_MESSAGES",
readableName: "Send voice messages",
description: "Allows sending voice messages",
},
{
name: "SEND_POLLS",
readableName: "Create polls",
description: "Allows sending polls",
},
{
name: "USE_EXTERNAL_APPS",
readableName: "Use external apps",
description:
},
{
name: "SEND_VOICE_MESSAGES",
readableName: "Send voice messages",
description: "Allows sending voice messages",
},
{
name: "SEND_POLLS",
readableName: "Create polls",
description: "Allows sending polls",
},
{
name: "USE_EXTERNAL_APPS",
readableName: "Use external apps",
description:
"Allows user-installed apps to send public responses. " +
"When disabled, users will still be allowed to use their apps but the responses will be ephemeral. " +
"This only applies to apps not also installed to the server.",
},
];
Permissions.map = {};
let i = 0;
for (const thing of Permissions.info) {
Permissions.map[i] = thing;
Permissions.map[thing.name] = i;
i++;
}
}
getPermission(name: string): number {
if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) {
return 1;
} else if (
this.getPermissionbit(Permissions.map[name] as number, this.deny)
) {
return -1;
} else {
return 0;
}
}
hasPermission(name: string): boolean {
if (this.deny) {
console.warn(
"This function may of been used in error, think about using getPermision instead"
);
}
if (this.getPermissionbit(Permissions.map[name] as number, this.allow))
return true;
if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR");
return false;
}
setPermission(name: string, setto: number): void {
const bit = Permissions.map[name] as number;
if (!bit) {
return console.error(
"Tried to set permission to " +
},
];
Permissions.map = {};
let i = 0;
for(const thing of Permissions.info){
Permissions.map[i] = thing;
Permissions.map[thing.name] = i;
i++;
}
}
getPermission(name: string): number{
if(this.getPermissionbit(Permissions.map[name] as number, this.allow)){
return 1;
}else if(
this.getPermissionbit(Permissions.map[name] as number, this.deny)
){
return-1;
}else{
return 0;
}
}
hasPermission(name: string): boolean{
if(this.deny){
console.warn(
"This function may of been used in error, think about using getPermision instead"
);
}
if(this.getPermissionbit(Permissions.map[name] as number, this.allow))
return true;
if(name != "ADMINISTRATOR")return this.hasPermission("ADMINISTRATOR");
return false;
}
setPermission(name: string, setto: number): void{
const bit = Permissions.map[name] as number;
if(!bit){
return console.error(
"Tried to set permission to " +
setto +
" for " +
name +
" but it doesn't exist"
);
}
);
}
if (setto === 0) {
this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow);
} else if (setto === 1) {
this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, true, this.allow);
} else if (setto === -1) {
this.deny = this.setPermissionbit(bit, true, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow);
} else {
console.error("invalid number entered:" + setto);
}
}
if(setto === 0){
this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow);
}else if(setto === 1){
this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, true, this.allow);
}else if(setto === -1){
this.deny = this.setPermissionbit(bit, true, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow);
}else{
console.error("invalid number entered:" + setto);
}
}
}
Permissions.makeMap();
export { Permissions };
export{ Permissions };

View file

@ -1,152 +1,152 @@
import { checkInstance, adduser } from "./login.js";
import{ checkInstance, adduser }from"./login.js";
const registerElement = document.getElementById("register");
if (registerElement) {
registerElement.addEventListener("submit", registertry);
if(registerElement){
registerElement.addEventListener("submit", registertry);
}
async function registertry(e: Event) {
e.preventDefault();
const elements = (e.target as HTMLFormElement)
.elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value;
const confirmPassword = (elements[4] as HTMLInputElement).value;
const dateofbirth = (elements[5] as HTMLInputElement).value;
const consent = (elements[6] as HTMLInputElement).checked;
const captchaKey = (elements[7] as HTMLInputElement)?.value;
async function registertry(e: Event){
e.preventDefault();
const elements = (e.target as HTMLFormElement)
.elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value;
const confirmPassword = (elements[4] as HTMLInputElement).value;
const dateofbirth = (elements[5] as HTMLInputElement).value;
const consent = (elements[6] as HTMLInputElement).checked;
const captchaKey = (elements[7] as HTMLInputElement)?.value;
if (password !== confirmPassword) {
(document.getElementById("wrong") as HTMLElement).textContent =
if(password !== confirmPassword){
(document.getElementById("wrong") as HTMLElement).textContent =
"Passwords don't match";
return;
return;
}
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
try{
const response = await fetch(apiurl + "/auth/register", {
body: JSON.stringify({
date_of_birth: dateofbirth,
email,
username,
password,
consent,
captcha_key: captchaKey,
}),
headers: {
"content-type": "application/json",
},
method: "POST",
});
const data = await response.json();
if(data.captcha_sitekey){
const capt = document.getElementById("h-captcha");
if(capt && !capt.children.length){
const capty = document.createElement("div");
capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", data.captcha_sitekey);
const script = document.createElement("script");
script.src = "https://js.hcaptcha.com/1/api.js";
capt.append(script);
capt.append(capty);
}else{
eval("hcaptcha.reset()");
}
return;
}
if(!data.token){
handleErrors(data.errors, elements);
}else{
adduser({
serverurls: instanceInfo,
email,
token: data.token,
}).username = username;
localStorage.setItem("token", data.token);
const redir = new URLSearchParams(window.location.search).get("goback");
window.location.href = redir ? redir : "/channels/@me";
}
}catch(error){
console.error("Registration failed:", error);
}
}
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
try {
const response = await fetch(apiurl + "/auth/register", {
body: JSON.stringify({
date_of_birth: dateofbirth,
email,
username,
password,
consent,
captcha_key: captchaKey,
}),
headers: {
"content-type": "application/json",
},
method: "POST",
});
const data = await response.json();
if (data.captcha_sitekey) {
const capt = document.getElementById("h-captcha");
if (capt && !capt.children.length) {
const capty = document.createElement("div");
capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", data.captcha_sitekey);
const script = document.createElement("script");
script.src = "https://js.hcaptcha.com/1/api.js";
capt.append(script);
capt.append(capty);
} else {
eval("hcaptcha.reset()");
}
return;
}
if (!data.token) {
handleErrors(data.errors, elements);
} else {
adduser({
serverurls: instanceInfo,
email,
token: data.token,
}).username = username;
localStorage.setItem("token", data.token);
const redir = new URLSearchParams(window.location.search).get("goback");
window.location.href = redir ? redir : "/channels/@me";
}
} catch (error) {
console.error("Registration failed:", error);
}
}
function handleErrors(errors: any, elements: HTMLFormControlsCollection) {
if (errors.consent) {
showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
} else if (errors.password) {
showError(
function handleErrors(errors: any, elements: HTMLFormControlsCollection){
if(errors.consent){
showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
}else if(errors.password){
showError(
elements[3] as HTMLElement,
"Password: " + errors.password._errors[0].message
);
} else if (errors.username) {
showError(
);
}else if(errors.username){
showError(
elements[2] as HTMLElement,
"Username: " + errors.username._errors[0].message
);
} else if (errors.email) {
showError(
);
}else if(errors.email){
showError(
elements[1] as HTMLElement,
"Email: " + errors.email._errors[0].message
);
} else if (errors.date_of_birth) {
showError(
);
}else if(errors.date_of_birth){
showError(
elements[5] as HTMLElement,
"Date of Birth: " + errors.date_of_birth._errors[0].message
);
} else {
(document.getElementById("wrong") as HTMLElement).textContent =
);
}else{
(document.getElementById("wrong") as HTMLElement).textContent =
errors[Object.keys(errors)[0]]._errors[0].message;
}
}
}
function showError(element: HTMLElement, message: string) {
const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName(
"suberror"
)[0] as HTMLElement;
if (!errorElement) {
const div = document.createElement("div");
div.classList.add("suberror", "suberrora");
parent.append(div);
errorElement = div;
} else {
errorElement.classList.remove("suberror");
setTimeout(() => {
errorElement.classList.add("suberror");
}, 100);
}
errorElement.textContent = message;
function showError(element: HTMLElement, message: string){
const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName(
"suberror"
)[0] as HTMLElement;
if(!errorElement){
const div = document.createElement("div");
div.classList.add("suberror", "suberrora");
parent.append(div);
errorElement = div;
}else{
errorElement.classList.remove("suberror");
setTimeout(()=>{
errorElement.classList.add("suberror");
}, 100);
}
errorElement.textContent = message;
}
let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null;
async function tosLogic() {
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
const response = await fetch(apiurl.toString() + "/ping");
const data = await response.json();
const tosPage = data.instance.tosPage;
async function tosLogic(){
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api);
const response = await fetch(apiurl.toString() + "/ping");
const data = await response.json();
const tosPage = data.instance.tosPage;
if (tosPage) {
if(tosPage){
document.getElementById("TOSbox")!.innerHTML =
'I agree to the <a href="" id="TOSa">Terms of Service</a>:';
"I agree to the <a href=\"\" id=\"TOSa\">Terms of Service</a>:";
TOSa = document.getElementById("TOSa") as HTMLAnchorElement;
TOSa.href = tosPage;
} else {
}else{
document.getElementById("TOSbox")!.textContent =
"This instance has no Terms of Service, accept ToS anyways:";
TOSa = null;
}
console.log(tosPage);
}
console.log(tosPage);
}
tosLogic();
(checkInstance as any)["alt"] = tosLogic;
(checkInstance as any).alt = tosLogic;

View file

@ -1,48 +1,48 @@
import { Permissions } from "./permissions.js";
import { Localuser } from "./localuser.js";
import { Guild } from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { rolesjson } from "./jsontypes.js";
class Role extends SnowFlake {
permissions: Permissions;
owner: Guild;
color!: number;
name!: string;
info: Guild["info"];
hoist!: boolean;
icon!: string;
mentionable!: boolean;
unicode_emoji!: string;
headers: Guild["headers"];
constructor(json: rolesjson, owner: Guild) {
super(json.id);
this.headers = owner.headers;
this.info = owner.info;
for (const thing of Object.keys(json)) {
if (thing === "id") {
continue;
import{ Permissions }from"./permissions.js";
import{ Localuser }from"./localuser.js";
import{ Guild }from"./guild.js";
import{ SnowFlake }from"./snowflake.js";
import{ rolesjson }from"./jsontypes.js";
class Role extends SnowFlake{
permissions: Permissions;
owner: Guild;
color!: number;
name!: string;
info: Guild["info"];
hoist!: boolean;
icon!: string;
mentionable!: boolean;
unicode_emoji!: string;
headers: Guild["headers"];
constructor(json: rolesjson, owner: Guild){
super(json.id);
this.headers = owner.headers;
this.info = owner.info;
for(const thing of Object.keys(json)){
if(thing === "id"){
continue;
}
(this as any)[thing] = (json as any)[thing];
}
this.permissions = new Permissions(json.permissions);
this.owner = owner;
}
get guild(): Guild{
return this.owner;
}
get localuser(): Localuser{
return this.guild.localuser;
}
getColor(): string | null{
if(this.color === 0){
return null;
}
return`#${this.color.toString(16)}`;
}
}
(this as any)[thing] = (json as any)[thing];
}
this.permissions = new Permissions(json.permissions);
this.owner = owner;
}
get guild(): Guild {
return this.owner;
}
get localuser(): Localuser {
return this.guild.localuser;
}
getColor(): string | null {
if (this.color === 0) {
return null;
}
return `#${this.color.toString(16)}`;
}
}
export { Role };
import { Options } from "./settings.js";
class PermissionToggle implements OptionsElement<number> {
export{ Role };
import{ Options }from"./settings.js";
class PermissionToggle implements OptionsElement<number>{
readonly rolejson: {
name: string;
readableName: string;
@ -52,76 +52,76 @@ class PermissionToggle implements OptionsElement<number> {
owner: Options;
value!: number;
constructor(
roleJSON: PermissionToggle["rolejson"],
permissions: Permissions,
owner: Options
) {
this.rolejson = roleJSON;
this.permissions = permissions;
this.owner = owner;
roleJSON: PermissionToggle["rolejson"],
permissions: Permissions,
owner: Options
){
this.rolejson = roleJSON;
this.permissions = permissions;
this.owner = owner;
}
watchForChange() {}
generateHTML(): HTMLElement {
const div = document.createElement("div");
div.classList.add("setting");
const name = document.createElement("span");
name.textContent = this.rolejson.readableName;
name.classList.add("settingsname");
div.append(name);
watchForChange(){}
generateHTML(): HTMLElement{
const div = document.createElement("div");
div.classList.add("setting");
const name = document.createElement("span");
name.textContent = this.rolejson.readableName;
name.classList.add("settingsname");
div.append(name);
div.append(this.generateCheckbox());
const p = document.createElement("p");
p.textContent = this.rolejson.description;
div.appendChild(p);
return div;
div.append(this.generateCheckbox());
const p = document.createElement("p");
p.textContent = this.rolejson.description;
div.appendChild(p);
return div;
}
generateCheckbox(): HTMLElement {
const div = document.createElement("div");
div.classList.add("tritoggle");
const state = this.permissions.getPermission(this.rolejson.name);
generateCheckbox(): HTMLElement{
const div = document.createElement("div");
div.classList.add("tritoggle");
const state = this.permissions.getPermission(this.rolejson.name);
const on = document.createElement("input");
on.type = "radio";
on.name = this.rolejson.name;
div.append(on);
if (state === 1) {
on.checked = true;
}
on.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 1);
this.owner.changed();
};
const on = document.createElement("input");
on.type = "radio";
on.name = this.rolejson.name;
div.append(on);
if(state === 1){
on.checked = true;
}
on.onclick = _=>{
this.permissions.setPermission(this.rolejson.name, 1);
this.owner.changed();
};
const no = document.createElement("input");
no.type = "radio";
no.name = this.rolejson.name;
div.append(no);
if (state === 0) {
no.checked = true;
const no = document.createElement("input");
no.type = "radio";
no.name = this.rolejson.name;
div.append(no);
if(state === 0){
no.checked = true;
}
no.onclick = _=>{
this.permissions.setPermission(this.rolejson.name, 0);
this.owner.changed();
};
if(this.permissions.hasDeny){
const off = document.createElement("input");
off.type = "radio";
off.name = this.rolejson.name;
div.append(off);
if(state === -1){
off.checked = true;
}
off.onclick = _=>{
this.permissions.setPermission(this.rolejson.name, -1);
this.owner.changed();
};
}
return div;
}
no.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 0);
this.owner.changed();
};
if (this.permissions.hasDeny) {
const off = document.createElement("input");
off.type = "radio";
off.name = this.rolejson.name;
div.append(off);
if (state === -1) {
off.checked = true;
}
off.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, -1);
this.owner.changed();
};
}
return div;
}
submit() {}
}
import { OptionsElement, Buttons } from "./settings.js";
class RoleList extends Buttons {
submit(){}
}
import{ OptionsElement, Buttons }from"./settings.js";
class RoleList extends Buttons{
readonly permissions: [Role, Permissions][];
permission: Permissions;
readonly guild: Guild;
@ -131,50 +131,50 @@ class PermissionToggle implements OptionsElement<number> {
onchange: Function;
curid!: string;
constructor(
permissions: [Role, Permissions][],
guild: Guild,
onchange: Function,
channel = false
) {
super("Roles");
this.guild = guild;
this.permissions = permissions;
this.channel = channel;
this.onchange = onchange;
const options = new Options("", this);
if (channel) {
this.permission = new Permissions("0", "0");
} else {
this.permission = new Permissions("0");
permissions: [Role, Permissions][],
guild: Guild,
onchange: Function,
channel = false
){
super("Roles");
this.guild = guild;
this.permissions = permissions;
this.channel = channel;
this.onchange = onchange;
const options = new Options("", this);
if(channel){
this.permission = new Permissions("0", "0");
}else{
this.permission = new Permissions("0");
}
for(const thing of Permissions.info){
options.options.push(
new PermissionToggle(thing, this.permission, options)
);
}
for(const i of permissions){
console.log(i);
this.buttons.push([i[0].name, i[0].id]);
}
this.options = options;
}
for (const thing of Permissions.info) {
options.options.push(
new PermissionToggle(thing, this.permission, options)
);
handleString(str: string): HTMLElement{
this.curid = str;
const arr = this.permissions.find(_=>_[0].id === str);
if(arr){
const perm = arr[1];
this.permission.deny = perm.deny;
this.permission.allow = perm.allow;
const role = this.permissions.find(e=>e[0].id === str);
if(role){
this.options.name = role[0].name;
this.options.haschanged = false;
}
}
return this.options.generateHTML();
}
for (const i of permissions) {
console.log(i);
this.buttons.push([i[0].name, i[0].id]);
save(){
this.onchange(this.curid, this.permission);
}
this.options = options;
}
handleString(str: string): HTMLElement {
this.curid = str;
const arr = this.permissions.find((_) => _[0].id === str);
if (arr) {
const perm = arr[1];
this.permission.deny = perm.deny;
this.permission.allow = perm.allow;
const role = this.permissions.find((e) => e[0].id === str);
if (role) {
this.options.name = role[0].name;
this.options.haschanged = false;
}
}
return this.options.generateHTML();
}
save() {
this.onchange(this.curid, this.permission);
}
}
export { RoleList };
}
export{ RoleList };

View file

@ -1,96 +1,96 @@
function deleteoldcache() {
caches.delete("cache");
console.log("this ran :P");
function deleteoldcache(){
caches.delete("cache");
console.log("this ran :P");
}
async function putInCache(request: URL | RequestInfo, response: Response) {
console.log(request, response);
const cache = await caches.open("cache");
console.log("Grabbed");
try {
console.log(await cache.put(request, response));
} catch (error) {
console.error(error);
}
async function putInCache(request: URL | RequestInfo, response: Response){
console.log(request, response);
const cache = await caches.open("cache");
console.log("Grabbed");
try{
console.log(await cache.put(request, response));
}catch(error){
console.error(error);
}
}
console.log("test");
let lastcache: string;
self.addEventListener("activate", async () => {
console.log("test2");
checkCache();
});
async function checkCache() {
if (checkedrecently) {
return;
}
const promise = await caches.match("/getupdates");
if (promise) {
lastcache = await promise.text();
}
console.log(lastcache);
fetch("/getupdates").then(async (data) => {
const text = await data.clone().text();
console.log(text, lastcache);
if (lastcache !== text) {
deleteoldcache();
putInCache("/getupdates", data.clone());
}
checkedrecently = true;
setTimeout((_: any) => {
checkedrecently = false;
}, 1000 * 60 * 30);
self.addEventListener("activate", async ()=>{
console.log("test2");
checkCache();
});
async function checkCache(){
if(checkedrecently){
return;
}
const promise = await caches.match("/getupdates");
if(promise){
lastcache = await promise.text();
}
console.log(lastcache);
fetch("/getupdates").then(async data=>{
const text = await data.clone().text();
console.log(text, lastcache);
if(lastcache !== text){
deleteoldcache();
putInCache("/getupdates", data.clone());
}
checkedrecently = true;
setTimeout((_: any)=>{
checkedrecently = false;
}, 1000 * 60 * 30);
});
}
var checkedrecently = false;
function samedomain(url: string | URL) {
return new URL(url).origin === self.origin;
function samedomain(url: string | URL){
return new URL(url).origin === self.origin;
}
function isindexhtml(url: string | URL) {
console.log(url);
if (new URL(url).pathname.startsWith("/channels")) {
return true;
}
return false;
function isindexhtml(url: string | URL){
console.log(url);
if(new URL(url).pathname.startsWith("/channels")){
return true;
}
return false;
}
async function getfile(event: {
request: { url: URL | RequestInfo; clone: () => string | URL | Request };
}) {
checkCache();
if (!samedomain(event.request.url.toString())) {
return await fetch(event.request.clone());
}
const responseFromCache = await caches.match(event.request.url);
console.log(responseFromCache, caches);
if (responseFromCache) {
console.log("cache hit");
return responseFromCache;
}
if (isindexhtml(event.request.url.toString())) {
console.log("is index.html");
const responseFromCache = await caches.match("/index.html");
if (responseFromCache) {
console.log("cache hit");
return responseFromCache;
}
const responseFromNetwork = await fetch("/index.html");
await putInCache("/index.html", responseFromNetwork.clone());
return responseFromNetwork;
}
const responseFromNetwork = await fetch(event.request.clone());
console.log(event.request.clone());
await putInCache(event.request.clone(), responseFromNetwork.clone());
try {
return responseFromNetwork;
} catch (e) {
console.error(e);
return e;
}
}
self.addEventListener("fetch", (event: any) => {
try {
event.respondWith(getfile(event));
} catch (e) {
console.error(e);
}){
checkCache();
if(!samedomain(event.request.url.toString())){
return await fetch(event.request.clone());
}
const responseFromCache = await caches.match(event.request.url);
console.log(responseFromCache, caches);
if(responseFromCache){
console.log("cache hit");
return responseFromCache;
}
if(isindexhtml(event.request.url.toString())){
console.log("is index.html");
const responseFromCache = await caches.match("/index.html");
if(responseFromCache){
console.log("cache hit");
return responseFromCache;
}
const responseFromNetwork = await fetch("/index.html");
await putInCache("/index.html", responseFromNetwork.clone());
return responseFromNetwork;
}
const responseFromNetwork = await fetch(event.request.clone());
console.log(event.request.clone());
await putInCache(event.request.clone(), responseFromNetwork.clone());
try{
return responseFromNetwork;
}catch(e){
console.error(e);
return e;
}
}
self.addEventListener("fetch", (event: any)=>{
try{
event.respondWith(getfile(event));
}catch(e){
console.error(e);
}
});

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,489 +1,489 @@
import { Member } from "./member.js";
import { MarkDown } from "./markdown.js";
import { Contextmenu } from "./contextmenu.js";
import { Localuser } from "./localuser.js";
import { Guild } from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { presencejson, userjson } from "./jsontypes.js";
import{ Member }from"./member.js";
import{ MarkDown }from"./markdown.js";
import{ Contextmenu }from"./contextmenu.js";
import{ Localuser }from"./localuser.js";
import{ Guild }from"./guild.js";
import{ SnowFlake }from"./snowflake.js";
import{ presencejson, userjson }from"./jsontypes.js";
class User extends SnowFlake {
owner: Localuser;
hypotheticalpfp!: boolean;
avatar!: string | null;
username!: string;
nickname: string | null = null;
relationshipType: 0 | 1 | 2 | 3 | 4 = 0;
bio!: MarkDown;
discriminator!: string;
pronouns!: string;
bot!: boolean;
public_flags!: number;
accent_color!: number;
banner: string | undefined;
hypotheticalbanner!: boolean;
premium_since!: string;
premium_type!: number;
theme_colors!: string;
badge_ids!: string[];
members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> =
new WeakMap();
class User extends SnowFlake{
owner: Localuser;
hypotheticalpfp!: boolean;
avatar!: string | null;
username!: string;
nickname: string | null = null;
relationshipType: 0 | 1 | 2 | 3 | 4 = 0;
bio!: MarkDown;
discriminator!: string;
pronouns!: string;
bot!: boolean;
public_flags!: number;
accent_color!: number;
banner: string | undefined;
hypotheticalbanner!: boolean;
premium_since!: string;
premium_type!: number;
theme_colors!: string;
badge_ids!: string[];
members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> =
new WeakMap();
private 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 (!owner) {
console.error("missing localuser");
if(!owner){
console.error("missing localuser");
}
if (dontclone) {
for (const key of Object.keys(userjson)) {
if (key === "bio") {
this.bio = new MarkDown(userjson[key], this.localuser);
continue;
}
if (key === "id") {
continue;
}
(this as any)[key] = (userjson as any)[key];
}
this.hypotheticalpfp = false;
} else {
return User.checkuser(userjson, owner);
}
}
clone(): User {
return new User(
{
username: this.username,
id: this.id + "#clone",
public_flags: this.public_flags,
discriminator: this.discriminator,
avatar: this.avatar,
accent_color: this.accent_color,
banner: this.banner,
bio: this.bio.rawString,
premium_since: this.premium_since,
premium_type: this.premium_type,
bot: this.bot,
theme_colors: this.theme_colors,
pronouns: this.pronouns,
badge_ids: this.badge_ids,
},
this.owner
);
}
public getPresence(presence: presencejson | undefined): void {
if (presence) {
this.setstatus(presence.status);
} else {
this.setstatus("offline");
}
}
setstatus(status: string): void {
this.status = status;
}
async getStatus(): Promise<string> {
return this.status || "offline";
if(dontclone){
for(const key of Object.keys(userjson)){
if(key === "bio"){
this.bio = new MarkDown(userjson[key], this.localuser);
continue;
}
if(key === "id"){
continue;
}
(this as any)[key] = (userjson as any)[key];
}
this.hypotheticalpfp = false;
}else{
return User.checkuser(userjson, owner);
}
}
static contextmenu = new Contextmenu<User, Member | undefined>("User Menu");
clone(): User{
return new User(
{
username: this.username,
id: this.id + "#clone",
public_flags: this.public_flags,
discriminator: this.discriminator,
avatar: this.avatar,
accent_color: this.accent_color,
banner: this.banner,
bio: this.bio.rawString,
premium_since: this.premium_since,
premium_type: this.premium_type,
bot: this.bot,
theme_colors: this.theme_colors,
pronouns: this.pronouns,
badge_ids: this.badge_ids,
},
this.owner
);
}
static setUpContextMenu(): void {
this.contextmenu.addbutton("Copy user id", function (this: User) {
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Message user", function (this: User) {
fetch(this.info.api + "/users/@me/channels", {
public getPresence(presence: presencejson | undefined): void{
if(presence){
this.setstatus(presence.status);
}else{
this.setstatus("offline");
}
}
setstatus(status: string): void{
this.status = status;
}
async getStatus(): Promise<string>{
return this.status || "offline";
}
static contextmenu = new Contextmenu<User, Member | undefined>("User Menu");
static setUpContextMenu(): void{
this.contextmenu.addbutton("Copy user id", function(this: User){
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Message user", function(this: User){
fetch(this.info.api + "/users/@me/channels", {
method: "POST",
body: JSON.stringify({ recipients: [this.id] }),
headers: this.localuser.headers,
})
.then((res) => res.json())
.then((json) => {
this.localuser.goToChannel(json.id);
})
.then(res=>res.json())
.then(json=>{
this.localuser.goToChannel(json.id);
});
});
this.contextmenu.addbutton(
"Block user",
function (this: User) {
});
this.contextmenu.addbutton(
"Block user",
function(this: User){
this.block();
},
null,
function () {
},
null,
function(){
return this.relationshipType !== 2;
}
);
}
);
this.contextmenu.addbutton(
"Unblock user",
function (this: User) {
this.contextmenu.addbutton(
"Unblock user",
function(this: User){
this.unblock();
},
null,
function () {
},
null,
function(){
return this.relationshipType === 2;
}
);
this.contextmenu.addbutton("Friend request", function (this: User) {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
}
);
this.contextmenu.addbutton("Friend request", function(this: User){
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT",
headers: this.owner.headers,
body: JSON.stringify({
type: 1,
type: 1,
}),
});
});
this.contextmenu.addbutton(
"Kick member",
function (this: User, member: Member | undefined) {
});
});
this.contextmenu.addbutton(
"Kick member",
(this: User, member: Member | undefined)=>{
member?.kick();
},
null,
(member) => {
if (!member) return false;
},
null,
member=>{
if(!member)return false;
const us = member.guild.member;
if (member.id === us.id) {
return false;
if(member.id === us.id){
return false;
}
if (member.id === member.guild.properties.owner_id) {
return false;
if(member.id === member.guild.properties.owner_id){
return false;
}
return us.hasPermission("KICK_MEMBERS") || false;
}
);
this.contextmenu.addbutton(
"Ban member",
function (this: User, member: Member | undefined) {
}
);
this.contextmenu.addbutton(
"Ban member",
(this: User, member: Member | undefined)=>{
member?.ban();
},
null,
(member) => {
if (!member) return false;
},
null,
member=>{
if(!member)return false;
const us = member.guild.member;
if (member.id === us.id) {
return false;
if(member.id === us.id){
return false;
}
if (member.id === member.guild.properties.owner_id) {
return false;
if(member.id === member.guild.properties.owner_id){
return false;
}
return us.hasPermission("BAN_MEMBERS") || false;
}
);
}
}
);
}
static checkuser(user: User | userjson, owner: Localuser): User {
if (owner.userMap.has(user.id)) {
return owner.userMap.get(user.id) as User;
} else {
const tempuser = new User(user as userjson, owner, true);
owner.userMap.set(user.id, tempuser);
return tempuser;
}
}
static checkuser(user: User | userjson, owner: Localuser): User{
if(owner.userMap.has(user.id)){
return owner.userMap.get(user.id) as User;
}else{
const tempuser = new User(user as userjson, owner, true);
owner.userMap.set(user.id, tempuser);
return tempuser;
}
}
get info() {
return this.owner.info;
}
get info(){
return this.owner.info;
}
get localuser() {
return this.owner;
}
get localuser(){
return this.owner;
}
get name() {
return this.username;
}
get name(){
return this.username;
}
async resolvemember(guild: Guild): Promise<Member | undefined> {
return await Member.resolveMember(this, guild);
async resolvemember(guild: Guild): Promise<Member | undefined>{
return await Member.resolveMember(this, guild);
}
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());
}
async getBadge(id: string): Promise<any>{
if(this.localuser.badges.has(id)){
return this.localuser.badges.get(id);
}else{
if(this.resolving){
await this.resolving;
return this.localuser.badges.get(id);
}
const prom = await this.getUserProfile();
this.resolving = prom;
const badges = prom.badges;
this.resolving = false;
for(const badge of badges){
this.localuser.badges.set(badge.id, badge);
}
return this.localuser.badges.get(id);
}
}
buildpfp(): HTMLImageElement{
const pfp = document.createElement("img");
pfp.loading = "lazy";
pfp.src = this.getpfpsrc();
pfp.classList.add("pfp");
pfp.classList.add("userid:" + this.id);
return pfp;
}
async buildstatuspfp(): Promise<HTMLDivElement>{
const div = document.createElement("div");
div.style.position = "relative";
const pfp = this.buildpfp();
div.append(pfp);
const status = document.createElement("div");
status.classList.add("statusDiv");
switch(await this.getStatus()){
case"offline":
status.classList.add("offlinestatus");
break;
case"online":
default:
status.classList.add("onlinestatus");
break;
}
div.append(status);
return div;
}
userupdate(json: userjson): void{
if(json.avatar !== this.avatar){
this.changepfp(json.avatar);
}
}
bind(html: HTMLElement, guild: Guild | null = null, error = true): void{
if(guild && guild.id !== "@me"){
Member.resolveMember(this, guild)
.then(member=>{
User.contextmenu.bindContextmenu(html, this, member);
if(member === undefined && error){
const errorSpan = document.createElement("span");
errorSpan.textContent = "!";
errorSpan.classList.add("membererror");
html.after(errorSpan);
return;
}
if(member){
member.bind(html);
}
})
.catch(err=>{
console.log(err);
});
}
if(guild){
this.profileclick(html, guild);
}else{
this.profileclick(html);
}
}
async getUserProfile(): Promise<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());
}
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, localuser);
}
async getBadge(id: string): Promise<any> {
if (this.localuser.badges.has(id)) {
return this.localuser.badges.get(id);
} else {
if (this.resolving) {
await this.resolving;
return this.localuser.badges.get(id);
}
changepfp(update: string | null): void{
this.avatar = update;
this.hypotheticalpfp = false;
const src = this.getpfpsrc();
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach(
element=>{
(element as HTMLImageElement).src = src;
}
);
}
const prom = await this.getUserProfile();
this.resolving = prom;
const badges = prom.badges;
this.resolving = false;
for (const badge of badges) {
this.localuser.badges.set(badge.id, badge);
}
return this.localuser.badges.get(id);
}
}
block(): void{
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT",
headers: this.owner.headers,
body: JSON.stringify({
type: 2,
}),
});
this.relationshipType = 2;
const channel = this.localuser.channelfocus;
if(channel){
for(const message of channel.messages){
message[1].generateMessage();
}
}
}
buildpfp(): HTMLImageElement {
const pfp = document.createElement("img");
pfp.loading = "lazy";
pfp.src = this.getpfpsrc();
pfp.classList.add("pfp");
pfp.classList.add("userid:" + this.id);
return pfp;
}
unblock(): void{
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "DELETE",
headers: this.owner.headers,
});
this.relationshipType = 0;
const channel = this.localuser.channelfocus;
if(channel){
for(const message of channel.messages){
message[1].generateMessage();
}
}
}
async buildstatuspfp(): Promise<HTMLDivElement> {
const div = document.createElement("div");
div.style.position = "relative";
const pfp = this.buildpfp();
div.append(pfp);
const status = document.createElement("div");
status.classList.add("statusDiv");
switch (await this.getStatus()) {
case "offline":
status.classList.add("offlinestatus");
break;
case "online":
default:
status.classList.add("onlinestatus");
break;
}
div.append(status);
return div;
}
getpfpsrc(): string{
if(this.hypotheticalpfp && this.avatar){
return this.avatar;
}
if(this.avatar !== null){
return`${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${
this.avatar
}.png`;
}else{
const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n);
return`${this.info.cdn}/embed/avatars/${int}.png`;
}
}
userupdate(json: userjson): void {
if (json.avatar !== this.avatar) {
this.changepfp(json.avatar);
}
}
async buildprofile(
x: number,
y: number,
guild: Guild | null = null
): Promise<HTMLDivElement>{
if(Contextmenu.currentmenu != ""){
Contextmenu.currentmenu.remove();
}
bind(html: HTMLElement, guild: Guild | null = null, error = true): void {
if (guild && guild.id !== "@me") {
Member.resolveMember(this, guild)
.then((member) => {
User.contextmenu.bindContextmenu(html, this, member);
if (member === undefined && error) {
const errorSpan = document.createElement("span");
errorSpan.textContent = "!";
errorSpan.classList.add("membererror");
html.after(errorSpan);
return;
}
if (member) {
member.bind(html);
}
})
.catch((err) => {
console.log(err);
});
}
if (guild) {
this.profileclick(html, guild);
} else {
this.profileclick(html);
}
}
const div = document.createElement("div");
static async resolve(id: string, localuser: Localuser): Promise<User> {
const json = await fetch(
localuser.info.api.toString() + "/users/" + id + "/profile",
{ headers: localuser.headers }
).then((res) => res.json());
return new User(json, localuser);
}
if(this.accent_color){
div.style.setProperty(
"--accent_color",
`#${this.accent_color.toString(16).padStart(6, "0")}`
);
}else{
div.style.setProperty("--accent_color", "transparent");
}
if(this.banner){
const banner = document.createElement("img");
let src: string;
if(!this.hypotheticalbanner){
src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${
this.banner
}.png`;
}else{
src = this.banner;
}
banner.src = src;
banner.classList.add("banner");
div.append(banner);
}
if(x !== -1){
div.style.left = `${x}px`;
div.style.top = `${y}px`;
div.classList.add("profile", "flexttb");
}else{
this.setstatus("online");
div.classList.add("hypoprofile", "flexttb");
}
const badgediv = document.createElement("div");
badgediv.classList.add("badges");
(async ()=>{
if(!this.badge_ids)return;
for(const id of this.badge_ids){
const badgejson = await this.getBadge(id);
if(badgejson){
const badge = document.createElement(badgejson.link ? "a" : "div");
badge.classList.add("badge");
const img = document.createElement("img");
img.src = badgejson.icon;
badge.append(img);
const span = document.createElement("span");
span.textContent = badgejson.description;
badge.append(span);
if(badge instanceof HTMLAnchorElement){
badge.href = badgejson.link;
}
badgediv.append(badge);
}
}
})();
const pfp = await this.buildstatuspfp();
div.appendChild(pfp);
const userbody = document.createElement("div");
userbody.classList.add("infosection");
div.appendChild(userbody);
const usernamehtml = document.createElement("h2");
usernamehtml.textContent = this.username;
userbody.appendChild(usernamehtml);
userbody.appendChild(badgediv);
const discrimatorhtml = document.createElement("h3");
discrimatorhtml.classList.add("tag");
discrimatorhtml.textContent = `${this.username}#${this.discriminator}`;
userbody.appendChild(discrimatorhtml);
changepfp(update: string | null): void {
this.avatar = update;
this.hypotheticalpfp = false;
const src = this.getpfpsrc();
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach(
(element) => {
(element as HTMLImageElement).src = src;
}
);
}
const pronounshtml = document.createElement("p");
pronounshtml.textContent = this.pronouns;
pronounshtml.classList.add("pronouns");
userbody.appendChild(pronounshtml);
block(): void {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT",
headers: this.owner.headers,
body: JSON.stringify({
type: 2,
}),
});
this.relationshipType = 2;
const channel = this.localuser.channelfocus;
if (channel) {
for (const message of channel.messages) {
message[1].generateMessage();
}
}
}
const rule = document.createElement("hr");
userbody.appendChild(rule);
const biohtml = this.bio.makeHTML();
userbody.appendChild(biohtml);
if(guild){
Member.resolveMember(this, guild).then(member=>{
if(!member)return;
const roles = document.createElement("div");
roles.classList.add("rolesbox");
for(const role of member.roles){
const roleDiv = document.createElement("div");
roleDiv.classList.add("rolediv");
const color = document.createElement("div");
roleDiv.append(color);
color.style.setProperty(
"--role-color",
`#${role.color.toString(16).padStart(6, "0")}`
);
color.classList.add("colorrolediv");
const span = document.createElement("span");
roleDiv.append(span);
span.textContent = role.name;
roles.append(roleDiv);
}
userbody.append(roles);
});
}
if(x !== -1){
Contextmenu.currentmenu = div;
document.body.appendChild(div);
Contextmenu.keepOnScreen(div);
}
return div;
}
unblock(): void {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "DELETE",
headers: this.owner.headers,
});
this.relationshipType = 0;
const channel = this.localuser.channelfocus;
if (channel) {
for (const message of channel.messages) {
message[1].generateMessage();
}
}
}
profileclick(obj: HTMLElement, guild?: Guild): void{
obj.onclick = (e: MouseEvent)=>{
this.buildprofile(e.clientX, e.clientY, guild);
e.stopPropagation();
};
}
}
getpfpsrc(): string {
if (this.hypotheticalpfp && this.avatar) {
return this.avatar;
}
if (this.avatar !== null) {
return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${
this.avatar
}.png`;
} else {
const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n);
return `${this.info.cdn}/embed/avatars/${int}.png`;
}
}
async buildprofile(
x: number,
y: number,
guild: Guild | null = null
): Promise<HTMLDivElement> {
if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove();
}
const div = document.createElement("div");
if (this.accent_color) {
div.style.setProperty(
"--accent_color",
`#${this.accent_color.toString(16).padStart(6, "0")}`
);
} else {
div.style.setProperty("--accent_color", "transparent");
}
if (this.banner) {
const banner = document.createElement("img");
let src: string;
if (!this.hypotheticalbanner) {
src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${
this.banner
}.png`;
} else {
src = this.banner;
}
banner.src = src;
banner.classList.add("banner");
div.append(banner);
}
if (x !== -1) {
div.style.left = `${x}px`;
div.style.top = `${y}px`;
div.classList.add("profile", "flexttb");
} else {
this.setstatus("online");
div.classList.add("hypoprofile", "flexttb");
}
const badgediv = document.createElement("div");
badgediv.classList.add("badges");
(async () => {
if (!this.badge_ids) return;
for (const id of this.badge_ids) {
const badgejson = await this.getBadge(id);
if (badgejson) {
const badge = document.createElement(badgejson.link ? "a" : "div");
badge.classList.add("badge");
const img = document.createElement("img");
img.src = badgejson.icon;
badge.append(img);
const span = document.createElement("span");
span.textContent = badgejson.description;
badge.append(span);
if (badge instanceof HTMLAnchorElement) {
badge.href = badgejson.link;
}
badgediv.append(badge);
}
}
})();
const pfp = await this.buildstatuspfp();
div.appendChild(pfp);
const userbody = document.createElement("div");
userbody.classList.add("infosection");
div.appendChild(userbody);
const usernamehtml = document.createElement("h2");
usernamehtml.textContent = this.username;
userbody.appendChild(usernamehtml);
userbody.appendChild(badgediv);
const discrimatorhtml = document.createElement("h3");
discrimatorhtml.classList.add("tag");
discrimatorhtml.textContent = `${this.username}#${this.discriminator}`;
userbody.appendChild(discrimatorhtml);
const pronounshtml = document.createElement("p");
pronounshtml.textContent = this.pronouns;
pronounshtml.classList.add("pronouns");
userbody.appendChild(pronounshtml);
const rule = document.createElement("hr");
userbody.appendChild(rule);
const biohtml = this.bio.makeHTML();
userbody.appendChild(biohtml);
if (guild) {
Member.resolveMember(this, guild).then((member) => {
if (!member) return;
const roles = document.createElement("div");
roles.classList.add("rolesbox");
for (const role of member.roles) {
const roleDiv = document.createElement("div");
roleDiv.classList.add("rolediv");
const color = document.createElement("div");
roleDiv.append(color);
color.style.setProperty(
"--role-color",
`#${role.color.toString(16).padStart(6, "0")}`
);
color.classList.add("colorrolediv");
const span = document.createElement("span");
roleDiv.append(span);
span.textContent = role.name;
roles.append(roleDiv);
}
userbody.append(roles);
});
}
if (x !== -1) {
Contextmenu.currentmenu = div;
document.body.appendChild(div);
Contextmenu.keepOnScreen(div);
}
return div;
}
profileclick(obj: HTMLElement, guild?: Guild): void {
obj.onclick = (e: MouseEvent) => {
this.buildprofile(e.clientX, e.clientY, guild);
e.stopPropagation();
};
}
}
User.setUpContextMenu();
export { User };
User.setUpContextMenu();
export{ User };