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, parser: tsParser,
globals: global, globals: global,
}, },
files: ["webpage/*.ts"], files: ["src/*.ts","src/**/*.ts",],
ignores: ["!*.js", "!*.ts"], ignores: ["dist/", "node_modules/"],
plugins: { plugins: {
unicorn, unicorn,
sonarjs, sonarjs,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,164 +1,164 @@
import { getBulkInfo } from "./login.js"; import{ getBulkInfo }from"./login.js";
class Voice { class Voice{
audioCtx: AudioContext; audioCtx: AudioContext;
info: { wave: string | Function; freq: number }; info: { wave: string | Function; freq: number };
playing: boolean; playing: boolean;
myArrayBuffer: AudioBuffer; myArrayBuffer: AudioBuffer;
gainNode: GainNode; gainNode: GainNode;
buffer: Float32Array; buffer: Float32Array;
source: AudioBufferSourceNode; source: AudioBufferSourceNode;
constructor(wave: string | Function, freq: number, volume = 1) { constructor(wave: string | Function, freq: number, volume = 1){
this.audioCtx = new window.AudioContext(); this.audioCtx = new window.AudioContext();
this.info = { wave, freq }; this.info = { wave, freq };
this.playing = false; this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer( this.myArrayBuffer = this.audioCtx.createBuffer(
1, 1,
this.audioCtx.sampleRate, this.audioCtx.sampleRate,
this.audioCtx.sampleRate this.audioCtx.sampleRate
); );
this.gainNode = this.audioCtx.createGain(); this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume; this.gainNode.gain.value = volume;
this.gainNode.connect(this.audioCtx.destination); this.gainNode.connect(this.audioCtx.destination);
this.buffer = this.myArrayBuffer.getChannelData(0); this.buffer = this.myArrayBuffer.getChannelData(0);
this.source = this.audioCtx.createBufferSource(); this.source = this.audioCtx.createBufferSource();
this.source.buffer = this.myArrayBuffer; this.source.buffer = this.myArrayBuffer;
this.source.loop = true; this.source.loop = true;
this.source.start(); this.source.start();
this.updateWave(); 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 { export{ Voice };
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 };

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
import { Contextmenu } from "./contextmenu.js"; import{ Contextmenu }from"./contextmenu.js";
import { Guild } from "./guild.js"; import{ Guild }from"./guild.js";
import { Localuser } from "./localuser.js"; import{ Localuser }from"./localuser.js";
class Emoji { class Emoji{
static emojis: { static emojis: {
name: string; name: string;
emojis: { emojis: {
@ -14,32 +14,31 @@ class Emoji {
id: string; id: string;
animated: boolean; animated: boolean;
owner: Guild | Localuser; owner: Guild | Localuser;
get guild() { get guild(){
if (this.owner instanceof Guild) { if(this.owner instanceof Guild){
return this.owner; return this.owner;
} }
return;
} }
get localuser() { get localuser(){
if (this.owner instanceof Guild) { if(this.owner instanceof Guild){
return this.owner.localuser; return this.owner.localuser;
} else { }else{
return this.owner; return this.owner;
} }
} }
get info() { get info(){
return this.owner.info; return this.owner.info;
} }
constructor( constructor(
json: { name: string; id: string; animated: boolean }, json: { name: string; id: string; animated: boolean },
owner: Guild | Localuser owner: Guild | Localuser
) { ){
this.name = json.name; this.name = json.name;
this.id = json.id; this.id = json.id;
this.animated = json.animated; this.animated = json.animated;
this.owner = owner; this.owner = owner;
} }
getHTML(bigemoji: boolean = false) { getHTML(bigemoji: boolean = false){
const emojiElem = document.createElement("img"); const emojiElem = document.createElement("img");
emojiElem.classList.add("md-emoji"); emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
@ -56,29 +55,29 @@ class Emoji {
emojiElem.loading = "lazy"; emojiElem.loading = "lazy";
return emojiElem; return emojiElem;
} }
static decodeEmojiList(buffer: ArrayBuffer) { static decodeEmojiList(buffer: ArrayBuffer){
const view = new DataView(buffer, 0); const view = new DataView(buffer, 0);
let i = 0; let i = 0;
function read16() { function read16(){
const int = view.getUint16(i); const int = view.getUint16(i);
i += 2; i += 2;
return int; return int;
} }
function read8() { function read8(){
const int = view.getUint8(i); const int = view.getUint8(i);
i += 1; i += 1;
return int; return int;
} }
function readString8() { function readString8(){
return readStringNo(read8()); return readStringNo(read8());
} }
function readString16() { function readString16(){
return readStringNo(read16()); return readStringNo(read16());
} }
function readStringNo(length: number) { function readStringNo(length: number){
const array = new Uint8Array(length); const array = new Uint8Array(length);
for (let i = 0; i < length; i++) { for(let i = 0; i < length; i++){
array[i] = read8(); array[i] = read8();
} }
//console.log(array); //console.log(array);
@ -88,7 +87,7 @@ class Emoji {
[]; [];
let cats = read16(); let cats = read16();
for (; cats !== 0; cats--) { for(; cats !== 0; cats--){
const name = readString16(); const name = readString16();
const emojis: { const emojis: {
name: string; name: string;
@ -96,7 +95,7 @@ class Emoji {
emoji: string; emoji: string;
}[] = []; }[] = [];
let emojinumber = read16(); let emojinumber = read16();
for (; emojinumber !== 0; emojinumber--) { for(; emojinumber !== 0; emojinumber--){
//console.log(emojis); //console.log(emojis);
const name = readString8(); const name = readString8();
const len = read8(); const len = read8();
@ -116,12 +115,12 @@ class Emoji {
this.emojis = build; this.emojis = build;
console.log(build); console.log(build);
} }
static grabEmoji() { static grabEmoji(){
fetch("/emoji.bin") fetch("/emoji.bin")
.then((e) => { .then(e=>{
return e.arrayBuffer(); return e.arrayBuffer();
}) })
.then((e) => { .then(e=>{
Emoji.decodeEmojiList(e); Emoji.decodeEmojiList(e);
}); });
} }
@ -129,9 +128,9 @@ class Emoji {
x: number, x: number,
y: number, y: number,
localuser: Localuser localuser: Localuser
): Promise<Emoji | string> { ): Promise<Emoji | string>{
let res: (r: Emoji | string) => void; let res: (r: Emoji | string) => void;
const promise: Promise<Emoji | string> = new Promise((r) => { const promise: Promise<Emoji | string> = new Promise(r=>{
res = r; res = r;
}); });
const menu = document.createElement("div"); const menu = document.createElement("div");
@ -150,12 +149,12 @@ class Emoji {
let isFirst = true; let isFirst = true;
localuser.guilds localuser.guilds
.filter((guild) => guild.id != "@me" && guild.emojis.length > 0) .filter(guild=>guild.id != "@me" && guild.emojis.length > 0)
.forEach((guild) => { .forEach(guild=>{
const select = document.createElement("div"); const select = document.createElement("div");
select.classList.add("emojiSelect"); select.classList.add("emojiSelect");
if (guild.properties.icon) { if(guild.properties.icon){
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("pfp", "servericon", "emoji-server"); img.classList.add("pfp", "servericon", "emoji-server");
img.crossOrigin = "anonymous"; img.crossOrigin = "anonymous";
@ -168,21 +167,21 @@ class Emoji {
".png?size=48"; ".png?size=48";
img.alt = "Server: " + guild.properties.name; img.alt = "Server: " + guild.properties.name;
select.appendChild(img); select.appendChild(img);
} else { }else{
const div = document.createElement("span"); const div = document.createElement("span");
div.textContent = guild.properties.name div.textContent = guild.properties.name
.replace(/'s /g, " ") .replace(/'s /g, " ")
.replace(/\w+/g, (word) => word[0]) .replace(/\w+/g, word=>word[0])
.replace(/\s/g, ""); .replace(/\s/g, "");
select.append(div); select.append(div);
} }
selection.append(select); selection.append(select);
const clickEvent = () => { const clickEvent = ()=>{
title.textContent = guild.properties.name; title.textContent = guild.properties.name;
body.innerHTML = ""; body.innerHTML = "";
for (const emojit of guild.emojis) { for(const emojit of guild.emojis){
const emojiElem = document.createElement("div"); const emojiElem = document.createElement("div");
emojiElem.classList.add("emojiSelect"); emojiElem.classList.add("emojiSelect");
@ -197,9 +196,9 @@ class Emoji {
emojiElem.append(emojiClass.getHTML()); emojiElem.append(emojiClass.getHTML());
body.append(emojiElem); body.append(emojiElem);
emojiElem.addEventListener("click", () => { emojiElem.addEventListener("click", ()=>{
res(emojiClass); res(emojiClass);
if (Contextmenu.currentmenu !== "") { if(Contextmenu.currentmenu !== ""){
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
}); });
@ -207,14 +206,14 @@ class Emoji {
}; };
select.addEventListener("click", clickEvent); select.addEventListener("click", clickEvent);
if (isFirst) { if(isFirst){
clickEvent(); clickEvent();
isFirst = false; isFirst = false;
} }
}); });
setTimeout(() => { setTimeout(()=>{
if (Contextmenu.currentmenu != "") { if(Contextmenu.currentmenu != ""){
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
document.body.append(menu); document.body.append(menu);
@ -223,29 +222,29 @@ class Emoji {
}, 10); }, 10);
let i = 0; let i = 0;
for (const thing of Emoji.emojis) { for(const thing of Emoji.emojis){
const select = document.createElement("div"); const select = document.createElement("div");
select.textContent = thing.emojis[0].emoji; select.textContent = thing.emojis[0].emoji;
select.classList.add("emojiSelect"); select.classList.add("emojiSelect");
selection.append(select); selection.append(select);
const clickEvent = () => { const clickEvent = ()=>{
title.textContent = thing.name; title.textContent = thing.name;
body.innerHTML = ""; body.innerHTML = "";
for (const emojit of thing.emojis) { for(const emojit of thing.emojis){
const emoji = document.createElement("div"); const emoji = document.createElement("div");
emoji.classList.add("emojiSelect"); emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji; emoji.textContent = emojit.emoji;
body.append(emoji); body.append(emoji);
emoji.onclick = (_) => { emoji.onclick = _=>{
res(emojit.emoji); res(emojit.emoji);
if (Contextmenu.currentmenu !== "") { if(Contextmenu.currentmenu !== ""){
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
}; };
} }
}; };
select.onclick = clickEvent; select.onclick = clickEvent;
if (i === 0) { if(i === 0){
clickEvent(); clickEvent();
} }
i++; i++;
@ -256,4 +255,4 @@ class Emoji {
} }
} }
Emoji.grabEmoji(); Emoji.grabEmoji();
export { Emoji }; export{ Emoji };

View file

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

View file

@ -1,29 +1,29 @@
import { Channel } from "./channel.js"; import{ Channel }from"./channel.js";
import { Localuser } from "./localuser.js"; import{ Localuser }from"./localuser.js";
import { Contextmenu } from "./contextmenu.js"; import{ Contextmenu }from"./contextmenu.js";
import { Role, RoleList } from "./role.js"; import{ Role, RoleList }from"./role.js";
import { Dialog } from "./dialog.js"; import{ Dialog }from"./dialog.js";
import { Member } from "./member.js"; import{ Member }from"./member.js";
import { Settings } from "./settings.js"; import{ Settings }from"./settings.js";
import { Permissions } from "./permissions.js"; import{ Permissions }from"./permissions.js";
import { SnowFlake } from "./snowflake.js"; import{ SnowFlake }from"./snowflake.js";
import { import{
channeljson, channeljson,
guildjson, guildjson,
emojijson, emojijson,
memberjson, memberjson,
invitejson, invitejson,
} from "./jsontypes.js"; }from"./jsontypes.js";
import { User } from "./user.js"; import{ User }from"./user.js";
class Guild extends SnowFlake { class Guild extends SnowFlake{
owner!: Localuser; owner!: Localuser;
headers!: Localuser["headers"]; headers!: Localuser["headers"];
channels!: Channel[]; channels!: Channel[];
properties!: guildjson["properties"]; properties!: guildjson["properties"];
member_count!: number; member_count!: number;
roles!: Role[]; roles!: Role[];
roleids!: Map<string, Role>; roleids!: Map<string, Role>;
prevchannel: Channel | undefined; prevchannel: Channel | undefined;
banner!: string; banner!: string;
message_notifications!: number; message_notifications!: number;
@ -35,49 +35,49 @@ roleids!: Map<string, Role>;
emojis!: emojijson[]; emojis!: emojijson[];
large!: boolean; large!: boolean;
static contextmenu = new Contextmenu<Guild, undefined>("guild menu"); static contextmenu = new Contextmenu<Guild, undefined>("guild menu");
static setupcontextmenu() { static setupcontextmenu(){
Guild.contextmenu.addbutton("Copy Guild id", function (this: Guild) { Guild.contextmenu.addbutton("Copy Guild id", function(this: Guild){
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); });
Guild.contextmenu.addbutton("Mark as read", function (this: Guild) { Guild.contextmenu.addbutton("Mark as read", function(this: Guild){
this.markAsRead(); this.markAsRead();
}); });
Guild.contextmenu.addbutton("Notifications", function (this: Guild) { Guild.contextmenu.addbutton("Notifications", function(this: Guild){
this.setnotifcation(); this.setnotifcation();
}); });
Guild.contextmenu.addbutton( Guild.contextmenu.addbutton(
"Leave guild", "Leave guild",
function (this: Guild) { function(this: Guild){
this.confirmleave(); this.confirmleave();
}, },
null, null,
function (_) { function(_){
return this.properties.owner_id !== this.member.user.id; return this.properties.owner_id !== this.member.user.id;
} }
); );
Guild.contextmenu.addbutton( Guild.contextmenu.addbutton(
"Delete guild", "Delete guild",
function (this: Guild) { function(this: Guild){
this.confirmDelete(); this.confirmDelete();
}, },
null, null,
function (_) { function(_){
return this.properties.owner_id === this.member.user.id; return this.properties.owner_id === this.member.user.id;
} }
); );
Guild.contextmenu.addbutton( Guild.contextmenu.addbutton(
"Create invite", "Create invite",
function (this: Guild) {}, (this: Guild)=>{},
null, null,
(_) => true, _=>true,
(_) => false _=>false
); );
Guild.contextmenu.addbutton("Settings", function (this: Guild) { Guild.contextmenu.addbutton("Settings", function(this: Guild){
this.generateSettings(); this.generateSettings();
}); });
/* -----things left for later----- /* -----things left for later-----
@ -91,11 +91,11 @@ roleids!: Map<string, Role>;
},null,_=>{return thisuser.isAdmin()}) },null,_=>{return thisuser.isAdmin()})
*/ */
} }
generateSettings() { generateSettings(){
const settings = new Settings("Settings for " + this.properties.name); const settings = new Settings("Settings for " + this.properties.name);
{ {
const overview = settings.addButton("Overview"); const overview = settings.addButton("Overview");
const form = overview.addForm("", (_) => {}, { const form = overview.addForm("", _=>{}, {
headers: this.headers, headers: this.headers,
traditionalSubmit: true, traditionalSubmit: true,
fetchURL: this.info.api + "/guilds/" + this.id, fetchURL: this.info.api + "/guilds/" + this.id,
@ -108,14 +108,14 @@ roleids!: Map<string, Role>;
form.addFileInput("Banner:", "banner", { clear: true }); form.addFileInput("Banner:", "banner", { clear: true });
form.addFileInput("Icon:", "icon", { clear: true }); form.addFileInput("Icon:", "icon", { clear: true });
let region = this.properties.region; let region = this.properties.region;
if (!region) { if(!region){
region = ""; region = "";
} }
form.addTextInput("Region:", "region", { initText: region }); form.addTextInput("Region:", "region", { initText: region });
} }
const s1 = settings.addButton("roles"); const s1 = settings.addButton("roles");
const permlist: [Role, Permissions][] = []; const permlist: [Role, Permissions][] = [];
for (const thing of this.roles) { for(const thing of this.roles){
permlist.push([thing, thing.permissions]); permlist.push([thing, thing.permissions]);
} }
s1.options.push( s1.options.push(
@ -127,12 +127,12 @@ roleids!: Map<string, Role>;
json: guildjson | -1, json: guildjson | -1,
owner: Localuser, owner: Localuser,
member: memberjson | User | null member: memberjson | User | null
) { ){
if (json === -1 || member === null) { if(json === -1 || member === null){
super("@me"); super("@me");
return; return;
} }
if (json.stickers.length) { if(json.stickers.length){
console.log(json.stickers, ":3"); console.log(json.stickers, ":3");
} }
super(json.id); super(json.id);
@ -147,45 +147,45 @@ roleids!: Map<string, Role>;
this.roleids = new Map(); this.roleids = new Map();
this.message_notifications = 0; this.message_notifications = 0;
for (const roley of json.roles) { for(const roley of json.roles){
const roleh = new Role(roley, this); const roleh = new Role(roley, this);
this.roles.push(roleh); this.roles.push(roleh);
this.roleids.set(roleh.id, roleh); this.roleids.set(roleh.id, roleh);
} }
if (member instanceof User) { if(member instanceof User){
Member.resolveMember(member, this).then((_) => { Member.resolveMember(member, this).then(_=>{
if (_) { if(_){
this.member = _; this.member = _;
} else { }else{
console.error("Member was unable to resolve"); console.error("Member was unable to resolve");
} }
}); });
} else { }else{
Member.new(member, this).then((_) => { Member.new(member, this).then(_=>{
if (_) { if(_){
this.member = _; this.member = _;
} }
}); });
} }
this.perminfo ??= { channels: {} }; this.perminfo ??= { channels: {} };
for (const thing of json.channels) { for(const thing of json.channels){
const temp = new Channel(thing, this); const temp = new Channel(thing, this);
this.channels.push(temp); this.channels.push(temp);
this.localuser.channelids.set(temp.id, temp); this.localuser.channelids.set(temp.id, temp);
} }
this.headchannels = []; this.headchannels = [];
for (const thing of this.channels) { for(const thing of this.channels){
const parent = thing.resolveparent(this); const parent = thing.resolveparent(this);
if (!parent) { if(!parent){
this.headchannels.push(thing); this.headchannels.push(thing);
} }
} }
this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel); this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel);
} }
get perminfo() { get perminfo(){
return this.localuser.perminfo.guilds[this.id]; return this.localuser.perminfo.guilds[this.id];
} }
set perminfo(e) { set perminfo(e){
this.localuser.perminfo.guilds[this.id] = e; this.localuser.perminfo.guilds[this.id] = e;
} }
notisetting(settings: { notisetting(settings: {
@ -202,10 +202,10 @@ roleids!: Map<string, Role>;
suppress_roles?: boolean; suppress_roles?: boolean;
version?: number; version?: number;
guild_id?: string; guild_id?: string;
}) { }){
this.message_notifications = settings.message_notifications; this.message_notifications = settings.message_notifications;
} }
setnotifcation() { setnotifcation(){
let noti = this.message_notifications; let noti = this.message_notifications;
const notiselect = new Dialog([ const notiselect = new Dialog([
"vdiv", "vdiv",
@ -213,7 +213,7 @@ roleids!: Map<string, Role>;
"radio", "radio",
"select notifications type", "select notifications type",
["all", "only mentions", "none"], ["all", "only mentions", "none"],
function (e: string /* "all" | "only mentions" | "none" */) { function(e: string /* "all" | "only mentions" | "none" */){
noti = ["all", "only mentions", "none"].indexOf(e); noti = ["all", "only mentions", "none"].indexOf(e);
}, },
noti, noti,
@ -222,7 +222,7 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"submit", "submit",
(_: any) => { (_: any)=>{
// //
fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, { fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, {
method: "PATCH", method: "PATCH",
@ -237,7 +237,7 @@ roleids!: Map<string, Role>;
]); ]);
notiselect.show(); notiselect.show();
} }
confirmleave() { confirmleave(){
const full = new Dialog([ const full = new Dialog([
"vdiv", "vdiv",
["title", "Are you sure you want to leave?"], ["title", "Are you sure you want to leave?"],
@ -247,8 +247,8 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"Yes, I'm sure", "Yes, I'm sure",
(_: any) => { (_: any)=>{
this.leave().then((_) => { this.leave().then(_=>{
full.hide(); full.hide();
}); });
}, },
@ -257,7 +257,7 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"Nevermind", "Nevermind",
(_: any) => { (_: any)=>{
full.hide(); full.hide();
}, },
], ],
@ -265,63 +265,63 @@ roleids!: Map<string, Role>;
]); ]);
full.show(); full.show();
} }
async leave() { async leave(){
return fetch(this.info.api + "/users/@me/guilds/" + this.id, { return fetch(this.info.api + "/users/@me/guilds/" + this.id, {
method: "DELETE", method: "DELETE",
headers: this.headers, headers: this.headers,
}); });
} }
printServers() { printServers(){
let build = ""; let build = "";
for (const thing of this.headchannels) { for(const thing of this.headchannels){
build += thing.name + ":" + thing.position + "\n"; build += thing.name + ":" + thing.position + "\n";
for (const thingy of thing.children) { for(const thingy of thing.children){
build += " " + thingy.name + ":" + thingy.position + "\n"; build += " " + thingy.name + ":" + thingy.position + "\n";
} }
} }
console.log(build); console.log(build);
} }
calculateReorder() { calculateReorder(){
let position = -1; let position = -1;
const build: { const build: {
id: string; id: string;
position: number | undefined; position: number | undefined;
parent_id: string | undefined; parent_id: string | undefined;
}[] = []; }[] = [];
for (const thing of this.headchannels) { for(const thing of this.headchannels){
const thisthing: { const thisthing: {
id: string; id: string;
position: number | undefined; position: number | undefined;
parent_id: string | undefined; parent_id: string | undefined;
} = { id: thing.id, position: undefined, parent_id: undefined }; } = { id: thing.id, position: undefined, parent_id: undefined };
if (thing.position <= position) { if(thing.position <= position){
thing.position = thisthing.position = position + 1; thing.position = thisthing.position = position + 1;
} }
position = thing.position; position = thing.position;
console.log(position); console.log(position);
if (thing.move_id && thing.move_id !== thing.parent_id) { if(thing.move_id && thing.move_id !== thing.parent_id){
thing.parent_id = thing.move_id; thing.parent_id = thing.move_id;
thisthing.parent_id = thing.parent?.id; thisthing.parent_id = thing.parent?.id;
thing.move_id = undefined; thing.move_id = undefined;
} }
if (thisthing.position || thisthing.parent_id) { if(thisthing.position || thisthing.parent_id){
build.push(thisthing); build.push(thisthing);
} }
if (thing.children.length > 0) { if(thing.children.length > 0){
const things = thing.calculateReorder(); const things = thing.calculateReorder();
for (const thing of things) { for(const thing of things){
build.push(thing); build.push(thing);
} }
} }
} }
console.log(build); console.log(build);
this.printServers(); this.printServers();
if (build.length === 0) { if(build.length === 0){
return; return;
} }
const serverbug = false; const serverbug = false;
if (serverbug) { if(serverbug){
for (const thing of build) { for(const thing of build){
console.log(build, thing); console.log(build, thing);
fetch(this.info.api + "/guilds/" + this.id + "/channels", { fetch(this.info.api + "/guilds/" + this.id + "/channels", {
method: "PATCH", method: "PATCH",
@ -329,7 +329,7 @@ roleids!: Map<string, Role>;
body: JSON.stringify([thing]), body: JSON.stringify([thing]),
}); });
} }
} else { }else{
fetch(this.info.api + "/guilds/" + this.id + "/channels", { fetch(this.info.api + "/guilds/" + this.id + "/channels", {
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
@ -337,77 +337,77 @@ roleids!: Map<string, Role>;
}); });
} }
} }
get localuser() { get localuser(){
return this.owner; return this.owner;
} }
get info() { get info(){
return this.owner.info; return this.owner.info;
} }
sortchannels() { sortchannels(){
this.headchannels.sort((a, b) => { this.headchannels.sort((a, b)=>{
return a.position - b.position; return a.position - b.position;
}); });
} }
static generateGuildIcon( static generateGuildIcon(
guild: Guild | (invitejson["guild"] & { info: { cdn: string } }) guild: Guild | (invitejson["guild"] & { info: { cdn: string } })
) { ){
const divy = document.createElement("div"); const divy = document.createElement("div");
divy.classList.add("servernoti"); divy.classList.add("servernoti");
const noti = document.createElement("div"); const noti = document.createElement("div");
noti.classList.add("unread"); noti.classList.add("unread");
divy.append(noti); divy.append(noti);
if (guild instanceof Guild) { if(guild instanceof Guild){
guild.localuser.guildhtml.set(guild.id, divy); guild.localuser.guildhtml.set(guild.id, divy);
} }
let icon: string | null; let icon: string | null;
if (guild instanceof Guild) { if(guild instanceof Guild){
icon = guild.properties.icon; icon = guild.properties.icon;
} else { }else{
icon = guild.icon; icon = guild.icon;
} }
if (icon !== null) { if(icon !== null){
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("pfp", "servericon"); img.classList.add("pfp", "servericon");
img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png";
divy.appendChild(img); divy.appendChild(img);
if (guild instanceof Guild) { if(guild instanceof Guild){
img.onclick = () => { img.onclick = ()=>{
console.log(guild.loadGuild); console.log(guild.loadGuild);
guild.loadGuild(); guild.loadGuild();
guild.loadChannel(); guild.loadChannel();
}; };
Guild.contextmenu.bindContextmenu(img, guild, undefined); Guild.contextmenu.bindContextmenu(img, guild);
} }
} else { }else{
const div = document.createElement("div"); const div = document.createElement("div");
let name: string; let name: string;
if (guild instanceof Guild) { if(guild instanceof Guild){
name = guild.properties.name; name = guild.properties.name;
} else { }else{
name = guild.name; name = guild.name;
} }
const build = name const build = name
.replace(/'s /g, " ") .replace(/'s /g, " ")
.replace(/\w+/g, (word) => word[0]) .replace(/\w+/g, word=>word[0])
.replace(/\s/g, ""); .replace(/\s/g, "");
div.textContent = build; div.textContent = build;
div.classList.add("blankserver", "servericon"); div.classList.add("blankserver", "servericon");
divy.appendChild(div); divy.appendChild(div);
if (guild instanceof Guild) { if(guild instanceof Guild){
div.onclick = () => { div.onclick = ()=>{
guild.loadGuild(); guild.loadGuild();
guild.loadChannel(); guild.loadChannel();
}; };
Guild.contextmenu.bindContextmenu(div, guild, undefined); Guild.contextmenu.bindContextmenu(div, guild);
} }
} }
return divy; return divy;
} }
generateGuildIcon() { generateGuildIcon(){
return Guild.generateGuildIcon(this); return Guild.generateGuildIcon(this);
} }
confirmDelete() { confirmDelete(){
let confirmname = ""; let confirmname = "";
const full = new Dialog([ const full = new Dialog([
"vdiv", "vdiv",
@ -419,7 +419,7 @@ roleids!: Map<string, Role>;
"textbox", "textbox",
"Name of server:", "Name of server:",
"", "",
function (this: HTMLInputElement) { function(this: HTMLInputElement){
confirmname = this.value; confirmname = this.value;
}, },
], ],
@ -429,12 +429,12 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"Yes, I'm sure", "Yes, I'm sure",
(_: any) => { (_: any)=>{
console.log(confirmname); console.log(confirmname);
if (confirmname !== this.properties.name) { if(confirmname !== this.properties.name){
return; return;
} }
this.delete().then((_) => { this.delete().then(_=>{
full.hide(); full.hide();
}); });
}, },
@ -443,7 +443,7 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"Nevermind", "Nevermind",
(_: any) => { (_: any)=>{
full.hide(); full.hide();
}, },
], ],
@ -451,50 +451,50 @@ roleids!: Map<string, Role>;
]); ]);
full.show(); full.show();
} }
async delete() { async delete(){
return fetch(this.info.api + "/guilds/" + this.id + "/delete", { return fetch(this.info.api + "/guilds/" + this.id + "/delete", {
method: "POST", method: "POST",
headers: this.headers, headers: this.headers,
}); });
} }
unreads(html?: HTMLElement | undefined) { unreads(html?: HTMLElement | undefined){
if (html) { if(html){
this.html = html; this.html = html;
} else { }else{
html = this.html; html = this.html;
} }
let read = true; let read = true;
for (const thing of this.channels) { for(const thing of this.channels){
if (thing.hasunreads) { if(thing.hasunreads){
console.log(thing); console.log(thing);
read = false; read = false;
break; break;
} }
} }
if (!html) { if(!html){
return; return;
} }
if (read) { if(read){
html.children[0].classList.remove("notiunread"); html.children[0].classList.remove("notiunread");
} else { }else{
html.children[0].classList.add("notiunread"); html.children[0].classList.add("notiunread");
} }
} }
getHTML() { getHTML(){
//this.printServers(); //this.printServers();
this.sortchannels(); this.sortchannels();
this.printServers(); this.printServers();
const build = document.createElement("div"); const build = document.createElement("div");
for (const thing of this.headchannels) { for(const thing of this.headchannels){
build.appendChild(thing.createguildHTML(this.isAdmin())); build.appendChild(thing.createguildHTML(this.isAdmin()));
} }
return build; return build;
} }
isAdmin() { isAdmin(){
return this.member.isAdmin(); return this.member.isAdmin();
} }
async markAsRead() { async markAsRead(){
const build: { const build: {
read_states: { read_states: {
channel_id: string; channel_id: string;
@ -502,15 +502,15 @@ roleids!: Map<string, Role>;
read_state_type: number; read_state_type: number;
}[]; }[];
} = { read_states: [] }; } = { read_states: [] };
for (const thing of this.channels) { for(const thing of this.channels){
if (thing.hasunreads) { if(thing.hasunreads){
build.read_states.push({ build.read_states.push({
channel_id: thing.id, channel_id: thing.id,
message_id: thing.lastmessageid, message_id: thing.lastmessageid,
read_state_type: 0, read_state_type: 0,
}); });
thing.lastreadmessageid = thing.lastmessageid; thing.lastreadmessageid = thing.lastmessageid;
if (!thing.myhtml) continue; if(!thing.myhtml)continue;
thing.myhtml.classList.remove("cunread"); thing.myhtml.classList.remove("cunread");
} }
} }
@ -521,67 +521,67 @@ roleids!: Map<string, Role>;
body: JSON.stringify(build), body: JSON.stringify(build),
}); });
} }
hasRole(r: Role | string) { hasRole(r: Role | string){
console.log("this should run"); console.log("this should run");
if (r instanceof Role) { if(r instanceof Role){
r = r.id; r = r.id;
} }
return this.member.hasRole(r); return this.member.hasRole(r);
} }
loadChannel(ID?: string | undefined) { loadChannel(ID?: string | undefined){
if (ID) { if(ID){
const channel = this.localuser.channelids.get(ID); const channel = this.localuser.channelids.get(ID);
if (channel) { if(channel){
channel.getHTML(); channel.getHTML();
return; return;
} }
} }
if (this.prevchannel) { if(this.prevchannel){
console.log(this.prevchannel); console.log(this.prevchannel);
this.prevchannel.getHTML(); this.prevchannel.getHTML();
return; return;
} }
for (const thing of this.channels) { for(const thing of this.channels){
if (thing.children.length === 0) { if(thing.children.length === 0){
thing.getHTML(); thing.getHTML();
return; return;
} }
} }
} }
loadGuild() { loadGuild(){
this.localuser.loadGuild(this.id); this.localuser.loadGuild(this.id);
} }
updateChannel(json: channeljson) { updateChannel(json: channeljson){
const channel = this.localuser.channelids.get(json.id); const channel = this.localuser.channelids.get(json.id);
if (channel) { if(channel){
channel.updateChannel(json); channel.updateChannel(json);
this.headchannels = []; this.headchannels = [];
for (const thing of this.channels) { for(const thing of this.channels){
thing.children = []; thing.children = [];
} }
this.headchannels = []; this.headchannels = [];
for (const thing of this.channels) { for(const thing of this.channels){
const parent = thing.resolveparent(this); const parent = thing.resolveparent(this);
if (!parent) { if(!parent){
this.headchannels.push(thing); this.headchannels.push(thing);
} }
} }
this.printServers(); this.printServers();
} }
} }
createChannelpac(json: channeljson) { createChannelpac(json: channeljson){
const thischannel = new Channel(json, this); const thischannel = new Channel(json, this);
this.localuser.channelids.set(json.id, thischannel); this.localuser.channelids.set(json.id, thischannel);
this.channels.push(thischannel); this.channels.push(thischannel);
thischannel.resolveparent(this); thischannel.resolveparent(this);
if (!thischannel.parent) { if(!thischannel.parent){
this.headchannels.push(thischannel); this.headchannels.push(thischannel);
} }
this.calculateReorder(); this.calculateReorder();
this.printServers(); this.printServers();
return thischannel; return thischannel;
} }
createchannels(func = this.createChannel) { createchannels(func = this.createChannel){
let name = ""; let name = "";
let category = 0; let category = 0;
const channelselect = new Dialog([ const channelselect = new Dialog([
@ -590,7 +590,7 @@ roleids!: Map<string, Role>;
"radio", "radio",
"select channel type", "select channel type",
["voice", "text", "announcement"], ["voice", "text", "announcement"],
function (radio: string) { function(radio: string){
console.log(radio); console.log(radio);
category = category =
{ text: 0, voice: 2, announcement: 5, category: 4 }[radio] || 0; { text: 0, voice: 2, announcement: 5, category: 4 }[radio] || 0;
@ -601,7 +601,7 @@ roleids!: Map<string, Role>;
"textbox", "textbox",
"Name of channel", "Name of channel",
"", "",
function (this: HTMLInputElement) { function(this: HTMLInputElement){
name = this.value; name = this.value;
}, },
], ],
@ -609,7 +609,7 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"submit", "submit",
function () { function(){
console.log(name, category); console.log(name, category);
func(name, category); func(name, category);
channelselect.hide(); channelselect.hide();
@ -618,7 +618,7 @@ roleids!: Map<string, Role>;
]); ]);
channelselect.show(); channelselect.show();
} }
createcategory() { createcategory(){
let name = ""; let name = "";
const category = 4; const category = 4;
const channelselect = new Dialog([ const channelselect = new Dialog([
@ -627,7 +627,7 @@ roleids!: Map<string, Role>;
"textbox", "textbox",
"Name of category", "Name of category",
"", "",
function (this: HTMLInputElement) { function(this: HTMLInputElement){
name = this.value; name = this.value;
}, },
], ],
@ -635,7 +635,7 @@ roleids!: Map<string, Role>;
"button", "button",
"", "",
"submit", "submit",
() => { ()=>{
console.log(name, category); console.log(name, category);
this.createChannel(name, category); this.createChannel(name, category);
channelselect.hide(); channelselect.hide();
@ -644,13 +644,13 @@ roleids!: Map<string, Role>;
]); ]);
channelselect.show(); channelselect.show();
} }
delChannel(json: channeljson) { delChannel(json: channeljson){
const channel = this.localuser.channelids.get(json.id); const channel = this.localuser.channelids.get(json.id);
this.localuser.channelids.delete(json.id); this.localuser.channelids.delete(json.id);
if (!channel) return; if(!channel)return;
this.channels.splice(this.channels.indexOf(channel), 1); this.channels.splice(this.channels.indexOf(channel), 1);
const indexy = this.headchannels.indexOf(channel); const indexy = this.headchannels.indexOf(channel);
if (indexy !== -1) { if(indexy !== -1){
this.headchannels.splice(indexy, 1); this.headchannels.splice(indexy, 1);
} }
@ -671,14 +671,14 @@ roleids!: Map<string, Role>;
*/ */
this.printServers(); this.printServers();
} }
createChannel(name: string, type: number) { createChannel(name: string, type: number){
fetch(this.info.api + "/guilds/" + this.id + "/channels", { fetch(this.info.api + "/guilds/" + this.id + "/channels", {
method: "POST", method: "POST",
headers: this.headers, headers: this.headers,
body: JSON.stringify({ name, type }), body: JSON.stringify({ name, type }),
}); });
} }
async createRole(name: string) { async createRole(name: string){
const fetched = await fetch( const fetched = await fetch(
this.info.api + "/guilds/" + this.id + "roles", this.info.api + "/guilds/" + this.id + "roles",
{ {
@ -697,9 +697,9 @@ roleids!: Map<string, Role>;
this.roles.push(role); this.roles.push(role);
return role; return role;
} }
async updateRolePermissions(id: string, perms: Permissions) { async updateRolePermissions(id: string, perms: Permissions){
const role = this.roleids.get(id); const role = this.roleids.get(id);
if (!role) { if(!role){
return; return;
} }
role.permissions.allow = perms.allow; role.permissions.allow = perms.allow;
@ -719,6 +719,6 @@ roleids!: Map<string, Role>;
}), }),
}); });
} }
} }
Guild.setupcontextmenu(); Guild.setupcontextmenu();
export { Guild }; export{ Guild };

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jank Client</title> <title>Jank Client</title>
@ -11,9 +11,9 @@
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet"> <link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss">
</head> </head>
<body class="Dark-theme"> <body class="Dark-theme">
<div id="titleDiv"> <div id="titleDiv">
<img src="/logo.svg" width="40"> <img src="/logo.svg" width="40">
<h1 id="pageTitle">Jank Client</h1> <h1 id="pageTitle">Jank Client</h1>
@ -55,7 +55,7 @@
</a> </a>
</div> </div>
</div> </div>
</body> </body>
<script src="/home.js" type="module"></script> <script src="/home.js" type="module"></script>
</html> </html>

View file

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

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Jank Client</title> <title>Jank Client</title>
@ -13,9 +13,9 @@
<link href="/themes.css" rel="stylesheet" id="lightcss"> <link href="/themes.css" rel="stylesheet" id="lightcss">
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
</head> </head>
<body class="Dark-theme"> <body class="Dark-theme">
<script src="/index.js" type="module"></script> <script src="/index.js" type="module"></script>
<div id="loading" class="loading"> <div id="loading" class="loading">
@ -77,6 +77,6 @@
</div> </div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -1,30 +1,30 @@
import { Localuser } from "./localuser.js"; import{ Localuser }from"./localuser.js";
import { Contextmenu } from "./contextmenu.js"; import{ Contextmenu }from"./contextmenu.js";
import { mobile, getBulkUsers, setTheme, Specialuser } from "./login.js"; import{ mobile, getBulkUsers, setTheme, Specialuser }from"./login.js";
import { MarkDown } from "./markdown.js"; import{ MarkDown }from"./markdown.js";
import { Message } from "./message.js"; import{ Message }from"./message.js";
import { File } from "./file.js"; import{ File }from"./file.js";
(async () => { (async ()=>{
async function waitForLoad(): Promise<void> { async function waitForLoad(): Promise<void>{
return new Promise((resolve) => { return new Promise(resolve=>{
document.addEventListener("DOMContentLoaded", (_) => resolve()); document.addEventListener("DOMContentLoaded", _=>resolve());
}); });
} }
await waitForLoad(); await waitForLoad();
const users = getBulkUsers(); const users = getBulkUsers();
if (!users.currentuser) { if(!users.currentuser){
window.location.href = "/login.html"; window.location.href = "/login.html";
return; return;
} }
function showAccountSwitcher(): void { function showAccountSwitcher(): void{
const table = document.createElement("div"); const table = document.createElement("div");
table.classList.add("accountSwitcher"); table.classList.add("accountSwitcher");
for (const user of Object.values(users.users)) { for(const user of Object.values(users.users)){
const specialUser = user as Specialuser; const specialUser = user as Specialuser;
const userInfo = document.createElement("div"); const userInfo = document.createElement("div");
userInfo.classList.add("flexltr", "switchtable"); userInfo.classList.add("flexltr", "switchtable");
@ -49,7 +49,7 @@ async function waitForLoad(): Promise<void> {
userInfo.append(userDiv); userInfo.append(userDiv);
table.append(userInfo); table.append(userInfo);
userInfo.addEventListener("click", () => { userInfo.addEventListener("click", ()=>{
thisUser.unload(); thisUser.unload();
thisUser.swapped = true; thisUser.swapped = true;
const loading = document.getElementById("loading") as HTMLDivElement; const loading = document.getElementById("loading") as HTMLDivElement;
@ -60,7 +60,7 @@ async function waitForLoad(): Promise<void> {
users.currentuser = specialUser.uid; users.currentuser = specialUser.uid;
localStorage.setItem("userinfos", JSON.stringify(users)); localStorage.setItem("userinfos", JSON.stringify(users));
thisUser.initwebsocket().then(() => { thisUser.initwebsocket().then(()=>{
thisUser.loaduser(); thisUser.loaduser();
thisUser.init(); thisUser.init();
loading.classList.add("doneloading"); loading.classList.add("doneloading");
@ -75,12 +75,12 @@ async function waitForLoad(): Promise<void> {
const switchAccountDiv = document.createElement("div"); const switchAccountDiv = document.createElement("div");
switchAccountDiv.classList.add("switchtable"); switchAccountDiv.classList.add("switchtable");
switchAccountDiv.textContent = "Switch accounts ⇌"; switchAccountDiv.textContent = "Switch accounts ⇌";
switchAccountDiv.addEventListener("click", () => { switchAccountDiv.addEventListener("click", ()=>{
window.location.href = "/login.html"; window.location.href = "/login.html";
}); });
table.append(switchAccountDiv); table.append(switchAccountDiv);
if (Contextmenu.currentmenu) { if(Contextmenu.currentmenu){
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
Contextmenu.currentmenu = table; Contextmenu.currentmenu = table;
@ -88,7 +88,7 @@ async function waitForLoad(): Promise<void> {
} }
const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; const userInfoElement = document.getElementById("userinfo") as HTMLDivElement;
userInfoElement.addEventListener("click", (event) => { userInfoElement.addEventListener("click", event=>{
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
@ -96,16 +96,16 @@ async function waitForLoad(): Promise<void> {
const switchAccountsElement = document.getElementById( const switchAccountsElement = document.getElementById(
"switchaccounts" "switchaccounts"
) as HTMLDivElement; ) as HTMLDivElement;
switchAccountsElement.addEventListener("click", (event) => { switchAccountsElement.addEventListener("click", event=>{
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
let thisUser: Localuser; let thisUser: Localuser;
try { try{
console.log(users.users, users.currentuser); console.log(users.users, users.currentuser);
thisUser = new Localuser(users.users[users.currentuser]); thisUser = new Localuser(users.users[users.currentuser]);
thisUser.initwebsocket().then(() => { thisUser.initwebsocket().then(()=>{
thisUser.loaduser(); thisUser.loaduser();
thisUser.init(); thisUser.init();
const loading = document.getElementById("loading") as HTMLDivElement; const loading = document.getElementById("loading") as HTMLDivElement;
@ -113,7 +113,7 @@ async function waitForLoad(): Promise<void> {
loading.classList.remove("loading"); loading.classList.remove("loading");
console.log("done loading"); console.log("done loading");
}); });
} catch (e) { }catch(e){
console.error(e); console.error(e);
(document.getElementById("load-desc") as HTMLSpanElement).textContent = (document.getElementById("load-desc") as HTMLSpanElement).textContent =
"Account unable to start"; "Account unable to start";
@ -123,24 +123,24 @@ async function waitForLoad(): Promise<void> {
const menu = new Contextmenu("create rightclick"); const menu = new Contextmenu("create rightclick");
menu.addbutton( menu.addbutton(
"Create channel", "Create channel",
() => { ()=>{
if (thisUser.lookingguild) { if(thisUser.lookingguild){
thisUser.lookingguild.createchannels(); thisUser.lookingguild.createchannels();
} }
}, },
null, null,
() => thisUser.isAdmin() ()=>thisUser.isAdmin()
); );
menu.addbutton( menu.addbutton(
"Create category", "Create category",
() => { ()=>{
if (thisUser.lookingguild) { if(thisUser.lookingguild){
thisUser.lookingguild.createcategory(); thisUser.lookingguild.createcategory();
} }
}, },
null, null,
() => thisUser.isAdmin() ()=>thisUser.isAdmin()
); );
menu.bindContextmenu( menu.bindContextmenu(
@ -154,26 +154,26 @@ async function waitForLoad(): Promise<void> {
) as HTMLDivElement; ) as HTMLDivElement;
let replyingTo: Message | null = null; let replyingTo: Message | null = null;
async function handleEnter(event: KeyboardEvent): Promise<void> { async function handleEnter(event: KeyboardEvent): Promise<void>{
const channel = thisUser.channelfocus; const channel = thisUser.channelfocus;
if (!channel) return; if(!channel)return;
channel.typingstart(); channel.typingstart();
if (event.key === "Enter" && !event.shiftKey) { if(event.key === "Enter" && !event.shiftKey){
event.preventDefault(); event.preventDefault();
if (channel.editing) { if(channel.editing){
channel.editing.edit(markdown.rawString); channel.editing.edit(markdown.rawString);
channel.editing = null; channel.editing = null;
} else { }else{
replyingTo = thisUser.channelfocus replyingTo = thisUser.channelfocus
? thisUser.channelfocus.replyingto ? thisUser.channelfocus.replyingto
: null; : null;
if (replyingTo?.div) { if(replyingTo?.div){
replyingTo.div.classList.remove("replying"); replyingTo.div.classList.remove("replying");
} }
if (thisUser.channelfocus) { if(thisUser.channelfocus){
thisUser.channelfocus.replyingto = null; thisUser.channelfocus.replyingto = null;
} }
channel.sendMessage(markdown.rawString, { channel.sendMessage(markdown.rawString, {
@ -182,12 +182,12 @@ async function waitForLoad(): Promise<void> {
embeds: [], // Add an empty array for the embeds property embeds: [], // Add an empty array for the embeds property
replyingto: replyingTo, replyingto: replyingTo,
}); });
if (thisUser.channelfocus) { if(thisUser.channelfocus){
thisUser.channelfocus.makereplybox(); thisUser.channelfocus.makereplybox();
} }
} }
while (images.length) { while(images.length){
images.pop(); images.pop();
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);
} }
@ -204,18 +204,18 @@ async function waitForLoad(): Promise<void> {
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
typebox.markdown = markdown; typebox.markdown = markdown;
typebox.addEventListener("keyup", handleEnter); typebox.addEventListener("keyup", handleEnter);
typebox.addEventListener("keydown", (event) => { typebox.addEventListener("keydown", event=>{
if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
}); });
markdown.giveBox(typebox); markdown.giveBox(typebox);
const images: Blob[] = []; const images: Blob[] = [];
const imagesHtml: HTMLElement[] = []; const imagesHtml: HTMLElement[] = [];
document.addEventListener("paste", async (e: ClipboardEvent) => { document.addEventListener("paste", async (e: ClipboardEvent)=>{
if (!e.clipboardData) return; if(!e.clipboardData)return;
for (const file of Array.from(e.clipboardData.files)) { for(const file of Array.from(e.clipboardData.files)){
const fileInstance = File.initFromBlob(file); const fileInstance = File.initFromBlob(file);
e.preventDefault(); e.preventDefault();
const html = fileInstance.upHTML(images, file); const html = fileInstance.upHTML(images, file);
@ -227,18 +227,18 @@ async function waitForLoad(): Promise<void> {
setTheme(); setTheme();
function userSettings(): void { function userSettings(): void{
thisUser.showusersettings(); thisUser.showusersettings();
} }
(document.getElementById("settings") as HTMLImageElement).onclick = (document.getElementById("settings") as HTMLImageElement).onclick =
userSettings; userSettings;
if (mobile) { if(mobile){
const channelWrapper = document.getElementById( const channelWrapper = document.getElementById(
"channelw" "channelw"
) as HTMLDivElement; ) as HTMLDivElement;
channelWrapper.onclick = () => { channelWrapper.onclick = ()=>{
( (
document.getElementById("channels")!.parentNode as HTMLElement document.getElementById("channels")!.parentNode as HTMLElement
).classList.add("collapse"); ).classList.add("collapse");
@ -248,7 +248,7 @@ async function waitForLoad(): Promise<void> {
const mobileBack = document.getElementById("mobileback") as HTMLDivElement; const mobileBack = document.getElementById("mobileback") as HTMLDivElement;
mobileBack.textContent = "#"; mobileBack.textContent = "#";
mobileBack.onclick = () => { mobileBack.onclick = ()=>{
( (
document.getElementById("channels")!.parentNode as HTMLElement document.getElementById("channels")!.parentNode as HTMLElement
).classList.remove("collapse"); ).classList.remove("collapse");
@ -256,4 +256,4 @@ async function waitForLoad(): Promise<void> {
document.getElementById("servers")!.classList.remove("collapse"); document.getElementById("servers")!.classList.remove("collapse");
}; };
} }
})(); })();

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,24 @@
<body class="Dark-theme"> <body class="Dark-theme">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title" /> <meta content="Jank Client" property="og:title">
<meta <meta
content="A spacebar client that has DMs, replying and more" content="A spacebar client that has DMs, replying and more"
property="og:description" property="og:description"
/> >
<meta content="/logo.webp" property="og:image" /> <meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color" /> <meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet" /> <link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss" /> <link href="/themes.css" rel="stylesheet" id="lightcss">
</head> </head>
<div id="logindiv"> <div id="logindiv">
<h1>Login</h1> <h1>Login</h1>
<br /> <br >
<form id="form" submit="check(e)"> <form id="form" submit="check(e)">
<label for="instance"><b>Instance:</b></label <label for="instance"><b>Instance:</b></label
><br /> ><br >
<p id="verify"></p> <p id="verify"></p>
<input <input
type="search" type="search"
@ -29,27 +29,27 @@
value="" value=""
id="instancein" id="instancein"
required required
/><br /><br /> ><br ><br >
<label for="uname"><b>Email:</b></label <label for="uname"><b>Email:</b></label
><br /> ><br >
<input <input
type="text" type="text"
placeholder="Enter email address" placeholder="Enter email address"
name="uname" name="uname"
id="uname" id="uname"
required required
/><br /><br /> ><br ><br >
<label for="psw"><b>Password:</b></label <label for="psw"><b>Password:</b></label
><br /> ><br >
<input <input
type="password" type="password"
placeholder="Enter Password" placeholder="Enter Password"
name="psw" name="psw"
id="psw" id="psw"
required required
/><br /><br /><br /><br /> ><br ><br ><br ><br >
<p class="wrongred" id="wrong"></p> <p class="wrongred" id="wrong"></p>
<div id="h-captcha"></div> <div id="h-captcha"></div>

View file

@ -1,14 +1,14 @@
import { Dialog } from "./dialog.js"; import{ Dialog }from"./dialog.js";
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
function setTheme() { function setTheme(){
let name = localStorage.getItem("theme"); let name = localStorage.getItem("theme");
if (!name) { if(!name){
localStorage.setItem("theme", "Dark"); localStorage.setItem("theme", "Dark");
name = "Dark"; name = "Dark";
} }
document.body.className = name + "-theme"; document.body.className = name + "-theme";
} }
let instances: let instances:
| { | {
@ -31,179 +31,179 @@ login?: string;
| null; | null;
setTheme(); setTheme();
function getBulkUsers() { function getBulkUsers(){
const json = getBulkInfo(); const json = getBulkInfo();
for (const thing in json.users) { for(const thing in json.users){
json.users[thing] = new Specialuser(json.users[thing]); json.users[thing] = new Specialuser(json.users[thing]);
}
return json;
} }
return json; function trimswitcher(){
} const json = getBulkInfo();
function trimswitcher() { const map = new Map();
const json = getBulkInfo(); for(const thing in json.users){
const map = new Map(); const user = json.users[thing];
for (const thing in json.users) { let wellknown = user.serverurls.wellknown;
const user = json.users[thing]; if(wellknown.at(-1) !== "/"){
let wellknown = user.serverurls.wellknown; wellknown += "/";
if (wellknown.at(-1) !== "/") { }
wellknown += "/"; wellknown += user.username;
} if(map.has(wellknown)){
wellknown += user.username; const otheruser = map.get(wellknown);
if (map.has(wellknown)) { if(otheruser[1].serverurls.wellknown.at(-1) === "/"){
const otheruser = map.get(wellknown); delete json.users[otheruser[0]];
if (otheruser[1].serverurls.wellknown.at(-1) === "/") { map.set(wellknown, [thing, user]);
delete json.users[otheruser[0]]; }else{
map.set(wellknown, [thing, user]); delete json.users[thing];
} else { }
delete json.users[thing]; }else{
} map.set(wellknown, [thing, user]);
} else { }
map.set(wellknown, [thing, user]); }
} for(const thing in json.users){
} if(thing.at(-1) === "/"){
for (const thing in json.users) { const user = json.users[thing];
if (thing.at(-1) === "/") { delete json.users[thing];
const user = json.users[thing]; json.users[thing.slice(0, -1)] = user;
delete json.users[thing]; }
json.users[thing.slice(0, -1)] = user; }
} localStorage.setItem("userinfos", JSON.stringify(json));
} console.log(json);
localStorage.setItem("userinfos", JSON.stringify(json));
console.log(json);
} }
function getBulkInfo() { function getBulkInfo(){
return JSON.parse(localStorage.getItem("userinfos")!); return JSON.parse(localStorage.getItem("userinfos")!);
} }
function setDefaults() { function setDefaults(){
let userinfos = getBulkInfo(); let userinfos = getBulkInfo();
if (!userinfos) { if(!userinfos){
localStorage.setItem( localStorage.setItem(
"userinfos", "userinfos",
JSON.stringify({ JSON.stringify({
currentuser: null, currentuser: null,
users: {}, users: {},
preferences: { preferences: {
theme: "Dark", theme: "Dark",
notifications: false, notifications: false,
notisound: "three", notisound: "three",
}, },
}) })
); );
userinfos = getBulkInfo(); userinfos = getBulkInfo();
} }
if (userinfos.users === undefined) { if(userinfos.users === undefined){
userinfos.users = {}; userinfos.users = {};
} }
if (userinfos.accent_color === undefined) { if(userinfos.accent_color === undefined){
userinfos.accent_color = "#242443"; userinfos.accent_color = "#242443";
} }
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
"--accent-color", "--accent-color",
userinfos.accent_color userinfos.accent_color
); );
if (userinfos.preferences === undefined) { if(userinfos.preferences === undefined){
userinfos.preferences = { userinfos.preferences = {
theme: "Dark", theme: "Dark",
notifications: false, notifications: false,
notisound: "three", notisound: "three",
}; };
} }
if (userinfos.preferences && userinfos.preferences.notisound === undefined) { if(userinfos.preferences && userinfos.preferences.notisound === undefined){
userinfos.preferences.notisound = "three"; userinfos.preferences.notisound = "three";
} }
localStorage.setItem("userinfos", JSON.stringify(userinfos)); localStorage.setItem("userinfos", JSON.stringify(userinfos));
} }
setDefaults(); setDefaults();
class Specialuser { class Specialuser{
serverurls: { serverurls: {
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
wellknown: string; wellknown: string;
login: string; login: string;
}; };
email: string; email: string;
token: string; token: string;
loggedin; loggedin;
json; json;
constructor(json: any) { constructor(json: any){
if (json instanceof Specialuser) { if(json instanceof Specialuser){
console.error("specialuser can't construct from another specialuser"); console.error("specialuser can't construct from another specialuser");
}
this.serverurls = json.serverurls;
let apistring = new URL(json.serverurls.api).toString();
apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9";
this.serverurls.api = apistring;
this.serverurls.cdn = new URL(json.serverurls.cdn)
.toString()
.replace(/\/$/, "");
this.serverurls.gateway = new URL(json.serverurls.gateway)
.toString()
.replace(/\/$/, "");
this.serverurls.wellknown = new URL(json.serverurls.wellknown)
.toString()
.replace(/\/$/, "");
this.serverurls.login = new URL(json.serverurls.login)
.toString()
.replace(/\/$/, "");
this.email = json.email;
this.token = json.token;
this.loggedin = json.loggedin;
this.json = json;
this.json.localuserStore ??= {};
if(!this.serverurls || !this.email || !this.token){
console.error(
"There are fundamentally missing pieces of info missing from this user"
);
}
}
set pfpsrc(e){
this.json.pfpsrc = e;
this.updateLocal();
}
get pfpsrc(){
return this.json.pfpsrc;
}
set username(e){
this.json.username = e;
this.updateLocal();
}
get username(){
return this.json.username;
}
set localuserStore(e){
this.json.localuserStore = e;
this.updateLocal();
}
get localuserStore(){
return this.json.localuserStore;
}
get uid(){
return this.email + this.serverurls.wellknown;
}
toJSON(){
return this.json;
}
updateLocal(){
const info = getBulkInfo();
info.users[this.uid] = this.toJSON();
localStorage.setItem("userinfos", JSON.stringify(info));
}
} }
this.serverurls = json.serverurls; function adduser(user: typeof Specialuser.prototype.json){
let apistring = new URL(json.serverurls.api).toString(); user = new Specialuser(user);
apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; const info = getBulkInfo();
this.serverurls.api = apistring; info.users[user.uid] = user;
this.serverurls.cdn = new URL(json.serverurls.cdn) info.currentuser = user.uid;
.toString() localStorage.setItem("userinfos", JSON.stringify(info));
.replace(/\/$/, ""); return user;
this.serverurls.gateway = new URL(json.serverurls.gateway)
.toString()
.replace(/\/$/, "");
this.serverurls.wellknown = new URL(json.serverurls.wellknown)
.toString()
.replace(/\/$/, "");
this.serverurls.login = new URL(json.serverurls.login)
.toString()
.replace(/\/$/, "");
this.email = json.email;
this.token = json.token;
this.loggedin = json.loggedin;
this.json = json;
this.json.localuserStore ??= {};
if (!this.serverurls || !this.email || !this.token) {
console.error(
"There are fundamentally missing pieces of info missing from this user"
);
}
}
set pfpsrc(e) {
this.json.pfpsrc = e;
this.updateLocal();
}
get pfpsrc() {
return this.json.pfpsrc;
}
set username(e) {
this.json.username = e;
this.updateLocal();
}
get username() {
return this.json.username;
}
set localuserStore(e) {
this.json.localuserStore = e;
this.updateLocal();
}
get localuserStore() {
return this.json.localuserStore;
}
get uid() {
return this.email + this.serverurls.wellknown;
}
toJSON() {
return this.json;
}
updateLocal() {
const info = getBulkInfo();
info.users[this.uid] = this.toJSON();
localStorage.setItem("userinfos", JSON.stringify(info));
}
}
function adduser(user: typeof Specialuser.prototype.json) {
user = new Specialuser(user);
const info = getBulkInfo();
info.users[user.uid] = user;
info.currentuser = user.uid;
localStorage.setItem("userinfos", JSON.stringify(info));
return user;
} }
const instancein = document.getElementById("instancein") as HTMLInputElement; const instancein = document.getElementById("instancein") as HTMLInputElement;
let timeout: string | number | NodeJS.Timeout | undefined; let timeout: string | number | NodeJS.Timeout | undefined;
// let instanceinfo; // let instanceinfo;
const stringURLMap = new Map<string, string>(); const stringURLMap = new Map<string, string>();
const stringURLsMap = new Map< const stringURLsMap = new Map<
string, string,
{ {
wellknown: string; wellknown: string;
@ -213,7 +213,7 @@ const stringURLMap = new Map<string, string>();
login?: string; login?: string;
} }
>(); >();
async function getapiurls(str: string): Promise< async function getapiurls(str: string): Promise<
| { | {
api: string; api: string;
cdn: string; cdn: string;
@ -222,19 +222,19 @@ const stringURLMap = new Map<string, string>();
login: string; login: string;
} }
| false | false
> { >{
if (!URL.canParse(str)) { if(!URL.canParse(str)){
const val = stringURLMap.get(str); const val = stringURLMap.get(str);
if (val) { if(val){
str = val; str = val;
} else { }else{
const val = stringURLsMap.get(str); const val = stringURLsMap.get(str);
if (val) { if(val){
const responce = await fetch( const responce = await fetch(
val.api + val.api.endsWith("/") ? "" : "/" + "ping" val.api + val.api.endsWith("/") ? "" : "/" + "ping"
); );
if (responce.ok) { if(responce.ok){
if (val.login) { if(val.login){
return val as { return val as {
wellknown: string; wellknown: string;
api: string; api: string;
@ -242,7 +242,7 @@ const stringURLMap = new Map<string, string>();
gateway: string; gateway: string;
login: string; login: string;
}; };
} else { }else{
val.login = val.api; val.login = val.api;
return val as { return val as {
wellknown: string; wellknown: string;
@ -256,40 +256,39 @@ const stringURLMap = new Map<string, string>();
} }
} }
} }
if (str.at(-1) !== "/") { if(str.at(-1) !== "/"){
str += "/"; str += "/";
} }
let api: string; let api: string;
try { try{
const info = await fetch(`${str}/.well-known/spacebar`).then((x) => const info = await fetch(`${str}/.well-known/spacebar`).then(x=>x.json()
x.json()
); );
api = info.api; api = info.api;
} catch { }catch{
return false; return false;
} }
const url = new URL(api); const url = new URL(api);
try { try{
const info = await fetch( const info = await fetch(
`${api}${ `${api}${
url.pathname.includes("api") ? "" : "api" url.pathname.includes("api") ? "" : "api"
}/policies/instance/domains` }/policies/instance/domains`
).then((x) => x.json()); ).then(x=>x.json());
return { return{
api: info.apiEndpoint, api: info.apiEndpoint,
gateway: info.gateway, gateway: info.gateway,
cdn: info.cdn, cdn: info.cdn,
wellknown: str, wellknown: str,
login: url.toString(), login: url.toString(),
}; };
} catch { }catch{
const val = stringURLsMap.get(str); const val = stringURLsMap.get(str);
if (val) { if(val){
const responce = await fetch( const responce = await fetch(
val.api + val.api.endsWith("/") ? "" : "/" + "ping" val.api + val.api.endsWith("/") ? "" : "/" + "ping"
); );
if (responce.ok) { if(responce.ok){
if (val.login) { if(val.login){
return val as { return val as {
wellknown: string; wellknown: string;
api: string; api: string;
@ -297,7 +296,7 @@ const stringURLMap = new Map<string, string>();
gateway: string; gateway: string;
login: string; login: string;
}; };
} else { }else{
val.login = val.api; val.login = val.api;
return val as { return val as {
wellknown: string; wellknown: string;
@ -311,10 +310,10 @@ const stringURLMap = new Map<string, string>();
} }
return false; return false;
} }
} }
async function checkInstance(instance?: string) { async function checkInstance(instance?: string){
const verify = document.getElementById("verify"); const verify = document.getElementById("verify");
try { try{
verify!.textContent = "Checking Instance"; verify!.textContent = "Checking Instance";
const instanceValue = instance || (instancein as HTMLInputElement).value; const instanceValue = instance || (instancein as HTMLInputElement).value;
const instanceinfo = (await getapiurls(instanceValue)) as { const instanceinfo = (await getapiurls(instanceValue)) as {
@ -325,50 +324,50 @@ const stringURLMap = new Map<string, string>();
login: string; login: string;
value: string; value: string;
}; };
if (instanceinfo) { if(instanceinfo){
instanceinfo.value = instanceValue; instanceinfo.value = instanceValue;
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
verify!.textContent = "Instance is all good"; verify!.textContent = "Instance is all good";
// @ts-ignore // @ts-ignore
if (checkInstance.alt) { if(checkInstance.alt){
// @ts-ignore // @ts-ignore
checkInstance.alt(); checkInstance.alt();
} }
setTimeout((_: any) => { setTimeout((_: any)=>{
console.log(verify!.textContent); console.log(verify!.textContent);
verify!.textContent = ""; verify!.textContent = "";
}, 3000); }, 3000);
} else { }else{
verify!.textContent = "Invalid Instance, try again"; verify!.textContent = "Invalid Instance, try again";
} }
} catch { }catch{
console.log("catch"); console.log("catch");
verify!.textContent = "Invalid Instance, try again"; verify!.textContent = "Invalid Instance, try again";
} }
} }
if (instancein) { if(instancein){
console.log(instancein); console.log(instancein);
instancein.addEventListener("keydown", (_) => { instancein.addEventListener("keydown", _=>{
const verify = document.getElementById("verify"); const verify = document.getElementById("verify");
verify!.textContent = "Waiting to check Instance"; verify!.textContent = "Waiting to check Instance";
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(() => checkInstance(), 1000); timeout = setTimeout(()=>checkInstance(), 1000);
}); });
if (localStorage.getItem("instanceinfo")) { if(localStorage.getItem("instanceinfo")){
const json = JSON.parse(localStorage.getItem("instanceinfo")!); const json = JSON.parse(localStorage.getItem("instanceinfo")!);
if (json.value) { if(json.value){
(instancein as HTMLInputElement).value = json.value; (instancein as HTMLInputElement).value = json.value;
} else { }else{
(instancein as HTMLInputElement).value = json.wellknown; (instancein as HTMLInputElement).value = json.wellknown;
} }
} else { }else{
checkInstance("https://spacebar.chat/"); checkInstance("https://spacebar.chat/");
} }
} }
async function login(username: string, password: string, captcha: string) { async function login(username: string, password: string, captcha: string){
if (captcha === "") { if(captcha === ""){
captcha = ""; captcha = "";
} }
const options = { const options = {
@ -383,23 +382,23 @@ const stringURLMap = new Map<string, string>();
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
}, },
}; };
try { try{
const info = JSON.parse(localStorage.getItem("instanceinfo")!); const info = JSON.parse(localStorage.getItem("instanceinfo")!);
const api = info.login + (info.login.startsWith("/") ? "/" : ""); const api = info.login + (info.login.startsWith("/") ? "/" : "");
return await fetch(api + "/auth/login", options) return await fetch(api + "/auth/login", options)
.then((response) => response.json()) .then(response=>response.json())
.then((response) => { .then(response=>{
console.log(response, response.message); console.log(response, response.message);
if (response.message === "Invalid Form Body") { if(response.message === "Invalid Form Body"){
return response.errors.login._errors[0].message; return response.errors.login._errors[0].message;
console.log("test"); console.log("test");
} }
//this.serverurls||!this.email||!this.token //this.serverurls||!this.email||!this.token
console.log(response); console.log(response);
if (response.captcha_sitekey) { if(response.captcha_sitekey){
const capt = document.getElementById("h-captcha"); const capt = document.getElementById("h-captcha");
if (!capt!.children.length) { if(!capt!.children.length){
const capty = document.createElement("div"); const capty = document.createElement("div");
capty.classList.add("h-captcha"); capty.classList.add("h-captcha");
@ -408,12 +407,12 @@ const stringURLMap = new Map<string, string>();
script.src = "https://js.hcaptcha.com/1/api.js"; script.src = "https://js.hcaptcha.com/1/api.js";
capt!.append(script); capt!.append(script);
capt!.append(capty); capt!.append(capty);
} else { }else{
eval("hcaptcha.reset()"); eval("hcaptcha.reset()");
} }
} else { }else{
console.log(response); console.log(response);
if (response.ticket) { if(response.ticket){
let onetimecode = ""; let onetimecode = "";
new Dialog([ new Dialog([
"vdiv", "vdiv",
@ -422,7 +421,7 @@ const stringURLMap = new Map<string, string>();
"textbox", "textbox",
"", "",
"", "",
function (this: HTMLInputElement) { function(this: HTMLInputElement){
onetimecode = this.value; onetimecode = this.value;
}, },
], ],
@ -430,7 +429,7 @@ const stringURLMap = new Map<string, string>();
"button", "button",
"", "",
"Submit", "Submit",
function () { function(){
fetch(api + "/auth/mfa/totp", { fetch(api + "/auth/mfa/totp", {
method: "POST", method: "POST",
headers: { headers: {
@ -441,13 +440,13 @@ const stringURLMap = new Map<string, string>();
ticket: response.ticket, ticket: response.ticket,
}), }),
}) })
.then((r) => r.json()) .then(r=>r.json())
.then((response) => { .then(response=>{
if (response.message) { if(response.message){
alert(response.message); alert(response.message);
} else { }else{
console.warn(response); console.warn(response);
if (!response.token) return; if(!response.token)return;
adduser({ adduser({
serverurls: JSON.parse( serverurls: JSON.parse(
localStorage.getItem("instanceinfo")! localStorage.getItem("instanceinfo")!
@ -458,9 +457,9 @@ const stringURLMap = new Map<string, string>();
const redir = new URLSearchParams( const redir = new URLSearchParams(
window.location.search window.location.search
).get("goback"); ).get("goback");
if (redir) { if(redir){
window.location.href = redir; window.location.href = redir;
} else { }else{
window.location.href = "/channels/@me"; window.location.href = "/channels/@me";
} }
} }
@ -468,9 +467,9 @@ const stringURLMap = new Map<string, string>();
}, },
], ],
]).show(); ]).show();
} else { }else{
console.warn(response); console.warn(response);
if (!response.token) return; if(!response.token)return;
adduser({ adduser({
serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), serverurls: JSON.parse(localStorage.getItem("instanceinfo")!),
email: username, email: username,
@ -479,21 +478,21 @@ const stringURLMap = new Map<string, string>();
const redir = new URLSearchParams(window.location.search).get( const redir = new URLSearchParams(window.location.search).get(
"goback" "goback"
); );
if (redir) { if(redir){
window.location.href = redir; window.location.href = redir;
} else { }else{
window.location.href = "/channels/@me"; window.location.href = "/channels/@me";
} }
return ""; return"";
} }
} }
}); });
} catch (error) { }catch(error){
console.error("Error:", error); console.error("Error:", error);
} }
} }
async function check(e: SubmitEvent) { async function check(e: SubmitEvent){
e.preventDefault(); e.preventDefault();
const target = e.target as HTMLFormElement; const target = e.target as HTMLFormElement;
const h = await login( const h = await login(
@ -502,19 +501,19 @@ const stringURLMap = new Map<string, string>();
(target[3] as HTMLInputElement).value (target[3] as HTMLInputElement).value
); );
const wrongElement = document.getElementById("wrong"); const wrongElement = document.getElementById("wrong");
if (wrongElement) { if(wrongElement){
wrongElement.textContent = h; wrongElement.textContent = h;
} }
console.log(h); console.log(h);
} }
if (document.getElementById("form")) { if(document.getElementById("form")){
const form = document.getElementById("form"); const form = document.getElementById("form");
if (form) { if(form){
form.addEventListener("submit", (e: SubmitEvent) => check(e)); form.addEventListener("submit", (e: SubmitEvent)=>check(e));
} }
} }
//this currently does not work, and need to be implemented better at some time. //this currently does not work, and need to be implemented better at some time.
/* /*
if ("serviceWorker" in navigator){ if ("serviceWorker" in navigator){
navigator.serviceWorker.register("/service.js", { navigator.serviceWorker.register("/service.js", {
scope: "/", scope: "/",
@ -539,19 +538,19 @@ const stringURLMap = new Map<string, string>();
}) })
} }
*/ */
const switchurl = document.getElementById("switch") as HTMLAreaElement; const switchurl = document.getElementById("switch") as HTMLAreaElement;
if (switchurl) { if(switchurl){
switchurl.href += window.location.search; switchurl.href += window.location.search;
const instance = new URLSearchParams(window.location.search).get("instance"); const instance = new URLSearchParams(window.location.search).get("instance");
console.log(instance); console.log(instance);
if (instance) { if(instance){
instancein.value = instance; instancein.value = instance;
checkInstance(""); checkInstance("");
} }
} }
export { checkInstance }; export{ checkInstance };
trimswitcher(); trimswitcher();
export { export{
mobile, mobile,
getBulkUsers, getBulkUsers,
getBulkInfo, getBulkInfo,
@ -559,16 +558,16 @@ const stringURLMap = new Map<string, string>();
Specialuser, Specialuser,
getapiurls, getapiurls,
adduser, adduser,
}; };
const datalist = document.getElementById("instances"); const datalist = document.getElementById("instances");
console.warn(datalist); console.warn(datalist);
export function getInstances() { export function getInstances(){
return instances; return instances;
} }
fetch("/instances.json") fetch("/instances.json")
.then((_) => _.json()) .then(_=>_.json())
.then( .then(
( (
json: { json: {
@ -588,33 +587,33 @@ const stringURLMap = new Map<string, string>();
login?: string; login?: string;
}; };
}[] }[]
) => { )=>{
instances = json; instances = json;
if (datalist) { if(datalist){
console.warn(json); console.warn(json);
if (instancein && instancein.value === "") { if(instancein && instancein.value === ""){
instancein.value = json[0].name; instancein.value = json[0].name;
} }
for (const instance of json) { for(const instance of json){
if (instance.display === false) { if(instance.display === false){
continue; continue;
} }
const option = document.createElement("option"); const option = document.createElement("option");
option.disabled = !instance.online; option.disabled = !instance.online;
option.value = instance.name; option.value = instance.name;
if (instance.url) { if(instance.url){
stringURLMap.set(option.value, instance.url); stringURLMap.set(option.value, instance.url);
if (instance.urls) { if(instance.urls){
stringURLsMap.set(instance.url, instance.urls); stringURLsMap.set(instance.url, instance.urls);
} }
} else if (instance.urls) { }else if(instance.urls){
stringURLsMap.set(option.value, instance.urls); stringURLsMap.set(option.value, instance.urls);
} else { }else{
option.disabled = true; option.disabled = true;
} }
if (instance.description) { if(instance.description){
option.label = instance.description; option.label = instance.description;
} else { }else{
option.label = instance.name; option.label = instance.name;
} }
datalist.append(option); datalist.append(option);

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,19 +1,19 @@
import { Contextmenu } from "./contextmenu.js"; import{ Contextmenu }from"./contextmenu.js";
import { User } from "./user.js"; import{ User }from"./user.js";
import { Member } from "./member.js"; import{ Member }from"./member.js";
import { MarkDown } from "./markdown.js"; import{ MarkDown }from"./markdown.js";
import { Embed } from "./embed.js"; import{ Embed }from"./embed.js";
import { Channel } from "./channel.js"; import{ Channel }from"./channel.js";
import { Localuser } from "./localuser.js"; import{ Localuser }from"./localuser.js";
import { Role } from "./role.js"; import{ Role }from"./role.js";
import { File } from "./file.js"; import{ File }from"./file.js";
import { SnowFlake } from "./snowflake.js"; import{ SnowFlake }from"./snowflake.js";
import { memberjson, messagejson } from "./jsontypes.js"; import{ memberjson, messagejson }from"./jsontypes.js";
import { Emoji } from "./emoji.js"; import{ Emoji }from"./emoji.js";
import { Dialog } from "./dialog.js"; import{ Dialog }from"./dialog.js";
class Message extends SnowFlake { class Message extends SnowFlake{
static contextmenu = new Contextmenu<Message, undefined>("message menu"); static contextmenu = new Contextmenu<Message, undefined>("message menu");
owner: Channel; owner: Channel;
headers: Localuser["headers"]; headers: Localuser["headers"];
embeds!: Embed[]; embeds!: Embed[];
@ -45,80 +45,80 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
| undefined; | undefined;
member: Member | undefined; member: Member | undefined;
reactions!: messagejson["reactions"]; reactions!: messagejson["reactions"];
static setup() { static setup(){
this.del = new Promise((_) => { this.del = new Promise(_=>{
this.resolve = _; this.resolve = _;
}); });
Message.setupcmenu(); Message.setupcmenu();
} }
static setupcmenu() { static setupcmenu(){
Message.contextmenu.addbutton("Copy raw text", function (this: Message) { Message.contextmenu.addbutton("Copy raw text", function(this: Message){
navigator.clipboard.writeText(this.content.rawString); navigator.clipboard.writeText(this.content.rawString);
}); });
Message.contextmenu.addbutton("Reply", function (this: Message) { Message.contextmenu.addbutton("Reply", function(this: Message){
this.channel.setReplying(this); this.channel.setReplying(this);
}); });
Message.contextmenu.addbutton("Copy message id", function (this: Message) { Message.contextmenu.addbutton("Copy message id", function(this: Message){
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); });
Message.contextmenu.addsubmenu( Message.contextmenu.addsubmenu(
"Add reaction", "Add reaction",
function (this: Message, _, e: MouseEvent) { function(this: Message, _, e: MouseEvent){
Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => { Emoji.emojiPicker(e.x, e.y, this.localuser).then(_=>{
this.reactionToggle(_); this.reactionToggle(_);
}); });
} }
); );
Message.contextmenu.addbutton( Message.contextmenu.addbutton(
"Edit", "Edit",
function (this: Message) { function(this: Message){
this.setEdit(); this.setEdit();
}, },
null, null,
function () { function(){
return this.author.id === this.localuser.user.id; return this.author.id === this.localuser.user.id;
} }
); );
Message.contextmenu.addbutton( Message.contextmenu.addbutton(
"Delete message", "Delete message",
function (this: Message) { function(this: Message){
this.delete(); this.delete();
}, },
null, null,
function () { function(){
return this.canDelete(); return this.canDelete();
} }
); );
} }
setEdit() { setEdit(){
this.channel.editing = this; this.channel.editing = this;
const markdown = ( const markdown = (
document.getElementById("typebox") as HTMLDivElement & { document.getElementById("typebox") as HTMLDivElement & {
markdown: MarkDown; markdown: MarkDown;
} }
)["markdown"] as MarkDown; ).markdown as MarkDown;
markdown.txt = this.content.rawString.split(""); markdown.txt = this.content.rawString.split("");
markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement);
} }
constructor(messagejson: messagejson, owner: Channel) { constructor(messagejson: messagejson, owner: Channel){
super(messagejson.id); super(messagejson.id);
this.owner = owner; this.owner = owner;
this.headers = this.owner.headers; this.headers = this.owner.headers;
this.giveData(messagejson); this.giveData(messagejson);
this.owner.messages.set(this.id, this); this.owner.messages.set(this.id, this);
} }
reactionToggle(emoji: string | Emoji) { reactionToggle(emoji: string | Emoji){
let remove = false; let remove = false;
for (const thing of this.reactions) { for(const thing of this.reactions){
if (thing.emoji.name === emoji) { if(thing.emoji.name === emoji){
remove = thing.me; remove = thing.me;
break; break;
} }
} }
let reactiontxt: string; let reactiontxt: string;
if (emoji instanceof Emoji) { if(emoji instanceof Emoji){
reactiontxt = `${emoji.name}:${emoji.id}`; reactiontxt = `${emoji.name}:${emoji.id}`;
} else { }else{
reactiontxt = encodeURIComponent(emoji); reactiontxt = encodeURIComponent(emoji);
} }
fetch( fetch(
@ -129,113 +129,113 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
} }
); );
} }
giveData(messagejson: messagejson) { giveData(messagejson: messagejson){
const func = this.channel.infinite.snapBottom(); const func = this.channel.infinite.snapBottom();
for (const thing of Object.keys(messagejson)) { for(const thing of Object.keys(messagejson)){
if (thing === "attachments") { if(thing === "attachments"){
this.attachments = []; this.attachments = [];
for (const thing of messagejson.attachments) { for(const thing of messagejson.attachments){
this.attachments.push(new File(thing, this)); this.attachments.push(new File(thing, this));
} }
continue; continue;
} else if (thing === "content") { }else if(thing === "content"){
this.content = new MarkDown(messagejson[thing], this.channel); this.content = new MarkDown(messagejson[thing], this.channel);
continue; continue;
} else if (thing === "id") { }else if(thing === "id"){
continue; continue;
} else if (thing === "member") { }else if(thing === "member"){
Member.new(messagejson.member as memberjson, this.guild).then((_) => { Member.new(messagejson.member as memberjson, this.guild).then(_=>{
this.member = _ as Member; this.member = _ as Member;
}); });
continue; continue;
} else if (thing === "embeds") { }else if(thing === "embeds"){
this.embeds = []; this.embeds = [];
for (const thing in messagejson.embeds) { for(const thing in messagejson.embeds){
this.embeds[thing] = new Embed(messagejson.embeds[thing], this); this.embeds[thing] = new Embed(messagejson.embeds[thing], this);
} }
continue; continue;
} }
(this as any)[thing] = (messagejson as any)[thing]; (this as any)[thing] = (messagejson as any)[thing];
} }
if (messagejson.reactions?.length) { if(messagejson.reactions?.length){
console.log(messagejson.reactions, ":3"); console.log(messagejson.reactions, ":3");
} }
this.author = new User(messagejson.author, this.localuser); this.author = new User(messagejson.author, this.localuser);
for (const thing in messagejson.mentions) { for(const thing in messagejson.mentions){
this.mentions[thing] = new User( this.mentions[thing] = new User(
messagejson.mentions[thing], messagejson.mentions[thing],
this.localuser this.localuser
); );
} }
if (!this.member && this.guild.id !== "@me") { if(!this.member && this.guild.id !== "@me"){
this.author.resolvemember(this.guild).then((_) => { this.author.resolvemember(this.guild).then(_=>{
this.member = _; this.member = _;
}); });
} }
if (this.mentions.length || this.mention_roles.length) { if(this.mentions.length || this.mention_roles.length){
//currently mention_roles isn't implemented on the spacebar servers //currently mention_roles isn't implemented on the spacebar servers
console.log(this.mentions, this.mention_roles); console.log(this.mentions, this.mention_roles);
} }
if (this.mentionsuser(this.localuser.user)) { if(this.mentionsuser(this.localuser.user)){
console.log(this); console.log(this);
} }
if (this.div) { if(this.div){
this.generateMessage(); this.generateMessage();
} }
func(); func();
} }
canDelete() { canDelete(){
return ( return(
this.channel.hasPermission("MANAGE_MESSAGES") || this.channel.hasPermission("MANAGE_MESSAGES") ||
this.author === this.localuser.user this.author === this.localuser.user
); );
} }
get channel() { get channel(){
return this.owner; return this.owner;
} }
get guild() { get guild(){
return this.owner.guild; return this.owner.guild;
} }
get localuser() { get localuser(){
return this.owner.localuser; return this.owner.localuser;
} }
get info() { get info(){
return this.owner.info; return this.owner.info;
} }
messageevents(obj: HTMLDivElement) { messageevents(obj: HTMLDivElement){
// const func = Message.contextmenu.bindContextmenu(obj, this, undefined); // const func = Message.contextmenu.bindContextmenu(obj, this, undefined);
this.div = obj; this.div = obj;
obj.classList.add("messagediv"); obj.classList.add("messagediv");
} }
deleteDiv() { deleteDiv(){
if (!this.div) return; if(!this.div)return;
try { try{
this.div.remove(); this.div.remove();
this.div = undefined; this.div = undefined;
} catch (e) { }catch(e){
console.error(e); console.error(e);
} }
} }
mentionsuser(userd: User | Member) { mentionsuser(userd: User | Member){
if (userd instanceof User) { if(userd instanceof User){
return this.mentions.includes(userd); return this.mentions.includes(userd);
} else if (userd instanceof Member) { }else if(userd instanceof Member){
return this.mentions.includes(userd.user); return this.mentions.includes(userd.user);
} else { }else{
return;
} }
} }
getimages() { getimages(){
const build: File[] = []; const build: File[] = [];
for (const thing of this.attachments) { for(const thing of this.attachments){
if (thing.content_type.startsWith("image/")) { if(thing.content_type.startsWith("image/")){
build.push(thing); build.push(thing);
} }
} }
return build; return build;
} }
async edit(content: string) { async edit(content: string){
return await fetch( return await fetch(
this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id, this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id,
{ {
@ -245,15 +245,15 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
} }
); );
} }
delete() { delete(){
fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, { fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, {
headers: this.headers, headers: this.headers,
method: "DELETE", method: "DELETE",
}); });
} }
deleteEvent() { deleteEvent(){
console.log("deleted"); console.log("deleted");
if (this.div) { if(this.div){
this.div.remove(); this.div.remove();
this.div.innerHTML = ""; this.div.innerHTML = "";
this.div = undefined; this.div = undefined;
@ -263,69 +263,69 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
this.channel.idToPrev.delete(this.id); this.channel.idToPrev.delete(this.id);
this.channel.idToNext.delete(this.id); this.channel.idToNext.delete(this.id);
this.channel.messages.delete(this.id); this.channel.messages.delete(this.id);
if (prev && next) { if(prev && next){
this.channel.idToPrev.set(next, prev); this.channel.idToPrev.set(next, prev);
this.channel.idToNext.set(prev, next); this.channel.idToNext.set(prev, next);
} else if (prev) { }else if(prev){
this.channel.idToNext.delete(prev); this.channel.idToNext.delete(prev);
} else if (next) { }else if(next){
this.channel.idToPrev.delete(next); this.channel.idToPrev.delete(next);
} }
if (prev) { if(prev){
const prevmessage = this.channel.messages.get(prev); const prevmessage = this.channel.messages.get(prev);
if (prevmessage) { if(prevmessage){
prevmessage.generateMessage(); prevmessage.generateMessage();
} }
} }
if ( if(
this.channel.lastmessage === this || this.channel.lastmessage === this ||
this.channel.lastmessageid === this.id this.channel.lastmessageid === this.id
) { ){
if (prev) { if(prev){
this.channel.lastmessage = this.channel.messages.get(prev); this.channel.lastmessage = this.channel.messages.get(prev);
this.channel.lastmessageid = prev; this.channel.lastmessageid = prev;
} else { }else{
this.channel.lastmessage = undefined; this.channel.lastmessage = undefined;
this.channel.lastmessageid = undefined; this.channel.lastmessageid = undefined;
} }
} }
if (this.channel.lastreadmessageid === this.id) { if(this.channel.lastreadmessageid === this.id){
if (prev) { if(prev){
this.channel.lastreadmessageid = prev; this.channel.lastreadmessageid = prev;
} else { }else{
this.channel.lastreadmessageid = undefined; this.channel.lastreadmessageid = undefined;
} }
} }
console.log("deleted done"); console.log("deleted done");
} }
reactdiv!: WeakRef<HTMLDivElement>; reactdiv!: WeakRef<HTMLDivElement>;
blockedPropigate() { blockedPropigate(){
const previd = this.channel.idToPrev.get(this.id); const previd = this.channel.idToPrev.get(this.id);
if (!previd) { if(!previd){
this.generateMessage(); this.generateMessage();
return; return;
} }
const premessage = this.channel.messages.get(previd); const premessage = this.channel.messages.get(previd);
if (premessage?.author === this.author) { if(premessage?.author === this.author){
premessage.blockedPropigate(); premessage.blockedPropigate();
} else { }else{
this.generateMessage(); this.generateMessage();
} }
} }
generateMessage(premessage?: Message | undefined, ignoredblock = false) { generateMessage(premessage?: Message | undefined, ignoredblock = false){
if (!this.div) return; if(!this.div)return;
if (!premessage) { if(!premessage){
premessage = this.channel.messages.get( premessage = this.channel.messages.get(
this.channel.idToPrev.get(this.id) as string this.channel.idToPrev.get(this.id) as string
); );
} }
const div = this.div; const div = this.div;
for (const user of this.mentions) { for(const user of this.mentions){
if (user === this.localuser.user) { if(user === this.localuser.user){
div.classList.add("mentioned"); div.classList.add("mentioned");
} }
} }
if (this === this.channel.replyingto) { if(this === this.channel.replyingto){
div.classList.add("replying"); div.classList.add("replying");
} }
div.innerHTML = ""; div.innerHTML = "";
@ -333,43 +333,43 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
build.classList.add("flexltr", "message"); build.classList.add("flexltr", "message");
div.classList.remove("zeroheight"); div.classList.remove("zeroheight");
if (this.author.relationshipType === 2) { if(this.author.relationshipType === 2){
if (ignoredblock) { if(ignoredblock){
if (premessage?.author !== this.author) { if(premessage?.author !== this.author){
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = span.textContent =
"You have this user blocked, click to hide these messages."; "You have this user blocked, click to hide these messages.";
div.append(span); div.append(span);
span.classList.add("blocked"); span.classList.add("blocked");
span.onclick = (_) => { span.onclick = _=>{
const scroll = this.channel.infinite.scrollTop; const scroll = this.channel.infinite.scrollTop;
let next: Message | undefined = this; let next: Message | undefined = this;
while (next?.author === this.author) { while(next?.author === this.author){
next.generateMessage(); next.generateMessage();
next = this.channel.messages.get( next = this.channel.messages.get(
this.channel.idToNext.get(next.id) as string this.channel.idToNext.get(next.id) as string
); );
} }
if (this.channel.infinite.scollDiv && scroll) { if(this.channel.infinite.scollDiv && scroll){
this.channel.infinite.scollDiv.scrollTop = scroll; this.channel.infinite.scollDiv.scrollTop = scroll;
} }
}; };
} }
} else { }else{
div.classList.remove("topMessage"); div.classList.remove("topMessage");
if (premessage?.author === this.author) { if(premessage?.author === this.author){
div.classList.add("zeroheight"); div.classList.add("zeroheight");
premessage.blockedPropigate(); premessage.blockedPropigate();
div.appendChild(build); div.appendChild(build);
return div; return div;
} else { }else{
build.classList.add("blocked", "topMessage"); build.classList.add("blocked", "topMessage");
const span = document.createElement("span"); const span = document.createElement("span");
let count = 1; let count = 1;
let next = this.channel.messages.get( let next = this.channel.messages.get(
this.channel.idToNext.get(this.id) as string this.channel.idToNext.get(this.id) as string
); );
while (next?.author === this.author) { while(next?.author === this.author){
count++; count++;
next = this.channel.messages.get( next = this.channel.messages.get(
this.channel.idToNext.get(next.id) as string this.channel.idToNext.get(next.id) as string
@ -377,18 +377,18 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
} }
span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`; span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`;
build.append(span); build.append(span);
span.onclick = (_) => { span.onclick = _=>{
const scroll = this.channel.infinite.scrollTop; const scroll = this.channel.infinite.scrollTop;
const func = this.channel.infinite.snapBottom(); const func = this.channel.infinite.snapBottom();
let next: Message | undefined = this; let next: Message | undefined = this;
while (next?.author === this.author) { while(next?.author === this.author){
next.generateMessage(undefined, true); next.generateMessage(undefined, true);
next = this.channel.messages.get( next = this.channel.messages.get(
this.channel.idToNext.get(next.id) as string this.channel.idToNext.get(next.id) as string
); );
console.log("loopy"); console.log("loopy");
} }
if (this.channel.infinite.scollDiv && scroll) { if(this.channel.infinite.scollDiv && scroll){
func(); func();
this.channel.infinite.scollDiv.scrollTop = scroll; this.channel.infinite.scollDiv.scrollTop = scroll;
} }
@ -398,7 +398,7 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
} }
} }
} }
if (this.message_reference) { if(this.message_reference){
const replyline = document.createElement("div"); const replyline = document.createElement("div");
const line = document.createElement("hr"); const line = document.createElement("hr");
const minipfp = document.createElement("img"); const minipfp = document.createElement("img");
@ -417,8 +417,8 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
line.classList.add("startreply"); line.classList.add("startreply");
replyline.classList.add("replyflex"); replyline.classList.add("replyflex");
// TODO: Fix this // TODO: Fix this
this.channel.getmessage(this.message_reference.id).then((message) => { this.channel.getmessage(this.message_reference.id).then(message=>{
if (message.author.relationshipType === 2) { if(message.author.relationshipType === 2){
username.textContent = "Blocked user"; username.textContent = "Blocked user";
return; return;
} }
@ -429,18 +429,18 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
username.textContent = author.username; username.textContent = author.username;
author.bind(username, this.guild); author.bind(username, this.guild);
}); });
reply.onclick = (_) => { reply.onclick = _=>{
// TODO: FIX this // TODO: FIX this
this.channel.infinite.focus(this.message_reference.id); this.channel.infinite.focus(this.message_reference.id);
}; };
div.appendChild(replyline); div.appendChild(replyline);
} }
div.appendChild(build); div.appendChild(build);
if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { if({ 0: true, 19: true }[this.type] || this.attachments.length !== 0){
const pfpRow = document.createElement("div"); const pfpRow = document.createElement("div");
pfpRow.classList.add("flexltr"); pfpRow.classList.add("flexltr");
let pfpparent, current; let pfpparent, current;
if (premessage != null) { if(premessage != null){
pfpparent ??= premessage; pfpparent ??= premessage;
// @ts-ignore // @ts-ignore
// TODO: type this // TODO: type this
@ -452,12 +452,12 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
} }
const combine = const combine =
premessage?.author != this.author || current || this.message_reference; premessage?.author != this.author || current || this.message_reference;
if (combine) { if(combine){
const pfp = this.author.buildpfp(); const pfp = this.author.buildpfp();
this.author.bind(pfp, this.guild, false); this.author.bind(pfp, this.guild, false);
pfpRow.appendChild(pfp); pfpRow.appendChild(pfp);
} else { }else{
div["pfpparent"] = pfpparent; div.pfpparent = pfpparent;
} }
pfpRow.classList.add("pfprow"); pfpRow.classList.add("pfprow");
build.appendChild(pfpRow); build.appendChild(pfpRow);
@ -466,7 +466,7 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
const texttxt = document.createElement("div"); const texttxt = document.createElement("div");
texttxt.classList.add("commentrow", "flexttb"); texttxt.classList.add("commentrow", "flexttb");
text.appendChild(texttxt); text.appendChild(texttxt);
if (combine) { if(combine){
const username = document.createElement("span"); const username = document.createElement("span");
username.classList.add("username"); username.classList.add("username");
this.author.bind(username, this.guild); this.author.bind(username, this.guild);
@ -475,7 +475,7 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
const userwrap = document.createElement("div"); const userwrap = document.createElement("div");
userwrap.classList.add("flexltr"); userwrap.classList.add("flexltr");
userwrap.appendChild(username); userwrap.appendChild(username);
if (this.author.bot) { if(this.author.bot){
const username = document.createElement("span"); const username = document.createElement("span");
username.classList.add("bot"); username.classList.add("bot");
username.textContent = "BOT"; username.textContent = "BOT";
@ -487,36 +487,36 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
userwrap.appendChild(time); userwrap.appendChild(time);
texttxt.appendChild(userwrap); texttxt.appendChild(userwrap);
} else { }else{
div.classList.remove("topMessage"); div.classList.remove("topMessage");
} }
const messaged = this.content.makeHTML(); const messaged = this.content.makeHTML();
(div as any)["txt"] = messaged; (div as any).txt = messaged;
const messagedwrap = document.createElement("div"); const messagedwrap = document.createElement("div");
messagedwrap.classList.add("flexttb"); messagedwrap.classList.add("flexttb");
messagedwrap.appendChild(messaged); messagedwrap.appendChild(messaged);
texttxt.appendChild(messagedwrap); texttxt.appendChild(messagedwrap);
build.appendChild(text); build.appendChild(text);
if (this.attachments.length) { if(this.attachments.length){
console.log(this.attachments); console.log(this.attachments);
const attach = document.createElement("div"); const attach = document.createElement("div");
attach.classList.add("flexltr"); attach.classList.add("flexltr");
for (const thing of this.attachments) { for(const thing of this.attachments){
attach.appendChild(thing.getHTML()); attach.appendChild(thing.getHTML());
} }
messagedwrap.appendChild(attach); messagedwrap.appendChild(attach);
} }
if (this.embeds.length) { if(this.embeds.length){
const embeds = document.createElement("div"); const embeds = document.createElement("div");
embeds.classList.add("flexltr"); embeds.classList.add("flexltr");
for (const thing of this.embeds) { for(const thing of this.embeds){
embeds.appendChild(thing.generateHTML()); embeds.appendChild(thing.generateHTML());
} }
messagedwrap.appendChild(embeds); messagedwrap.appendChild(embeds);
} }
// //
} else if (this.type === 7) { }else if(this.type === 7){
const text = document.createElement("div"); const text = document.createElement("div");
text.classList.add("flexttb"); text.classList.add("flexttb");
const texttxt = document.createElement("div"); const texttxt = document.createElement("div");
@ -524,7 +524,7 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
build.appendChild(text); build.appendChild(text);
texttxt.classList.add("flexltr"); texttxt.classList.add("flexltr");
const messaged = document.createElement("span"); const messaged = document.createElement("span");
div["txt"] = messaged; div.txt = messaged;
messaged.textContent = "welcome: "; messaged.textContent = "welcome: ";
texttxt.appendChild(messaged); texttxt.appendChild(messaged);
@ -549,45 +549,45 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
this.bindButtonEvent(); this.bindButtonEvent();
return div; return div;
} }
bindButtonEvent() { bindButtonEvent(){
if (this.div) { if(this.div){
let buttons: HTMLDivElement | undefined; let buttons: HTMLDivElement | undefined;
this.div.onmouseenter = (_) => { this.div.onmouseenter = _=>{
if (buttons) { if(buttons){
buttons.remove(); buttons.remove();
buttons = undefined; buttons = undefined;
} }
if (this.div) { if(this.div){
buttons = document.createElement("div"); buttons = document.createElement("div");
buttons.classList.add("messageButtons", "flexltr"); buttons.classList.add("messageButtons", "flexltr");
if (this.channel.hasPermission("SEND_MESSAGES")) { if(this.channel.hasPermission("SEND_MESSAGES")){
const container = document.createElement("div"); const container = document.createElement("div");
const reply = document.createElement("span"); const reply = document.createElement("span");
reply.classList.add("svgtheme", "svg-reply", "svgicon"); reply.classList.add("svgtheme", "svg-reply", "svgicon");
container.append(reply); container.append(reply);
buttons.append(container); buttons.append(container);
container.onclick = (_) => { container.onclick = _=>{
this.channel.setReplying(this); this.channel.setReplying(this);
}; };
} }
if (this.author === this.localuser.user) { if(this.author === this.localuser.user){
const container = document.createElement("div"); const container = document.createElement("div");
const edit = document.createElement("span"); const edit = document.createElement("span");
edit.classList.add("svgtheme", "svg-edit", "svgicon"); edit.classList.add("svgtheme", "svg-edit", "svgicon");
container.append(edit); container.append(edit);
buttons.append(container); buttons.append(container);
container.onclick = (_) => { container.onclick = _=>{
this.setEdit(); this.setEdit();
}; };
} }
if (this.canDelete()) { if(this.canDelete()){
const container = document.createElement("div"); const container = document.createElement("div");
const reply = document.createElement("span"); const reply = document.createElement("span");
reply.classList.add("svgtheme", "svg-delete", "svgicon"); reply.classList.add("svgtheme", "svg-delete", "svgicon");
container.append(reply); container.append(reply);
buttons.append(container); buttons.append(container);
container.onclick = (_) => { container.onclick = _=>{
if (_.shiftKey) { if(_.shiftKey){
this.delete(); this.delete();
return; return;
} }
@ -598,7 +598,7 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
"button", "button",
"", "",
"yes", "yes",
() => { ()=>{
this.delete(); this.delete();
diaolog.hide(); diaolog.hide();
}, },
@ -607,7 +607,7 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
"button", "button",
"", "",
"no", "no",
() => { ()=>{
diaolog.hide(); diaolog.hide();
}, },
], ],
@ -615,40 +615,40 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
diaolog.show(); diaolog.show();
}; };
} }
if (buttons.childNodes.length !== 0) { if(buttons.childNodes.length !== 0){
this.div.append(buttons); this.div.append(buttons);
} }
} }
}; };
this.div.onmouseleave = (_) => { this.div.onmouseleave = _=>{
if (buttons) { if(buttons){
buttons.remove(); buttons.remove();
buttons = undefined; buttons = undefined;
} }
}; };
} }
} }
updateReactions() { updateReactions(){
const reactdiv = this.reactdiv.deref(); const reactdiv = this.reactdiv.deref();
if (!reactdiv) return; if(!reactdiv)return;
const func = this.channel.infinite.snapBottom(); const func = this.channel.infinite.snapBottom();
reactdiv.innerHTML = ""; reactdiv.innerHTML = "";
for (const thing of this.reactions) { for(const thing of this.reactions){
const reaction = document.createElement("div"); const reaction = document.createElement("div");
reaction.classList.add("reaction"); reaction.classList.add("reaction");
if (thing.me) { if(thing.me){
reaction.classList.add("meReacted"); reaction.classList.add("meReacted");
} }
let emoji: HTMLElement; let emoji: HTMLElement;
if (thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)) { if(thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)){
if (/\d{17,21}/.test(thing.emoji.name)) if(/\d{17,21}/.test(thing.emoji.name))
thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug
const emo = new Emoji( const emo = new Emoji(
thing.emoji as { name: string; id: string; animated: boolean }, thing.emoji as { name: string; id: string; animated: boolean },
this.guild this.guild
); );
emoji = emo.getHTML(false); emoji = emo.getHTML(false);
} else { }else{
emoji = document.createElement("p"); emoji = document.createElement("p");
emoji.textContent = thing.emoji.name; emoji.textContent = thing.emoji.name;
} }
@ -659,17 +659,17 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
reaction.append(emoji); reaction.append(emoji);
reactdiv.append(reaction); reactdiv.append(reaction);
reaction.onclick = (_) => { reaction.onclick = _=>{
this.reactionToggle(thing.emoji.name); this.reactionToggle(thing.emoji.name);
}; };
} }
func(); func();
} }
reactionAdd(data: { name: string }, member: Member | { id: string }) { reactionAdd(data: { name: string }, member: Member | { id: string }){
for (const thing of this.reactions) { for(const thing of this.reactions){
if (thing.emoji.name === data.name) { if(thing.emoji.name === data.name){
thing.count++; thing.count++;
if (member.id === this.localuser.user.id) { if(member.id === this.localuser.user.id){
thing.me = true; thing.me = true;
this.updateReactions(); this.updateReactions();
return; return;
@ -683,19 +683,19 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
}); });
this.updateReactions(); this.updateReactions();
} }
reactionRemove(data: { name: string }, id: string) { reactionRemove(data: { name: string }, id: string){
console.log("test"); console.log("test");
for (const i in this.reactions) { for(const i in this.reactions){
const thing = this.reactions[i]; const thing = this.reactions[i];
console.log(thing, data); console.log(thing, data);
if (thing.emoji.name === data.name) { if(thing.emoji.name === data.name){
thing.count--; thing.count--;
if (thing.count === 0) { if(thing.count === 0){
this.reactions.splice(Number(i), 1); this.reactions.splice(Number(i), 1);
this.updateReactions(); this.updateReactions();
return; return;
} }
if (id === this.localuser.user.id) { if(id === this.localuser.user.id){
thing.me = false; thing.me = false;
this.updateReactions(); this.updateReactions();
return; return;
@ -703,60 +703,59 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
} }
} }
} }
reactionRemoveAll() { reactionRemoveAll(){
this.reactions = []; this.reactions = [];
this.updateReactions(); this.updateReactions();
} }
reactionRemoveEmoji(emoji: Emoji) { reactionRemoveEmoji(emoji: Emoji){
for (const i in this.reactions) { for(const i in this.reactions){
const reaction = this.reactions[i]; const reaction = this.reactions[i];
if ( if(
(reaction.emoji.id && reaction.emoji.id == emoji.id) || (reaction.emoji.id && reaction.emoji.id == emoji.id) ||
(!reaction.emoji.id && reaction.emoji.name == emoji.name) (!reaction.emoji.id && reaction.emoji.name == emoji.name)
) { ){
this.reactions.splice(Number(i), 1); this.reactions.splice(Number(i), 1);
this.updateReactions(); this.updateReactions();
break; break;
} }
} }
} }
buildhtml(premessage?: Message | undefined): HTMLElement { buildhtml(premessage?: Message | undefined): HTMLElement{
if (this.div) { if(this.div){
console.error(`HTML for ${this.id} already exists, aborting`); console.error(`HTML for ${this.id} already exists, aborting`);
return this.div; return this.div;
} }
try { try{
const div = document.createElement("div"); const div = document.createElement("div");
this.div = div; this.div = div;
this.messageevents(div); this.messageevents(div);
return this.generateMessage(premessage) as HTMLElement; return this.generateMessage(premessage) as HTMLElement;
} catch (e) { }catch(e){
console.error(e); console.error(e);
} }
return this.div as HTMLElement; return this.div as HTMLElement;
} }
} }
let now: string; let now: string;
let yesterdayStr: string; let yesterdayStr: string;
function formatTime(date: Date) { function formatTime(date: Date){
updateTimes(); updateTimes();
const datestring = date.toLocaleDateString(); const datestring = date.toLocaleDateString();
const formatTime = (date: Date) => const formatTime = (date: Date)=>date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
if (datestring === now) { if(datestring === now){
return `Today at ${formatTime(date)}`; return`Today at ${formatTime(date)}`;
} else if (datestring === yesterdayStr) { }else if(datestring === yesterdayStr){
return `Yesterday at ${formatTime(date)}`; return`Yesterday at ${formatTime(date)}`;
} else { }else{
return `${date.toLocaleDateString()} at ${formatTime(date)}`; return`${date.toLocaleDateString()} at ${formatTime(date)}`;
} }
} }
let tomorrow = 0; let tomorrow = 0;
updateTimes(); updateTimes();
function updateTimes() { function updateTimes(){
if (tomorrow < Date.now()) { if(tomorrow < Date.now()){
const d = new Date(); const d = new Date();
tomorrow = d.setHours(24, 0, 0, 0); tomorrow = d.setHours(24, 0, 0, 0);
now = new Date().toLocaleDateString(); now = new Date().toLocaleDateString();
@ -764,6 +763,6 @@ static contextmenu = new Contextmenu<Message, undefined>("message menu");
yesterday.setDate(new Date().getDate() - 1); yesterday.setDate(new Date().getDate() - 1);
yesterdayStr = yesterday.toLocaleDateString(); yesterdayStr = yesterday.toLocaleDateString();
} }
} }
Message.setup(); Message.setup();
export { Message }; export{ Message };

View file

@ -1,347 +1,347 @@
class Permissions { class Permissions{
allow: bigint; allow: bigint;
deny: bigint; deny: bigint;
readonly hasDeny: boolean; readonly hasDeny: boolean;
constructor(allow: string, deny: string = "") { constructor(allow: string, deny: string = ""){
this.hasDeny = Boolean(deny); this.hasDeny = Boolean(deny);
try { try{
this.allow = BigInt(allow); this.allow = BigInt(allow);
this.deny = BigInt(deny); this.deny = BigInt(deny);
} catch { }catch{
this.allow = 0n; this.allow = 0n;
this.deny = 0n; this.deny = 0n;
console.error( console.error(
`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` `Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`
); );
} }
} }
getPermissionbit(b: number, big: bigint): boolean { getPermissionbit(b: number, big: bigint): boolean{
return Boolean((big >> BigInt(b)) & 1n); return Boolean((big >> BigInt(b)) & 1n);
} }
setPermissionbit(b: number, state: boolean, big: bigint): bigint { setPermissionbit(b: number, state: boolean, big: bigint): bigint{
const bit = 1n << BigInt(b); const bit = 1n << BigInt(b);
return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 return(big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
} }
static map: { static map: {
[key: number | string]: [key: number | string]:
| { name: string; readableName: string; description: string } | { name: string; readableName: string; description: string }
| number; | number;
}; };
static info: { name: string; readableName: string; description: string }[]; static info: { name: string; readableName: string; description: string }[];
static makeMap() { static makeMap(){
Permissions.info = [ Permissions.info = [
//for people in the future, do not reorder these, the creation of the map realize on the order //for people in the future, do not reorder these, the creation of the map realize on the order
{ {
name: "CREATE_INSTANT_INVITE", name: "CREATE_INSTANT_INVITE",
readableName: "Create invite", readableName: "Create invite",
description: "Allows the user to create invites for the guild", description: "Allows the user to create invites for the guild",
}, },
{ {
name: "KICK_MEMBERS", name: "KICK_MEMBERS",
readableName: "Kick members", readableName: "Kick members",
description: "Allows the user to kick members from the guild", description: "Allows the user to kick members from the guild",
}, },
{ {
name: "BAN_MEMBERS", name: "BAN_MEMBERS",
readableName: "Ban members", readableName: "Ban members",
description: "Allows the user to ban members from the guild", description: "Allows the user to ban members from the guild",
}, },
{ {
name: "ADMINISTRATOR", name: "ADMINISTRATOR",
readableName: "Administrator", readableName: "Administrator",
description: description:
"Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!",
}, },
{ {
name: "MANAGE_CHANNELS", name: "MANAGE_CHANNELS",
readableName: "Manage channels", readableName: "Manage channels",
description: "Allows the user to manage and edit channels", description: "Allows the user to manage and edit channels",
}, },
{ {
name: "MANAGE_GUILD", name: "MANAGE_GUILD",
readableName: "Manage guild", readableName: "Manage guild",
description: "Allows management and editing of the guild", description: "Allows management and editing of the guild",
}, },
{ {
name: "ADD_REACTIONS", name: "ADD_REACTIONS",
readableName: "Add reactions", readableName: "Add reactions",
description: "Allows user to add reactions to messages", description: "Allows user to add reactions to messages",
}, },
{ {
name: "VIEW_AUDIT_LOG", name: "VIEW_AUDIT_LOG",
readableName: "View audit log", readableName: "View audit log",
description: "Allows the user to view the audit log", description: "Allows the user to view the audit log",
}, },
{ {
name: "PRIORITY_SPEAKER", name: "PRIORITY_SPEAKER",
readableName: "Priority speaker", readableName: "Priority speaker",
description: "Allows for using priority speaker in a voice channel", description: "Allows for using priority speaker in a voice channel",
}, },
{ {
name: "STREAM", name: "STREAM",
readableName: "Video", readableName: "Video",
description: "Allows the user to stream", description: "Allows the user to stream",
}, },
{ {
name: "VIEW_CHANNEL", name: "VIEW_CHANNEL",
readableName: "View channels", readableName: "View channels",
description: "Allows the user to view the channel", description: "Allows the user to view the channel",
}, },
{ {
name: "SEND_MESSAGES", name: "SEND_MESSAGES",
readableName: "Send messages", readableName: "Send messages",
description: "Allows user to send messages", description: "Allows user to send messages",
}, },
{ {
name: "SEND_TTS_MESSAGES", name: "SEND_TTS_MESSAGES",
readableName: "Send text-to-speech messages", readableName: "Send text-to-speech messages",
description: "Allows the user to send text-to-speech messages", description: "Allows the user to send text-to-speech messages",
}, },
{ {
name: "MANAGE_MESSAGES", name: "MANAGE_MESSAGES",
readableName: "Manage messages", readableName: "Manage messages",
description: "Allows the user to delete messages that aren't their own", description: "Allows the user to delete messages that aren't their own",
}, },
{ {
name: "EMBED_LINKS", name: "EMBED_LINKS",
readableName: "Embed links", readableName: "Embed links",
description: "Allow links sent by this user to auto-embed", description: "Allow links sent by this user to auto-embed",
}, },
{ {
name: "ATTACH_FILES", name: "ATTACH_FILES",
readableName: "Attach files", readableName: "Attach files",
description: "Allows the user to attach files", description: "Allows the user to attach files",
}, },
{ {
name: "READ_MESSAGE_HISTORY", name: "READ_MESSAGE_HISTORY",
readableName: "Read message history", readableName: "Read message history",
description: "Allows user to read the message history", description: "Allows user to read the message history",
}, },
{ {
name: "MENTION_EVERYONE", name: "MENTION_EVERYONE",
readableName: "Mention @everyone, @here and all roles", readableName: "Mention @everyone, @here and all roles",
description: "Allows the user to mention everyone", description: "Allows the user to mention everyone",
}, },
{ {
name: "USE_EXTERNAL_EMOJIS", name: "USE_EXTERNAL_EMOJIS",
readableName: "Use external emojis", readableName: "Use external emojis",
description: "Allows the user to use external emojis", description: "Allows the user to use external emojis",
}, },
{ {
name: "VIEW_GUILD_INSIGHTS", name: "VIEW_GUILD_INSIGHTS",
readableName: "View guild insights", readableName: "View guild insights",
description: "Allows the user to see guild insights", description: "Allows the user to see guild insights",
}, },
{ {
name: "CONNECT", name: "CONNECT",
readableName: "Connect", readableName: "Connect",
description: "Allows the user to connect to a voice channel", description: "Allows the user to connect to a voice channel",
}, },
{ {
name: "SPEAK", name: "SPEAK",
readableName: "Speak", readableName: "Speak",
description: "Allows the user to speak in a voice channel", description: "Allows the user to speak in a voice channel",
}, },
{ {
name: "MUTE_MEMBERS", name: "MUTE_MEMBERS",
readableName: "Mute members", readableName: "Mute members",
description: "Allows user to mute other members", description: "Allows user to mute other members",
}, },
{ {
name: "DEAFEN_MEMBERS", name: "DEAFEN_MEMBERS",
readableName: "Deafen members", readableName: "Deafen members",
description: "Allows user to deafen other members", description: "Allows user to deafen other members",
}, },
{ {
name: "MOVE_MEMBERS", name: "MOVE_MEMBERS",
readableName: "Move members", readableName: "Move members",
description: "Allows the user to move members between voice channels", description: "Allows the user to move members between voice channels",
}, },
{ {
name: "USE_VAD", name: "USE_VAD",
readableName: "Use voice activity detection", readableName: "Use voice activity detection",
description: description:
"Allows users to speak in a voice channel by simply talking", "Allows users to speak in a voice channel by simply talking",
}, },
{ {
name: "CHANGE_NICKNAME", name: "CHANGE_NICKNAME",
readableName: "Change nickname", readableName: "Change nickname",
description: "Allows the user to change their own nickname", description: "Allows the user to change their own nickname",
}, },
{ {
name: "MANAGE_NICKNAMES", name: "MANAGE_NICKNAMES",
readableName: "Manage nicknames", readableName: "Manage nicknames",
description: "Allows user to change nicknames of other members", description: "Allows user to change nicknames of other members",
}, },
{ {
name: "MANAGE_ROLES", name: "MANAGE_ROLES",
readableName: "Manage roles", readableName: "Manage roles",
description: "Allows user to edit and manage roles", description: "Allows user to edit and manage roles",
}, },
{ {
name: "MANAGE_WEBHOOKS", name: "MANAGE_WEBHOOKS",
readableName: "Manage webhooks", readableName: "Manage webhooks",
description: "Allows management and editing of webhooks", description: "Allows management and editing of webhooks",
}, },
{ {
name: "MANAGE_GUILD_EXPRESSIONS", name: "MANAGE_GUILD_EXPRESSIONS",
readableName: "Manage expressions", readableName: "Manage expressions",
description: "Allows for managing emoji, stickers, and soundboards", description: "Allows for managing emoji, stickers, and soundboards",
}, },
{ {
name: "USE_APPLICATION_COMMANDS", name: "USE_APPLICATION_COMMANDS",
readableName: "Use application commands", readableName: "Use application commands",
description: "Allows the user to use application commands", description: "Allows the user to use application commands",
}, },
{ {
name: "REQUEST_TO_SPEAK", name: "REQUEST_TO_SPEAK",
readableName: "Request to speak", readableName: "Request to speak",
description: "Allows user to request to speak in stage channel", description: "Allows user to request to speak in stage channel",
}, },
{ {
name: "MANAGE_EVENTS", name: "MANAGE_EVENTS",
readableName: "Manage events", readableName: "Manage events",
description: "Allows user to edit and manage events", description: "Allows user to edit and manage events",
}, },
{ {
name: "MANAGE_THREADS", name: "MANAGE_THREADS",
readableName: "Manage threads", readableName: "Manage threads",
description: description:
"Allows the user to delete and archive threads and view all private threads", "Allows the user to delete and archive threads and view all private threads",
}, },
{ {
name: "CREATE_PUBLIC_THREADS", name: "CREATE_PUBLIC_THREADS",
readableName: "Create public threads", readableName: "Create public threads",
description: "Allows the user to create public threads", description: "Allows the user to create public threads",
}, },
{ {
name: "CREATE_PRIVATE_THREADS", name: "CREATE_PRIVATE_THREADS",
readableName: "Create private threads", readableName: "Create private threads",
description: "Allows the user to create private threads", description: "Allows the user to create private threads",
}, },
{ {
name: "USE_EXTERNAL_STICKERS", name: "USE_EXTERNAL_STICKERS",
readableName: "Use external stickers", readableName: "Use external stickers",
description: "Allows user to use external stickers", description: "Allows user to use external stickers",
}, },
{ {
name: "SEND_MESSAGES_IN_THREADS", name: "SEND_MESSAGES_IN_THREADS",
readableName: "Send messages in threads", readableName: "Send messages in threads",
description: "Allows the user to send messages in threads", description: "Allows the user to send messages in threads",
}, },
{ {
name: "USE_EMBEDDED_ACTIVITIES", name: "USE_EMBEDDED_ACTIVITIES",
readableName: "Use activities", readableName: "Use activities",
description: "Allows the user to use embedded activities", description: "Allows the user to use embedded activities",
}, },
{ {
name: "MODERATE_MEMBERS", name: "MODERATE_MEMBERS",
readableName: "Timeout members", readableName: "Timeout members",
description: 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", "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", name: "VIEW_CREATOR_MONETIZATION_ANALYTICS",
readableName: "View creator monetization analytics", readableName: "View creator monetization analytics",
description: "Allows for viewing role subscription insights", description: "Allows for viewing role subscription insights",
}, },
{ {
name: "USE_SOUNDBOARD", name: "USE_SOUNDBOARD",
readableName: "Use soundboard", readableName: "Use soundboard",
description: "Allows for using soundboard in a voice channel", description: "Allows for using soundboard in a voice channel",
}, },
{ {
name: "CREATE_GUILD_EXPRESSIONS", name: "CREATE_GUILD_EXPRESSIONS",
readableName: "Create expressions", readableName: "Create expressions",
description: description:
"Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.",
}, },
{ {
name: "CREATE_EVENTS", name: "CREATE_EVENTS",
readableName: "Create events", readableName: "Create events",
description: description:
"Allows for creating scheduled events, and editing and deleting those created by the current user.", "Allows for creating scheduled events, and editing and deleting those created by the current user.",
}, },
{ {
name: "USE_EXTERNAL_SOUNDS", name: "USE_EXTERNAL_SOUNDS",
readableName: "Use external sounds", readableName: "Use external sounds",
description: description:
"Allows the usage of custom soundboard sounds from other servers", "Allows the usage of custom soundboard sounds from other servers",
}, },
{ {
name: "SEND_VOICE_MESSAGES", name: "SEND_VOICE_MESSAGES",
readableName: "Send voice messages", readableName: "Send voice messages",
description: "Allows sending voice messages", description: "Allows sending voice messages",
}, },
{ {
name: "SEND_POLLS", name: "SEND_POLLS",
readableName: "Create polls", readableName: "Create polls",
description: "Allows sending polls", description: "Allows sending polls",
}, },
{ {
name: "USE_EXTERNAL_APPS", name: "USE_EXTERNAL_APPS",
readableName: "Use external apps", readableName: "Use external apps",
description: description:
"Allows user-installed apps to send public responses. " + "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. " + "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.", "This only applies to apps not also installed to the server.",
}, },
]; ];
Permissions.map = {}; Permissions.map = {};
let i = 0; let i = 0;
for (const thing of Permissions.info) { for(const thing of Permissions.info){
Permissions.map[i] = thing; Permissions.map[i] = thing;
Permissions.map[thing.name] = i; Permissions.map[thing.name] = i;
i++; i++;
} }
} }
getPermission(name: string): number { getPermission(name: string): number{
if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) { if(this.getPermissionbit(Permissions.map[name] as number, this.allow)){
return 1; return 1;
} else if ( }else if(
this.getPermissionbit(Permissions.map[name] as number, this.deny) this.getPermissionbit(Permissions.map[name] as number, this.deny)
) { ){
return -1; return-1;
} else { }else{
return 0; return 0;
} }
} }
hasPermission(name: string): boolean { hasPermission(name: string): boolean{
if (this.deny) { if(this.deny){
console.warn( console.warn(
"This function may of been used in error, think about using getPermision instead" "This function may of been used in error, think about using getPermision instead"
); );
} }
if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) if(this.getPermissionbit(Permissions.map[name] as number, this.allow))
return true; return true;
if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR"); if(name != "ADMINISTRATOR")return this.hasPermission("ADMINISTRATOR");
return false; return false;
} }
setPermission(name: string, setto: number): void { setPermission(name: string, setto: number): void{
const bit = Permissions.map[name] as number; const bit = Permissions.map[name] as number;
if (!bit) { if(!bit){
return console.error( return console.error(
"Tried to set permission to " + "Tried to set permission to " +
setto + setto +
" for " + " for " +
name + name +
" but it doesn't exist" " but it doesn't exist"
); );
} }
if (setto === 0) { if(setto === 0){
this.deny = this.setPermissionbit(bit, false, this.deny); this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow); this.allow = this.setPermissionbit(bit, false, this.allow);
} else if (setto === 1) { }else if(setto === 1){
this.deny = this.setPermissionbit(bit, false, this.deny); this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, true, this.allow); this.allow = this.setPermissionbit(bit, true, this.allow);
} else if (setto === -1) { }else if(setto === -1){
this.deny = this.setPermissionbit(bit, true, this.deny); this.deny = this.setPermissionbit(bit, true, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow); this.allow = this.setPermissionbit(bit, false, this.allow);
} else { }else{
console.error("invalid number entered:" + setto); console.error("invalid number entered:" + setto);
} }
} }
} }
Permissions.makeMap(); 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"); const registerElement = document.getElementById("register");
if (registerElement) { if(registerElement){
registerElement.addEventListener("submit", registertry); registerElement.addEventListener("submit", registertry);
} }
async function registertry(e: Event) { async function registertry(e: Event){
e.preventDefault(); e.preventDefault();
const elements = (e.target as HTMLFormElement) const elements = (e.target as HTMLFormElement)
.elements as HTMLFormControlsCollection; .elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value; const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value; const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value; const password = (elements[3] as HTMLInputElement).value;
const confirmPassword = (elements[4] as HTMLInputElement).value; const confirmPassword = (elements[4] as HTMLInputElement).value;
const dateofbirth = (elements[5] as HTMLInputElement).value; const dateofbirth = (elements[5] as HTMLInputElement).value;
const consent = (elements[6] as HTMLInputElement).checked; const consent = (elements[6] as HTMLInputElement).checked;
const captchaKey = (elements[7] as HTMLInputElement)?.value; const captchaKey = (elements[7] as HTMLInputElement)?.value;
if (password !== confirmPassword) { if(password !== confirmPassword){
(document.getElementById("wrong") as HTMLElement).textContent = (document.getElementById("wrong") as HTMLElement).textContent =
"Passwords don't match"; "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") ?? "{}"); function handleErrors(errors: any, elements: HTMLFormControlsCollection){
const apiurl = new URL(instanceInfo.api); if(errors.consent){
showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
try { }else if(errors.password){
const response = await fetch(apiurl + "/auth/register", { showError(
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(
elements[3] as HTMLElement, elements[3] as HTMLElement,
"Password: " + errors.password._errors[0].message "Password: " + errors.password._errors[0].message
); );
} else if (errors.username) { }else if(errors.username){
showError( showError(
elements[2] as HTMLElement, elements[2] as HTMLElement,
"Username: " + errors.username._errors[0].message "Username: " + errors.username._errors[0].message
); );
} else if (errors.email) { }else if(errors.email){
showError( showError(
elements[1] as HTMLElement, elements[1] as HTMLElement,
"Email: " + errors.email._errors[0].message "Email: " + errors.email._errors[0].message
); );
} else if (errors.date_of_birth) { }else if(errors.date_of_birth){
showError( showError(
elements[5] as HTMLElement, elements[5] as HTMLElement,
"Date of Birth: " + errors.date_of_birth._errors[0].message "Date of Birth: " + errors.date_of_birth._errors[0].message
); );
} else { }else{
(document.getElementById("wrong") as HTMLElement).textContent = (document.getElementById("wrong") as HTMLElement).textContent =
errors[Object.keys(errors)[0]]._errors[0].message; errors[Object.keys(errors)[0]]._errors[0].message;
} }
} }
function showError(element: HTMLElement, message: string) { function showError(element: HTMLElement, message: string){
const parent = element.parentElement!; const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName( let errorElement = parent.getElementsByClassName(
"suberror" "suberror"
)[0] as HTMLElement; )[0] as HTMLElement;
if (!errorElement) { if(!errorElement){
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("suberror", "suberrora"); div.classList.add("suberror", "suberrora");
parent.append(div); parent.append(div);
errorElement = div; errorElement = div;
} else { }else{
errorElement.classList.remove("suberror"); errorElement.classList.remove("suberror");
setTimeout(() => { setTimeout(()=>{
errorElement.classList.add("suberror"); errorElement.classList.add("suberror");
}, 100); }, 100);
} }
errorElement.textContent = message; errorElement.textContent = message;
} }
let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null; let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null;
async function tosLogic() { async function tosLogic(){
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api); const apiurl = new URL(instanceInfo.api);
const response = await fetch(apiurl.toString() + "/ping"); const response = await fetch(apiurl.toString() + "/ping");
const data = await response.json(); const data = await response.json();
const tosPage = data.instance.tosPage; const tosPage = data.instance.tosPage;
if (tosPage) { if(tosPage){
document.getElementById("TOSbox")!.innerHTML = 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 = document.getElementById("TOSa") as HTMLAnchorElement;
TOSa.href = tosPage; TOSa.href = tosPage;
} else { }else{
document.getElementById("TOSbox")!.textContent = document.getElementById("TOSbox")!.textContent =
"This instance has no Terms of Service, accept ToS anyways:"; "This instance has no Terms of Service, accept ToS anyways:";
TOSa = null; TOSa = null;
} }
console.log(tosPage); console.log(tosPage);
} }
tosLogic(); tosLogic();
(checkInstance as any)["alt"] = tosLogic; (checkInstance as any).alt = tosLogic;

View file

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

View file

@ -1,96 +1,96 @@
function deleteoldcache() { function deleteoldcache(){
caches.delete("cache"); caches.delete("cache");
console.log("this ran :P"); console.log("this ran :P");
} }
async function putInCache(request: URL | RequestInfo, response: Response) { async function putInCache(request: URL | RequestInfo, response: Response){
console.log(request, response); console.log(request, response);
const cache = await caches.open("cache"); const cache = await caches.open("cache");
console.log("Grabbed"); console.log("Grabbed");
try { try{
console.log(await cache.put(request, response)); console.log(await cache.put(request, response));
} catch (error) { }catch(error){
console.error(error); console.error(error);
} }
} }
console.log("test"); console.log("test");
let lastcache: string; let lastcache: string;
self.addEventListener("activate", async () => { self.addEventListener("activate", async ()=>{
console.log("test2"); console.log("test2");
checkCache(); 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);
}); });
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; var checkedrecently = false;
function samedomain(url: string | URL) { function samedomain(url: string | URL){
return new URL(url).origin === self.origin; return new URL(url).origin === self.origin;
} }
function isindexhtml(url: string | URL) { function isindexhtml(url: string | URL){
console.log(url); console.log(url);
if (new URL(url).pathname.startsWith("/channels")) { if(new URL(url).pathname.startsWith("/channels")){
return true; return true;
} }
return false; return false;
} }
async function getfile(event: { async function getfile(event: {
request: { url: URL | RequestInfo; clone: () => string | URL | Request }; request: { url: URL | RequestInfo; clone: () => string | URL | Request };
}) { }){
checkCache(); checkCache();
if (!samedomain(event.request.url.toString())) { if(!samedomain(event.request.url.toString())){
return await fetch(event.request.clone()); return await fetch(event.request.clone());
} }
const responseFromCache = await caches.match(event.request.url); const responseFromCache = await caches.match(event.request.url);
console.log(responseFromCache, caches); console.log(responseFromCache, caches);
if (responseFromCache) { if(responseFromCache){
console.log("cache hit"); console.log("cache hit");
return responseFromCache; return responseFromCache;
} }
if (isindexhtml(event.request.url.toString())) { if(isindexhtml(event.request.url.toString())){
console.log("is index.html"); console.log("is index.html");
const responseFromCache = await caches.match("/index.html"); const responseFromCache = await caches.match("/index.html");
if (responseFromCache) { if(responseFromCache){
console.log("cache hit"); console.log("cache hit");
return responseFromCache; return responseFromCache;
} }
const responseFromNetwork = await fetch("/index.html"); const responseFromNetwork = await fetch("/index.html");
await putInCache("/index.html", responseFromNetwork.clone()); await putInCache("/index.html", responseFromNetwork.clone());
return responseFromNetwork; return responseFromNetwork;
} }
const responseFromNetwork = await fetch(event.request.clone()); const responseFromNetwork = await fetch(event.request.clone());
console.log(event.request.clone()); console.log(event.request.clone());
await putInCache(event.request.clone(), responseFromNetwork.clone()); await putInCache(event.request.clone(), responseFromNetwork.clone());
try { try{
return responseFromNetwork; return responseFromNetwork;
} catch (e) { }catch(e){
console.error(e); console.error(e);
return e; return e;
} }
}
self.addEventListener("fetch", (event: any) => {
try {
event.respondWith(getfile(event));
} catch (e) {
console.error(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 { abstract class SnowFlake{
public readonly id: string; public readonly id: string;
constructor(id: string) { constructor(id: string){
this.id = id; this.id = id;
}
getUnixTime(): number{
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 { export{ SnowFlake };
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 };

View file

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