From beeee247994d85fe9188b478bc5a9d3b48f66a8a Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Fri, 30 Aug 2024 08:22:44 -0500 Subject: [PATCH 1/8] algorithm fixes for uptime --- stats.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/stats.js b/stats.js index 7c82cc9..df58f21 100644 --- a/stats.js +++ b/stats.js @@ -16,7 +16,7 @@ if(uptimeObject["undefined"]){ async function observe(instances){ const active=new Set(); async function resolveinstance(instance){ - calcStats(instance); + try{calcStats(instance)}catch(e){console.error(e)} let api; if(instance.urls){ api=instance.urls.api; @@ -36,7 +36,7 @@ async function observe(instances){ active.add(instance.name); api+=api.endsWith("/")?"":"/" function check(){ - fetch(api+"ping").then(_=>{ + fetch(api+"ping",{method:"HEAD"}).then(_=>{ setStatus(instance,_.ok); }) } @@ -67,17 +67,22 @@ function calcStats(instance){ const week=Date.now()-1000*60*60*24*7; let alltime=-1; let totalTimePassed=0; - let laststamp=0; let daytime=-1; let weektime=-1; let online=false; + let i=0; for(const thing of obj){ - const stamp=thing.time; + online=obj[i].online; + const stamp=obj[i].time; if(alltime===-1){ - laststamp=stamp; alltime=0; } - const timepassed=stamp-laststamp; + let timepassed; + if(obj[i+1]){ + timepassed=obj[i+1].time-stamp; + }else{ + timepassed=Date.now()-stamp; + } totalTimePassed+=timepassed; alltime+=online*timepassed; if(stamp>week){ @@ -94,18 +99,14 @@ function calcStats(instance){ } } } - online=thing.online; + + i++; } instance.online=online; - const timepassed=Date.now()-laststamp; - totalTimePassed+=timepassed; - daytime+=online*Math.min(timepassed,1000*60*60*24); - weektime+=online*Math.min(timepassed,1000*60*60*24*7); - alltime+=online*timepassed; alltime/=totalTimePassed; - if(timepassed>1000*60*60*24){ + if(totalTimePassed>1000*60*60*24){ daytime/=1000*60*60*24; - if(timepassed>1000*60*60*24*7){ + if(totalTimePassed>1000*60*60*24*7){ weektime/=1000*60*60*24*7; }else{ weektime=alltime; From a8724da386c1514aa55c0ba7970250b59a6a1474 Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:27:16 +0200 Subject: [PATCH 2/8] Make application creation a sub form --- webpage/localuser.ts | 2976 +++++++++++++++++++++--------------------- 1 file changed, 1494 insertions(+), 1482 deletions(-) diff --git a/webpage/localuser.ts b/webpage/localuser.ts index d004fed..852ad75 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -1,1492 +1,1504 @@ -import {Guild} from "./guild.js"; -import {Channel} from "./channel.js"; -import {Direct} from "./direct.js"; -import {Voice} from "./audio.js"; -import {User} from "./user.js"; -import {Dialog} from "./dialog.js"; -import {getapiurls, getBulkInfo, setTheme, Specialuser} from "./login.js"; -import { SnowFlake } from "./snowflake.js"; -import { Message } from "./message.js"; -import { channeljson, memberjson, presencejson, readyjson } from "./jsontypes.js"; -import { Member } from "./member.js"; -import { FormError, Settings } from "./settings.js"; -import { MarkDown } from "./markdown.js"; +import{Guild}from"./guild.js"; +import{Channel}from"./channel.js"; +import{Direct}from"./direct.js"; +import{Voice}from"./audio.js"; +import{User}from"./user.js"; +import{Dialog}from"./dialog.js"; +import{getapiurls, getBulkInfo, setTheme, Specialuser}from"./login.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ Message }from"./message.js"; +import{ channeljson, memberjson, presencejson, readyjson }from"./jsontypes.js"; +import{ Member }from"./member.js"; +import{ FormError, Settings }from"./settings.js"; +import{ MarkDown }from"./markdown.js"; const wsCodesRetry=new Set([4000,4003,4005,4007,4008,4009]); class Localuser{ - badges:Map=new Map(); - lastSequence:number|null=null; - token:string; - userinfo:Specialuser; - serverurls:Specialuser["serverurls"]; - initialized:boolean; - info:Specialuser["serverurls"]; - headers:{"Content-type":string,Authorization:string}; - userConnections:Dialog; - devPortal:Dialog; - ready:readyjson; - guilds:Guild[]; - guildids:Map; - user:User; - status:string; - channelfocus:Channel|null; - lookingguild:Guild|null; - guildhtml:Map; - ws:WebSocket|undefined; - typing:Map=new Map(); - connectionSucceed=0; - errorBackoff=0; - readonly userMap=new Map(); - instancePing={ - name:"Unknown", - }; - mfa_enabled:boolean; - constructor(userinfo:Specialuser|-1){ - if(userinfo===-1){ - return; - } - this.token=userinfo.token; - this.userinfo=userinfo; - this.serverurls=this.userinfo.serverurls; - this.initialized=false; - this.info=this.serverurls; - this.headers={"Content-type": "application/json; charset=UTF-8",Authorization:this.userinfo.token}; - } - gottenReady(ready:readyjson):void{ - this.initialized=true; - this.ready=ready; - this.guilds=[]; - this.guildids=new Map(); - this.user=new User(ready.d.user,this); - this.user.setstatus("online"); - this.mfa_enabled=ready.d.user.mfa_enabled as boolean; - this.userinfo.username=this.user.username; - this.userinfo.pfpsrc=this.user.getpfpsrc(); - this.status=this.ready.d.user_settings.status; - this.channelfocus=null; - this.lookingguild=null; - this.guildhtml=new Map(); - const members={}; - for(const thing of ready.d.merged_members){ - members[thing[0].guild_id]=thing[0]; - } - - for(const thing of ready.d.guilds){ - const temp=new Guild(thing,this,members[thing.id]); - this.guilds.push(temp); - this.guildids.set(temp.id,temp); - } - { - const temp=new Direct(ready.d.private_channels,this); - this.guilds.push(temp); - this.guildids.set(temp.id,temp); - } - console.log(ready.d.user_guild_settings.entries); - - - for(const thing of ready.d.user_guild_settings.entries){ - (this.guildids.get(thing.guild_id) as Guild).notisetting(thing); - } - - for(const thing of ready.d.read_state.entries){ - const channel=this.resolveChannelFromID(thing.id); - if(!channel){continue;} - const guild=channel.guild; - if(guild===undefined){ - continue - } - const guildid=guild.snowflake; - (this.guildids.get(guildid.id) as Guild).channelids[thing.channel_id].readStateInfo(thing); - } - for(const thing of ready.d.relationships){ - const user=new User(thing.user,this); - user.nickname=thing.nickname; - user.relationshipType=thing.type; - } - - this.pingEndpoint() - } - outoffocus():void{ - const servers=document.getElementById("servers") as HTMLDivElement; - servers.innerHTML=""; - const channels=document.getElementById("channels") as HTMLDivElement; - channels.innerHTML=""; - if(this.channelfocus){ - this.channelfocus.infinite.delete(); - } - this.lookingguild=null; - this.channelfocus=null; - } - unload():void{ - this.initialized=false; - this.outoffocus(); - this.guilds=[]; - this.guildids=new Map(); - if(this.ws){ - this.ws.close(4001) - } - SnowFlake.clear(); - } - swapped=false; - async initwebsocket():Promise{ - let returny:()=>void; - const ws= new WebSocket(this.serverurls.gateway.toString()+"?encoding=json&v=9"+(DecompressionStream?"&compress=zlib-stream":""));; - this.ws=ws; - let ds:DecompressionStream; - let w:WritableStreamDefaultWriter; - let r:ReadableStreamDefaultReader; - let arr:Uint8Array; - let build=""; - if(DecompressionStream){ - ds = new DecompressionStream("deflate"); - w= ds.writable.getWriter(); - r=ds.readable.getReader(); - arr=new Uint8Array(); - - } - const promise=new Promise((res)=>{ - returny=res - ws.addEventListener('open', (_event) => { - console.log('WebSocket connected'); - ws.send(JSON.stringify({ - "op": 2, - "d": { - "token":this.token, - "capabilities": 16381, - "properties": { - "browser": "Jank Client", - "client_build_number": 0,//might update this eventually lol - "release_channel": "Custom", - "browser_user_agent": navigator.userAgent - }, - "compress": !!DecompressionStream, - "presence": { - "status": "online", - "since": null,//new Date().getTime() - "activities": [], - "afk": false - } - } - })) - }); - const textdecode=new TextDecoder(); - if(DecompressionStream){ - (async ()=>{ - while(true){ - const read=await r.read(); - const data=textdecode.decode(read.value); - build+=data; - try{ - const temp=JSON.parse(build); - build=""; - if(temp.op===0&&temp.t==="READY"){ - returny(); - } - await this.handleEvent(temp); - }catch{} - } - })(); - } - }); - - let order=new Promise((res)=>(res())); - - ws.addEventListener('message', async (event) => { - const temp2=order; - order=new Promise(async (res)=>{ - await temp2; - let temp:{op:number,t:string}; - try{ - if(event.data instanceof Blob){ - const buff=await event.data.arrayBuffer() - const array=new Uint8Array(buff); - - const temparr=new Uint8Array(array.length+arr.length); - temparr.set(arr, 0); - temparr.set(array, arr.length); - arr=temparr; - - const len=array.length; - if(!(array[len-1]===255&&array[len-2]===255&&array[len-3]===0&&array[len-4]===0)){ - return; - } - w.write(arr.buffer); - arr=new Uint8Array(); - return;//had to move the while loop due to me being dumb - }else{ - temp=JSON.parse(event.data); - } - if(temp.op===0&&temp.t==="READY"){ - returny(); - } - await this.handleEvent(temp); - }catch(e){ - console.error(e); - }finally{ - res(); - } - }) - }); - - ws.addEventListener("close",async event => { - this.ws=undefined; - console.log("WebSocket closed with code " + event.code); - - this.unload(); - (document.getElementById("loading") as HTMLElement).classList.remove("doneloading"); - (document.getElementById("loading") as HTMLElement).classList.add("loading"); - this.fetchingmembers=new Map(); - this.noncemap=new Map(); - this.noncebuild=new Map(); - if (((event.code>1000 && event.code<1016) || wsCodesRetry.has(event.code))) { - if (this.connectionSucceed!==0 && Date.now()>this.connectionSucceed+20000) this.errorBackoff=0; - else this.errorBackoff++; - this.connectionSucceed=0; - - (document.getElementById("load-desc") as HTMLElement).innerHTML="Unable to connect to the Spacebar server, retrying in " + Math.round(0.2 + (this.errorBackoff*2.8)) + " seconds..."; - switch(this.errorBackoff){//try to recover from bad domain - case 3: - const newurls=await getapiurls(this.info.wellknown); - if(newurls){ - this.info=newurls; - this.serverurls=newurls; - this.userinfo.json.serverurls=this.info; - this.userinfo.updateLocal(); - break - } - - case 4: - { - const newurls=await getapiurls(new URL(this.info.wellknown).origin); - if(newurls){ - this.info=newurls; - this.serverurls=newurls; - this.userinfo.json.serverurls=this.info; - this.userinfo.updateLocal(); - break - } - - } - case 5: - { - const breakappart=new URL(this.info.wellknown).origin.split("."); - const url="https://"+breakappart[breakappart.length-2]+"."+breakappart[breakappart.length-1] - const newurls=await getapiurls(url); - if(newurls){ - this.info=newurls; - this.serverurls=newurls; - this.userinfo.json.serverurls=this.info; - this.userinfo.updateLocal(); - } - break - } - } - setTimeout(() => { - if(this.swapped) return; - (document.getElementById("load-desc") as HTMLElement).textContent="Retrying..."; - this.initwebsocket().then(() => { - this.loaduser(); - this.init(); - const loading=document.getElementById("loading") as HTMLElement; - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); - }, 200 + (this.errorBackoff*2800)); - } else (document.getElementById("load-desc") as HTMLElement).textContent="Unable to connect to the Spacebar server. Please try logging out and back in."; - }); - - await promise; - return; - } - async handleEvent(temp){ - console.debug(temp); - if (temp.s) this.lastSequence=temp.s; - if(temp.op==0){ - switch(temp.t){ - case "MESSAGE_CREATE": - if(this.initialized){ - this.messageCreate(temp); - } - break; - case "MESSAGE_DELETE": - console.log(temp.d); - SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent(); - break; - case "READY": - this.gottenReady(temp as readyjson); - break; - case "MESSAGE_UPDATE": - const message=SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject(); - message.giveData(temp.d); - break; - case "TYPING_START": - if(this.initialized){ - this.typingStart(temp); - } - break; - case "USER_UPDATE": - if(this.initialized){ - const users=this.userMap.get(temp.d.id); - if(users){ - users.userupdate(temp.d); - } - } - break - case "CHANNEL_UPDATE": - if(this.initialized){ - this.updateChannel(temp.d); - } - break; - case "CHANNEL_CREATE": - if(this.initialized){ - this.createChannel(temp.d); - } - break; - case "CHANNEL_DELETE": - if(this.initialized){ - this.delChannel(temp.d); - } - break; - case "GUILD_DELETE": - { - const guildy=this.guildids.get(temp.d.id); - if(guildy){ - this.guildids.delete(temp.d.id); - this.guilds.splice(this.guilds.indexOf(guildy),1); - guildy.html.remove(); - } - break; - } - case "GUILD_CREATE": - { - const guildy=new Guild(temp.d,this,this.user); - this.guilds.push(guildy); - this.guildids.set(guildy.id,guildy); - (document.getElementById("servers") as HTMLDivElement).insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseparator")); - break; - } - case "MESSAGE_REACTION_ADD": - if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id,Message)){ - temp.d.guild_id??="@me"; - const message=SnowFlake.getSnowFlakeFromID(temp.d.message_id,Message).getObject(); - const guild=SnowFlake.getSnowFlakeFromID(temp.d.guild_id,Guild).getObject(); - let thing:Member|{id:string}; - if(temp.d.member){ - thing=await Member.new(temp.d.member,guild) as Member; - }else{ - thing={id:temp.d.user_id} - } - message.reactionAdd(temp.d.emoji,thing); - } - break; - case "MESSAGE_REACTION_REMOVE": - if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id,Message)){ - - const message=SnowFlake.getSnowFlakeFromID(temp.d.message_id,Message).getObject(); - console.log("test"); - message.reactionRemove(temp.d.emoji,temp.d.user_id); - } - break; - case "MESSAGE_REACTION_REMOVE_ALL": - if (SnowFlake.hasSnowFlakeFromID(temp.d.message_id, Message)) { - const messageReactionRemoveAll = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject() - messageReactionRemoveAll.reactionRemoveAll() - } - break - case "MESSAGE_REACTION_REMOVE_EMOJI": - if (SnowFlake.hasSnowFlakeFromID(temp.d.message_id, Message)) { - const messageReactionRemoveEmoji = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject() - messageReactionRemoveEmoji.reactionRemoveEmoji(temp.d.emoji) - } - break - case "GUILD_MEMBERS_CHUNK": - this.gotChunk(temp.d); - break; - } - - }else if(temp.op===10){ - if(!this.ws) return; - console.log("heartbeat down"); - this.heartbeat_interval=temp.d.heartbeat_interval; - this.ws.send(JSON.stringify({op:1,d:this.lastSequence})) - }else if(temp.op===11){ - setTimeout(_=>{ - if(!this.ws) return; - if (this.connectionSucceed===0) this.connectionSucceed=Date.now() - this.ws.send(JSON.stringify({op:1,d:this.lastSequence})) - },this.heartbeat_interval) - } - } - heartbeat_interval:number; - resolveChannelFromID(ID:string):Channel|undefined{ - let resolve=this.guilds.find(guild => guild.channelids[ID]); - if(resolve){ - return resolve.channelids[ID]; - } - return undefined; - } - updateChannel(json:channeljson):void{ - SnowFlake.getSnowFlakeFromID(json.guild_id,Guild).getObject().updateChannel(json); - if(json.guild_id===this.lookingguild?.id){ - this.loadGuild(json.guild_id); - } - } - createChannel(json:channeljson):void{ - json.guild_id??="@me"; - SnowFlake.getSnowFlakeFromID(json.guild_id,Guild).getObject().createChannelpac(json); - if(json.guild_id===this.lookingguild?.id){ - this.loadGuild(json.guild_id); - } - } - delChannel(json:channeljson):void{ - let guild_id=json.guild_id; - guild_id??="@me"; - const guild=this.guildids.get(guild_id); - if(guild){ - guild.delChannel(json); - } - - if(json.guild_id===this.lookingguild?.id){ - this.loadGuild(json.guild_id); - } - } - init():void{ - const location=window.location.href.split("/"); - this.buildservers(); - if(location[3]==="channels"){ - const guild=this.loadGuild(location[4]); - if(!guild){return;} - guild.loadChannel(location[5]); - this.channelfocus=guild.channelids[location[5]]; - } - - } - loaduser():void{ - (document.getElementById("username") as HTMLSpanElement).textContent=this.user.username; - (document.getElementById("userpfp") as HTMLImageElement).src=this.user.getpfpsrc(); - (document.getElementById("status") as HTMLSpanElement).textContent=this.status; - } - isAdmin():boolean{ - if(this.lookingguild){ - return this.lookingguild.isAdmin(); - }else{ - return false; - } - } - loadGuild(id:string):Guild|undefined{ - let guild=this.guildids.get(id); - if(!guild){ - guild=this.guildids.get("@me"); - } - if(this.lookingguild){ - this.lookingguild.html.classList.remove("serveropen"); - } - - if(!guild) return; - if(guild.html){ - guild.html.classList.add("serveropen") - } - this.lookingguild=guild; - (document.getElementById("serverName") as HTMLElement).textContent=guild.properties.name; - //console.log(this.guildids,id) - const channels=document.getElementById("channels") as HTMLDivElement; - channels.innerHTML=""; - const html=guild.getHTML(); - channels.appendChild(html); - console.log("found :3",html) - return guild; - } - buildservers():void{ - const serverlist=document.getElementById("servers") as HTMLDivElement;// - const outdiv=document.createElement("div"); - const img=document.createElement("img"); - const div=document.createElement("div"); - div.classList.add("home","servericon"); - - img.src="/icons/home.svg"; - img.classList.add("svgtheme","svgicon") - img["all"]=this.guildids.get("@me"); - (this.guildids.get("@me") as Guild).html=outdiv; - const unread=document.createElement("div"); - unread.classList.add("unread"); - outdiv.append(unread); - outdiv.append(div); - div.appendChild(img); - - outdiv.classList.add("servernoti") - serverlist.append(outdiv); - img.onclick=function(){ - this["all"].loadGuild(); - this["all"].loadChannel(); - } - const sentdms=document.createElement("div"); - sentdms.classList.add("sentdms"); - serverlist.append(sentdms); - sentdms.id="sentdms"; - - const br=document.createElement("hr") - br.classList.add("lightbr"); - serverlist.appendChild(br) - for(const thing of this.guilds){ - if(thing instanceof Direct){ - (thing as Direct).unreaddms(); - continue; - } - const divy=thing.generateGuildIcon(); - serverlist.append(divy); - } - { - const br=document.createElement("hr"); - br.classList.add("lightbr"); - serverlist.appendChild(br); - br.id="bottomseparator"; - - const div=document.createElement("div"); - div.textContent="+"; - div.classList.add("home","servericon") - serverlist.appendChild(div) - div.onclick=_=>{ - this.createGuild(); - } - const guilddsdiv=document.createElement("div"); - const guildDiscoveryContainer=document.createElement("img"); - guildDiscoveryContainer.src="/icons/explore.svg"; - guildDiscoveryContainer.classList.add("svgtheme","svgicon"); - guilddsdiv.classList.add("home","servericon"); - guilddsdiv.appendChild(guildDiscoveryContainer); - serverlist.appendChild(guilddsdiv); - guildDiscoveryContainer.addEventListener("click", ()=>{ - this.guildDiscovery(); - }); - - } - this.unreads(); - } - createGuild(){ - let inviteurl=""; - const error=document.createElement("span"); - const fields:{name:string,icon:string|null}={ - name:"", - icon:null, - } - const full=new Dialog(["tabs",[ - ["Join using invite",[ - "vdiv", - ["textbox", - "Invite Link/Code", - "", - function(this:HTMLInputElement){ - inviteurl=this.value; - } - ], - ["html",error] - , - ["button", - "", - "Submit", - _=>{ - let parsed=""; - if(inviteurl.includes("/")){ - parsed=inviteurl.split("/")[inviteurl.split("/").length-1] - }else{ - parsed=inviteurl; - } - fetch(this.info.api+"/invites/"+parsed,{ - method:"POST", - headers:this.headers, - }).then(r=>r.json()).then(_=>{ - if(_.message){ - error.textContent=_.message; - } - }) - } - ] - - ]], - ["Create Guild", - ["vdiv", - ["title","Create a guild"], - ["fileupload","Icon:",function(event:InputEvent){ - const target=event.target as HTMLInputElement; - if(!target.files) return; - const reader=new FileReader(); - reader.readAsDataURL(target.files[0]); - reader.onload=() => { - fields.icon=reader.result as string; - } - }], - ["textbox","Name:","",function(event:InputEvent){ - const target=event.target as HTMLInputElement; - fields.name=target.value; - }], - ["button","","submit",()=>{ - this.makeGuild(fields).then(_=>{ - if(_.message){ - alert(_.errors.name._errors[0].message) - }else{ - full.hide(); - } - }) - }] - ]] - ]]) - full.show(); - } - async makeGuild(fields:{name:string,icon:string|null}){ - return await (await fetch(this.info.api+"/guilds",{ - method:"POST", - headers:this.headers, - body:JSON.stringify(fields), - })).json(); - } - async guildDiscovery() { - const content=document.createElement("div"); - content.classList.add("guildy"); - content.textContent="Loading..."; - const full=new Dialog(["html", content]); - full.show(); - - const res=await fetch(this.info.api+"/discoverable-guilds?limit=50", { - headers: this.headers - }); - const json=await res.json(); - - content.innerHTML=""; - const title=document.createElement("h2"); - title.textContent="Guild discovery ("+json.total+" entries)"; - content.appendChild(title); - - const guilds=document.createElement("div"); - guilds.id="discovery-guild-content"; - - json.guilds.forEach((guild)=>{ - const content=document.createElement("div"); - content.classList.add("discovery-guild"); - - if(guild.banner) { - const banner=document.createElement("img"); - banner.classList.add("banner"); - banner.crossOrigin="anonymous"; - banner.src=this.info.cdn+"/icons/"+guild.id+"/"+guild.banner+".png?size=256"; - banner.alt=""; - content.appendChild(banner); - } - - const nameContainer=document.createElement("div"); - nameContainer.classList.add("flex"); - const img=document.createElement("img"); - img.classList.add("icon"); - img.crossOrigin="anonymous"; - img.src=this.info.cdn+(guild.icon ? ("/icons/"+guild.id+"/"+guild.icon+".png?size=48") : "/embed/avatars/3.png"); - img.alt=""; - nameContainer.appendChild(img); - - const name=document.createElement("h3"); - name.textContent=guild.name; - nameContainer.appendChild(name); - content.appendChild(nameContainer); - const desc=document.createElement("p"); - desc.textContent=guild.description; - content.appendChild(desc); - - content.addEventListener("click", async ()=>{ - const joinRes=await fetch(this.info.api+"/guilds/"+guild.id+"/members/@me", { - method: "PUT", - headers: this.headers - }); - if(joinRes.ok) full.hide(); - }) - guilds.appendChild(content); - }) - content.appendChild(guilds); - } - messageCreate(messagep):void{ - messagep.d.guild_id??="@me"; - const guild=this.guildids.get(messagep.d.guild_id); - if(!guild) return; - guild.channelids[messagep.d.channel_id].messageCreate(messagep); - this.unreads(); - } - unreads():void{ - for(const thing of this.guilds){ - if(thing.id==="@me"){continue;} - const html=this.guildhtml.get(thing.id); - thing.unreads(html); - } - } - async typingStart(typing):Promise{ - if(this.channelfocus?.id===typing.d.channel_id){ - - const guild=this.guildids.get(typing.d.guild_id); - if(!guild) return; - const memb=await Member.new(typing.d.member,guild); - if(!memb) return; - if(memb.id===this.user.id){ - console.log("you is typing") - return; - } - console.log("user is typing and you should see it"); - this.typing.set(memb,new Date().getTime()); - setTimeout(this.rendertyping.bind(this),10000); - this.rendertyping(); - } - } - updatepfp(file:Blob):void{ - var reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = ()=>{ - fetch(this.info.api+"/users/@me",{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({ - avatar:reader.result, - }) - }); - }; - - } - updatebanner(file:Blob|null):void{ - if(file){ - var reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = ()=>{ - fetch(this.info.api+"/users/@me",{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({ - banner:reader.result, - }) - }); - }; - }else{ - fetch(this.info.api+"/users/@me",{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({ - banner:null, - }) - }); - } - - - } - updateProfile(json:{bio?:string,pronouns?:string,accent_color?:number}){ - fetch(this.info.api+"/users/@me/profile",{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify(json) - }); - } - rendertyping():void{ - const typingtext=document.getElementById("typing") as HTMLDivElement; - let build=""; - let showing=false; - let i=0; - const curtime=new Date().getTime()-5000; - for(const thing of this.typing.keys()){ - if(this.typing.get(thing) as number>curtime){ - if(i!==0){ - build+=", "; - } - i++; - if(thing.nick){ - build+=thing.nick; - }else{ - build+=thing.user.username; - } - showing=true; - }else{ - this.typing.delete(thing); - } - } - if(i>1){ - build+=" are typing"; - }else{ - build+=" is typing"; - } - if(showing){ - typingtext.classList.remove("hidden"); - const typingtext2=document.getElementById("typingtext") as HTMLDivElement; - typingtext2.textContent=build; - }else{ - typingtext.classList.add("hidden"); - } - } - showusersettings(){ - const settings=new Settings("Settings"); - { - const userOptions=settings.addButton("User Settings",{ltr:true}); - const hypotheticalProfile=document.createElement("div"); - let file:undefined|File|null=undefined; - let newpronouns:string|undefined=undefined; - let newbio:string|undefined=undefined; - let hypouser=this.user.clone(); - let color:string; - async function regen(){ - hypotheticalProfile.textContent=""; - const hypoprofile=await hypouser.buildprofile(-1,-1); - - hypotheticalProfile.appendChild(hypoprofile) - } - regen(); - const settingsLeft=userOptions.addOptions(""); - const settingsRight=userOptions.addOptions(""); - settingsRight.addHTMLArea(hypotheticalProfile); - - const finput=settingsLeft.addFileInput("Upload pfp:",_=>{ - if(file){ - this.updatepfp(file) - } - },{clear:true}); - finput.watchForChange(_=>{ - if(!_) { - file=null; - hypouser.avatar = null; - hypouser.hypotheticalpfp=true; - regen(); - return; - }; - if(_.length){ - file=_[0]; - const blob = URL.createObjectURL(file); - hypouser.avatar = blob; - hypouser.hypotheticalpfp=true; - regen(); - } - }); - let bfile:undefined|File|null=undefined; - const binput=settingsLeft.addFileInput("Upload banner:",_=>{ - if(bfile!==undefined){ - this.updatebanner(bfile) - } - },{clear:true}); - binput.watchForChange(_=>{ - if(!_){ - bfile=null; - hypouser.banner = undefined; - hypouser.hypotheticalbanner=true; - regen(); - return; - } - if(_.length){ - bfile=_[0]; - const blob = URL.createObjectURL(bfile); - hypouser.banner = blob; - hypouser.hypotheticalbanner=true; - regen(); - } - }); - let changed=false; - const pronounbox=settingsLeft.addTextInput("Pronouns",_=>{ - if(newpronouns||newbio||changed){ - this.updateProfile({pronouns:newpronouns,bio:newbio,accent_color:parseInt("0x"+color.substr(1),16)}); - } - },{initText:this.user.pronouns}); - pronounbox.watchForChange(_=>{ - hypouser.pronouns=_; - newpronouns=_; - regen(); - }); - const bioBox=settingsLeft.addMDInput("Bio:",_=>{ - - },{initText:this.user.bio.rawString}); - bioBox.watchForChange(_=>{ - newbio=_; - hypouser.bio=new MarkDown(_,this); - regen(); - }); - - if(this.user.accent_color){ - color="#"+this.user.accent_color.toString(16); - }else{ - color="transparent"; - } - const colorPicker=settingsLeft.addColorInput("Profile color",(_)=>{},{initColor:color}); - colorPicker.watchForChange(_=>{ - console.log() - color=_; - hypouser.accent_color=parseInt("0x"+_.substr(1),16); - changed=true; - regen(); - }) - } - { - const tas=settings.addButton("Themes & sounds"); - { - const themes=["Dark","WHITE","Light"]; - tas.addSelect("Theme:",_=>{ - localStorage.setItem("theme",themes[_]); - setTheme(); - },themes,{defaultIndex:themes.indexOf(localStorage.getItem("theme") as string)}); - } - { - const sounds=Voice.sounds; - tas.addSelect("Notification sound:",_=>{ - Voice.setNotificationSound(sounds[_]); - },sounds,{defaultIndex:sounds.indexOf(Voice.getNotificationSound())}).watchForChange(_=>{ - Voice.noises(sounds[_]); - }) - } - - { - const userinfos=getBulkInfo(); - tas.addColorInput("Accent color:",_=>{ - fixsvgtheme(); - userinfos.accent_color=_; - localStorage.setItem("userinfos",JSON.stringify(userinfos)); - document.documentElement.style.setProperty('--accent-color', userinfos.accent_color); - },{initColor:userinfos.accent_color}) - } - } - { - const security=settings.addButton("Account Settings"); - const genSecurity=()=>{ - security.removeAll(); - if(this.mfa_enabled){ - security.addButtonInput("","Disable 2FA",()=>{ - const form=security.addSubForm("2FA Disable",(_:any)=>{ - if(_.message){ - switch(_.code){ - case 60008: - form.error("code","Invalid code"); - break; - } - }else{ - this.mfa_enabled=false; - security.returnFromSub(); - genSecurity(); - } - },{ - fetchURL:(this.info.api+"/users/@me/mfa/totp/disable"), - headers:this.headers - }); - form.addTextInput("Code:","code",{required:true}); - }) - }else{ - security.addButtonInput("","Enable 2FA",async ()=>{ - let secret="" - for(let i=0;i<18;i++){ - secret+="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[Math.floor(Math.random()*32)]; - } - const form=security.addSubForm("2FA Setup",(_:any)=>{ - if(_.message){ - switch(_.code){ - case 60008: - form.error("code","Invalid code"); - break; - case 400: - form.error("password","Incorrect password"); - break; - } - }else{ - genSecurity(); - this.mfa_enabled=true; - security.returnFromSub(); - } - },{ - fetchURL:(this.info.api+"/users/@me/mfa/totp/enable/"), - headers:this.headers - }); - form.addTitle("Copy this secret into your totp(time-based one time password) app"); - form.addText(`Your secret is: ${secret} and it's 6 digits, with a 30 second token period`); - form.addTextInput("Account Password:","password",{required:true,password:true}); - form.addTextInput("Code:","code",{required:true}); - form.setValue("secret",secret); - }) - } - security.addButtonInput("","Change discriminator",()=>{ - const form=security.addSubForm("Change Discriminator",(_)=>{security.returnFromSub()},{ - fetchURL:(this.info.api+"/users/@me/"), - headers:this.headers, - method:"PATCH" - }); - form.addTextInput("New discriminator:","discriminator") - }); - security.addButtonInput("","Change email",()=>{ - const form=security.addSubForm("Change Email",(_)=>{security.returnFromSub()},{ - fetchURL:(this.info.api+"/users/@me/"), - headers:this.headers, - method:"PATCH" - }); - form.addTextInput("Password:","password",{password:true}); - if(this.mfa_enabled){ - form.addTextInput("Code:","code"); - } - form.addTextInput("New email:","email"); - }); - security.addButtonInput("","Change username",()=>{ - const form=security.addSubForm("Change Username",(_)=>{security.returnFromSub()},{ - fetchURL:(this.info.api+"/users/@me/"), - headers:this.headers, - method:"PATCH" - }); - form.addTextInput("Password:","password",{password:true}); - if(this.mfa_enabled){ - form.addTextInput("Code:","code"); - } - form.addTextInput("New username:","username"); - }); - security.addButtonInput("","Change password",()=>{ - const form=security.addSubForm("Change Password",(_)=>{security.returnFromSub()},{ - fetchURL:(this.info.api+"/users/@me/"), - headers:this.headers, - method:"PATCH" - }); - form.addTextInput("Old password:","password",{password:true}); - if(this.mfa_enabled){ - form.addTextInput("Code:","code"); - } - let in1=""; - let in2=""; - form.addTextInput("New password:","").watchForChange(text=>{ - in1=text; - }); - const copy=form.addTextInput("New password again:","") - copy.watchForChange(text=>{ - in2=text; - }); - form.setValue("new_password",()=>{ - if(in1===in2){ - return in1; - }else{ - throw new FormError(copy,"Passwords don't match"); - } - }) - }); - } - genSecurity(); - } - { - const connections=settings.addButton("Connections"); - const connectionContainer=document.createElement("div"); - connectionContainer.id="connection-container"; - - fetch(this.info.api+"/connections", { - headers: this.headers - }).then(r => r.json()).then(json => { - Object.keys(json).sort(key => json[key].enabled ? -1 : 1).forEach(key => { - const connection=json[key]; - - const container=document.createElement("div"); - container.textContent=key.charAt(0).toUpperCase() + key.slice(1); - - if (connection.enabled) { - container.addEventListener("click", async () => { - const connectionRes=await fetch(this.info.api+"/connections/"+key+"/authorize", { - headers: this.headers - }); - const connectionJSON=await connectionRes.json(); - window.open(connectionJSON.url, "_blank", "noopener noreferrer"); - }) - } else { - container.classList.add("disabled"); - container.title="This connection has been disabled server-side."; - } - - connectionContainer.appendChild(container); - }) - }) - connections.addHTMLArea(connectionContainer); - } - { - const devPortal=settings.addButton("Developer Portal"); - - let appName=""; - devPortal.addTextInput("Name:", value => { - appName=value - }); - devPortal.addButtonInput("", "Create application", async () => { - if (appName.trim().length == 0) { - return alert("Please enter a name for the application."); - } - - const res=await fetch(this.info.api+"/applications", { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - name: appName - }) - }); - const json=await res.json(); - this.manageApplication(json.id); - }) - - const appListContainer=document.createElement("div"); - appListContainer.id="app-list-container"; - fetch(this.info.api+"/applications", { - headers: this.headers - }).then(r => r.json()).then(json => { - json.forEach(application => { - const container=document.createElement("div"); - - if (application.cover_image || application.icon) { - const cover=document.createElement("img"); - cover.crossOrigin="anonymous"; - cover.src=this.info.cdn+"/app-icons/"+application.id+"/"+(application.cover_image || application.icon)+".png?size=256"; - cover.alt=""; - cover.loading="lazy"; - container.appendChild(cover); - } - - const name=document.createElement("h2"); - name.textContent=application.name + (application.bot ? " (Bot)" : ""); - container.appendChild(name); - - container.addEventListener("click", async () => { - this.manageApplication(application.id); - }); - appListContainer.appendChild(container); - }) - }) - devPortal.addHTMLArea(appListContainer); - } - settings.show(); - } - async manageApplication(appId="") { - const res=await fetch(this.info.api+"/applications/" + appId, { - headers: this.headers - }); - const json=await res.json(); - - const fields: any={}; - const appDialog=new Dialog( - ["vdiv", - ["title", - "Editing " + json.name - ], - ["vdiv", - ["textbox", "Application name:", json.name, event => { - fields.name=event.target.value; - }], - ["mdbox", "Description:", json.description, event => { - fields.description=event.target.value; - }], - ["vdiv", - json.icon ? ["img", this.info.cdn+"/app-icons/" + appId + "/" + json.icon + ".png?size=128", [128, 128]] : ["text", "No icon"], - ["fileupload", "Application icon:", event => { - const reader=new FileReader(); - reader.readAsDataURL(event.target.files[0]); - reader.onload=() => { - fields.icon=reader.result; - } - }] - ] - ], - ["hdiv", - ["textbox", "Privacy policy URL:", json.privacy_policy_url || "", event => { - fields.privacy_policy_url=event.target.value; - }], - ["textbox", "Terms of Service URL:", json.terms_of_service_url || "", event => { - fields.terms_of_service_url=event.target.value; - }] - ], - ["hdiv", - ["checkbox", "Make bot publicly inviteable?", json.bot_public, event => { - fields.bot_public=event.target.checked; - }], - ["checkbox", "Require code grant to invite the bot?", json.bot_require_code_grant, event => { - fields.bot_require_code_grant=event.target.checked; - }] - ], - ["hdiv", - ["button", - "", - "Save changes", - async () => { - const updateRes=await fetch(this.info.api+"/applications/" + appId, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields) - }); - if (updateRes.ok) appDialog.hide(); - else { - const updateJSON=await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - } - ], - ["button", - "", - (json.bot ? "Manage" : "Add") + " bot", - async () => { - if (!json.bot) { - if (!confirm("Are you sure you want to add a bot to this application? There's no going back.")) return; - - const updateRes=await fetch(this.info.api+"/applications/" + appId + "/bot", { - method: "POST", - headers: this.headers - }); - const updateJSON=await updateRes.json(); - alert("Bot token:\n" + updateJSON.token); - } - - appDialog.hide(); - this.manageBot(appId); - } - ] - ] - ] - ) - appDialog.show(); - } - async manageBot(appId="") { - const res=await fetch(this.info.api+"/applications/" + appId, { - headers: this.headers - }); - const json=await res.json(); - if (!json.bot) return alert("For some reason, this application doesn't have a bot (yet)."); - - const fields: any={ - username: json.bot.username, - avatar: json.bot.avatar ? (this.info.cdn+"/app-icons/" + appId + "/" + json.bot.avatar + ".png?size=256") : "" - }; - const botDialog=new Dialog( - ["vdiv", - ["title", - "Editing bot: " + json.bot.username - ], - ["hdiv", - ["textbox", "Bot username:", json.bot.username, event => { - fields.username=event.target.value - }], - ["vdiv", - fields.avatar ? ["img", fields.avatar, [128, 128]] : ["text", "No avatar"], - ["fileupload", "Bot avatar:", event => { - const reader=new FileReader(); - reader.readAsDataURL(event.target.files[0]); - reader.onload=() => { - fields.avatar=reader.result; - } - }] - ] - ], - ["hdiv", - ["button", - "", - "Save changes", - async () => { - const updateRes=await fetch(this.info.api+"/applications/" + appId + "/bot", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields) - }); - if (updateRes.ok) botDialog.hide(); - else { - const updateJSON=await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - } - ], - ["button", - "", - "Reset token", - async () => { - if (!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")) return; - - const updateRes=await fetch(this.info.api+"/applications/" + appId + "/bot/reset", { - method: "POST", - headers: this.headers - }); - const updateJSON=await updateRes.json(); - alert("New token:\n" + updateJSON.token); - botDialog.hide(); - } - ] - ] - ] - ); - botDialog.show(); - } - - //---------- resolving members code ----------- - readonly waitingmembers:Mapvoid>>=new Map(); - readonly presences:Map=new Map(); - async resolvemember(id:string,guildid:string):Promise{ - if(guildid==="@me"){return undefined} - let guildmap=this.waitingmembers.get(guildid); - if(!guildmap){ - guildmap=new Map(); - this.waitingmembers.set(guildid,guildmap); - - } - const promise:Promise=new Promise((res)=>{ - guildmap.set(id,res); - this.getmembers(); - }) - return await promise; - } - fetchingmembers:Map=new Map(); - noncemap:Mapvoid>=new Map(); - noncebuild:Map=new Map(); - async gotChunk(chunk:{chunk_index:number,chunk_count:number,nonce:string,not_found?:string[],members?:memberjson[],presences:presencejson[]}){ - for(const thing of chunk.presences){ - if(thing.user){ - this.presences.set(thing.user.id,thing); - } - } - console.log(chunk); - chunk.members??=[]; - const arr=this.noncebuild.get(chunk.nonce); - if(!arr) return; - arr[0]=arr[0].concat(chunk.members); - if(chunk.not_found){ - arr[1]=chunk.not_found; - } - arr[2].push(chunk.chunk_index); - if(arr[2].length===chunk.chunk_count){ - console.log("got through"); - this.noncebuild.delete(chunk.nonce); - const func=this.noncemap.get(chunk.nonce) - if(!func) return; - func([arr[0],arr[1]]); - this.noncemap.delete(chunk.nonce); - } - - } - async getmembers(){ - const promise=new Promise(res=>{setTimeout(res,10)}); - await promise;//allow for more to be sent at once :P - if(this.ws){ - this.waitingmembers.forEach(async (value,guildid)=>{ - const keys=value.keys(); - if(this.fetchingmembers.has(guildid)){ - return; - } - const build:string[]=[]; - for(const key of keys){build.push(key);if(build.length===100){break;}}; - if(!build.length) { - this.waitingmembers.delete(guildid); - return - }; - const promise:Promise<[memberjson[],string[]]>=new Promise((res)=>{ - const nonce=""+Math.floor(Math.random()*100000000000); - this.noncemap.set(nonce,res); - this.noncebuild.set(nonce,[[],[],[]]); - if(!this.ws) return; - this.ws.send(JSON.stringify({ - op:8, - d:{ - user_ids:build, - guild_id:guildid, - limit:100, - nonce, - presences:true - } - })); - this.fetchingmembers.set(guildid,true); - - }) - const prom=await promise;; - const data=prom[0]; - for(const thing of data){ - if(value.has(thing.id)){ - const func=value.get(thing.id); - if(!func) continue; - func(thing); - value.delete(thing.id); - } - } - for(const thing of prom[1]){ - if(value.has(thing)){ - const func=value.get(thing) - if(!func) continue; - func(undefined); - value.delete(thing); - } - } - this.fetchingmembers.delete(guildid); - this.getmembers(); - }) - } - } - async pingEndpoint() { - const userInfo = getBulkInfo(); - if (!userInfo.instances) userInfo.instances = {}; - const wellknown = this.info.wellknown; - if (!userInfo.instances[wellknown]) { - const pingRes = await fetch(this.info.api + "/ping"); - const pingJSON = await pingRes.json(); - userInfo.instances[wellknown] = pingJSON; - localStorage.setItem("userinfos", JSON.stringify(userInfo)); - } - this.instancePing = userInfo.instances[wellknown].instance; - - this.pageTitle("Loading..."); - } - pageTitle(channelName = "", guildName = "") { - (document.getElementById("channelname") as HTMLSpanElement).textContent = channelName; - (document.getElementsByTagName("title")[0] as HTMLTitleElement).textContent = channelName + (guildName ? " | " + guildName : "") + " | " + this.instancePing.name + " | Jank Client (Tomato fork)"; - } - async instanceStats() { - const res = await fetch(this.info.api + "/policies/stats", { - headers: this.headers - }); - const json = await res.json(); - - const dialog = new Dialog(["vdiv", - ["title", "Instance stats: " + this.instancePing.name], - ["text", "Registered users: " + json.counts.user], - ["text", "Servers: " + json.counts.guild], - ["text", "Messages: " + json.counts.message], - ["text", "Members: " + json.counts.members] - ]); - dialog.show(); - } + badges:Map=new Map(); + lastSequence:number|null=null; + token:string; + userinfo:Specialuser; + serverurls:Specialuser["serverurls"]; + initialized:boolean; + info:Specialuser["serverurls"]; + headers:{"Content-type":string,Authorization:string}; + userConnections:Dialog; + devPortal:Dialog; + ready:readyjson; + guilds:Guild[]; + guildids:Map; + user:User; + status:string; + channelfocus:Channel|null; + lookingguild:Guild|null; + guildhtml:Map; + ws:WebSocket|undefined; + typing:Map=new Map(); + connectionSucceed=0; + errorBackoff=0; + readonly userMap=new Map(); + instancePing={ + name: "Unknown", + }; + mfa_enabled:boolean; + constructor(userinfo:Specialuser|-1){ + if(userinfo===-1){ + return; + } + this.token=userinfo.token; + this.userinfo=userinfo; + this.serverurls=this.userinfo.serverurls; + this.initialized=false; + this.info=this.serverurls; + this.headers={"Content-type": "application/json; charset=UTF-8",Authorization: this.userinfo.token}; + } + gottenReady(ready:readyjson):void{ + this.initialized=true; + this.ready=ready; + this.guilds=[]; + this.guildids=new Map(); + this.user=new User(ready.d.user,this); + this.user.setstatus("online"); + this.mfa_enabled=ready.d.user.mfa_enabled as boolean; + this.userinfo.username=this.user.username; + this.userinfo.pfpsrc=this.user.getpfpsrc(); + this.status=this.ready.d.user_settings.status; + this.channelfocus=null; + this.lookingguild=null; + this.guildhtml=new Map(); + const members={}; + for(const thing of ready.d.merged_members){ + members[thing[0].guild_id]=thing[0]; + } + + for(const thing of ready.d.guilds){ + const temp=new Guild(thing,this,members[thing.id]); + this.guilds.push(temp); + this.guildids.set(temp.id,temp); + } + { + const temp=new Direct(ready.d.private_channels,this); + this.guilds.push(temp); + this.guildids.set(temp.id,temp); + } + console.log(ready.d.user_guild_settings.entries); + + + for(const thing of ready.d.user_guild_settings.entries){ + (this.guildids.get(thing.guild_id) as Guild).notisetting(thing); + } + + for(const thing of ready.d.read_state.entries){ + const channel=this.resolveChannelFromID(thing.id); + if(!channel){ + continue; + } + const guild=channel.guild; + if(guild===undefined){ + continue; + } + const guildid=guild.snowflake; + (this.guildids.get(guildid.id) as Guild).channelids[thing.channel_id].readStateInfo(thing); + } + for(const thing of ready.d.relationships){ + const user=new User(thing.user,this); + user.nickname=thing.nickname; + user.relationshipType=thing.type; + } + + this.pingEndpoint(); + } + outoffocus():void{ + const servers=document.getElementById("servers") as HTMLDivElement; + servers.innerHTML=""; + const channels=document.getElementById("channels") as HTMLDivElement; + channels.innerHTML=""; + if(this.channelfocus){ + this.channelfocus.infinite.delete(); + } + this.lookingguild=null; + this.channelfocus=null; + } + unload():void{ + this.initialized=false; + this.outoffocus(); + this.guilds=[]; + this.guildids=new Map(); + if(this.ws){ + this.ws.close(4001); + } + SnowFlake.clear(); + } + swapped=false; + async initwebsocket():Promise{ + let returny:()=>void; + const ws= new WebSocket(this.serverurls.gateway.toString()+"?encoding=json&v=9"+(DecompressionStream?"&compress=zlib-stream":"")); + this.ws=ws; + let ds:DecompressionStream; + let w:WritableStreamDefaultWriter; + let r:ReadableStreamDefaultReader; + let arr:Uint8Array; + let build=""; + if(DecompressionStream){ + ds = new DecompressionStream("deflate"); + w= ds.writable.getWriter(); + r=ds.readable.getReader(); + arr=new Uint8Array(); + } + const promise=new Promise(res=>{ + returny=res; + ws.addEventListener("open", _event=>{ + console.log("WebSocket connected"); + ws.send(JSON.stringify({ + op: 2, + d: { + token: this.token, + capabilities: 16381, + properties: { + browser: "Jank Client", + client_build_number: 0,//might update this eventually lol + release_channel: "Custom", + browser_user_agent: navigator.userAgent + }, + compress: Boolean(DecompressionStream), + presence: { + status: "online", + since: null,//new Date().getTime() + activities: [], + afk: false + } + } + })); + }); + const textdecode=new TextDecoder(); + if(DecompressionStream){ + (async ()=>{ + while(true){ + const read=await r.read(); + const data=textdecode.decode(read.value); + build+=data; + try{ + const temp=JSON.parse(build); + build=""; + if(temp.op===0&&temp.t==="READY"){ + returny(); + } + await this.handleEvent(temp); + }catch{} + } + })(); + } + }); + + let order=new Promise(res=>(res())); + + ws.addEventListener("message", async event=>{ + const temp2=order; + order=new Promise(async res=>{ + await temp2; + let temp:{op:number,t:string}; + try{ + if(event.data instanceof Blob){ + const buff=await event.data.arrayBuffer(); + const array=new Uint8Array(buff); + + const temparr=new Uint8Array(array.length+arr.length); + temparr.set(arr, 0); + temparr.set(array, arr.length); + arr=temparr; + + const len=array.length; + if(!(array[len-1]===255&&array[len-2]===255&&array[len-3]===0&&array[len-4]===0)){ + return; + } + w.write(arr.buffer); + arr=new Uint8Array(); + return;//had to move the while loop due to me being dumb + }else{ + temp=JSON.parse(event.data); + } + if(temp.op===0&&temp.t==="READY"){ + returny(); + } + await this.handleEvent(temp); + }catch(e){ + console.error(e); + }finally{ + res(); + } + }); + }); + + ws.addEventListener("close",async event=>{ + this.ws=undefined; + console.log("WebSocket closed with code " + event.code); + + this.unload(); + (document.getElementById("loading") as HTMLElement).classList.remove("doneloading"); + (document.getElementById("loading") as HTMLElement).classList.add("loading"); + this.fetchingmembers=new Map(); + this.noncemap=new Map(); + this.noncebuild=new Map(); + if(((event.code>1000 && event.code<1016) || wsCodesRetry.has(event.code))){ + if(this.connectionSucceed!==0 && Date.now()>this.connectionSucceed+20000)this.errorBackoff=0; + else this.errorBackoff++; + this.connectionSucceed=0; + + (document.getElementById("load-desc") as HTMLElement).innerHTML="Unable to connect to the Spacebar server, retrying in " + Math.round(0.2 + (this.errorBackoff*2.8)) + " seconds..."; + switch(this.errorBackoff){//try to recover from bad domain + case 3: + const newurls=await getapiurls(this.info.wellknown); + if(newurls){ + this.info=newurls; + this.serverurls=newurls; + this.userinfo.json.serverurls=this.info; + this.userinfo.updateLocal(); + break; + } + + case 4: + { + const newurls=await getapiurls(new URL(this.info.wellknown).origin); + if(newurls){ + this.info=newurls; + this.serverurls=newurls; + this.userinfo.json.serverurls=this.info; + this.userinfo.updateLocal(); + break; + } + } + case 5: + { + const breakappart=new URL(this.info.wellknown).origin.split("."); + const url="https://"+breakappart.at(-2)+"."+breakappart.at(-1); + const newurls=await getapiurls(url); + if(newurls){ + this.info=newurls; + this.serverurls=newurls; + this.userinfo.json.serverurls=this.info; + this.userinfo.updateLocal(); + } + break; + } + } + setTimeout(()=>{ + if(this.swapped)return; + (document.getElementById("load-desc") as HTMLElement).textContent="Retrying..."; + this.initwebsocket().then(()=>{ + this.loaduser(); + this.init(); + const loading=document.getElementById("loading") as HTMLElement; + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + }, 200 + (this.errorBackoff*2800)); + }else(document.getElementById("load-desc") as HTMLElement).textContent="Unable to connect to the Spacebar server. Please try logging out and back in."; + }); + + await promise; + } + async handleEvent(temp){ + console.debug(temp); + if(temp.s)this.lastSequence=temp.s; + if(temp.op==0){ + switch(temp.t){ + case"MESSAGE_CREATE": + if(this.initialized){ + this.messageCreate(temp); + } + break; + case"MESSAGE_DELETE": + console.log(temp.d); + SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent(); + break; + case"READY": + this.gottenReady(temp as readyjson); + break; + case"MESSAGE_UPDATE": + const message=SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject(); + message.giveData(temp.d); + break; + case"TYPING_START": + if(this.initialized){ + this.typingStart(temp); + } + break; + case"USER_UPDATE": + if(this.initialized){ + const users=this.userMap.get(temp.d.id); + if(users){ + users.userupdate(temp.d); + } + } + break; + case"CHANNEL_UPDATE": + if(this.initialized){ + this.updateChannel(temp.d); + } + break; + case"CHANNEL_CREATE": + if(this.initialized){ + this.createChannel(temp.d); + } + break; + case"CHANNEL_DELETE": + if(this.initialized){ + this.delChannel(temp.d); + } + break; + case"GUILD_DELETE": + { + const guildy=this.guildids.get(temp.d.id); + if(guildy){ + this.guildids.delete(temp.d.id); + this.guilds.splice(this.guilds.indexOf(guildy),1); + guildy.html.remove(); + } + break; + } + case"GUILD_CREATE": + { + const guildy=new Guild(temp.d,this,this.user); + this.guilds.push(guildy); + this.guildids.set(guildy.id,guildy); + (document.getElementById("servers") as HTMLDivElement).insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseparator")); + break; + } + case"MESSAGE_REACTION_ADD": + if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id,Message)){ + temp.d.guild_id??="@me"; + const message=SnowFlake.getSnowFlakeFromID(temp.d.message_id,Message).getObject(); + const guild=SnowFlake.getSnowFlakeFromID(temp.d.guild_id,Guild).getObject(); + let thing:Member|{id:string}; + if(temp.d.member){ + thing=await Member.new(temp.d.member,guild) as Member; + }else{ + thing={id: temp.d.user_id}; + } + message.reactionAdd(temp.d.emoji,thing); + } + break; + case"MESSAGE_REACTION_REMOVE": + if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id,Message)){ + const message=SnowFlake.getSnowFlakeFromID(temp.d.message_id,Message).getObject(); + console.log("test"); + message.reactionRemove(temp.d.emoji,temp.d.user_id); + } + break; + case"MESSAGE_REACTION_REMOVE_ALL": + if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id, Message)){ + const messageReactionRemoveAll = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject(); + messageReactionRemoveAll.reactionRemoveAll(); + } + break; + case"MESSAGE_REACTION_REMOVE_EMOJI": + if(SnowFlake.hasSnowFlakeFromID(temp.d.message_id, Message)){ + const messageReactionRemoveEmoji = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject(); + messageReactionRemoveEmoji.reactionRemoveEmoji(temp.d.emoji); + } + break; + case"GUILD_MEMBERS_CHUNK": + this.gotChunk(temp.d); + break; + } + }else if(temp.op===10){ + if(!this.ws)return; + console.log("heartbeat down"); + this.heartbeat_interval=temp.d.heartbeat_interval; + this.ws.send(JSON.stringify({op: 1,d: this.lastSequence})); + }else if(temp.op===11){ + setTimeout(_=>{ + if(!this.ws)return; + if(this.connectionSucceed===0)this.connectionSucceed=Date.now(); + this.ws.send(JSON.stringify({op: 1,d: this.lastSequence})); + },this.heartbeat_interval); + } + } + heartbeat_interval:number; + resolveChannelFromID(ID:string):Channel|undefined{ + const resolve=this.guilds.find(guild=>guild.channelids[ID]); + if(resolve){ + return resolve.channelids[ID]; + } + return undefined; + } + updateChannel(json:channeljson):void{ + SnowFlake.getSnowFlakeFromID(json.guild_id,Guild).getObject().updateChannel(json); + if(json.guild_id===this.lookingguild?.id){ + this.loadGuild(json.guild_id); + } + } + createChannel(json:channeljson):void{ + json.guild_id??="@me"; + SnowFlake.getSnowFlakeFromID(json.guild_id,Guild).getObject().createChannelpac(json); + if(json.guild_id===this.lookingguild?.id){ + this.loadGuild(json.guild_id); + } + } + delChannel(json:channeljson):void{ + let guild_id=json.guild_id; + guild_id??="@me"; + const guild=this.guildids.get(guild_id); + if(guild){ + guild.delChannel(json); + } + + if(json.guild_id===this.lookingguild?.id){ + this.loadGuild(json.guild_id); + } + } + init():void{ + const location=window.location.href.split("/"); + this.buildservers(); + if(location[3]==="channels"){ + const guild=this.loadGuild(location[4]); + if(!guild){ + return; + } + guild.loadChannel(location[5]); + this.channelfocus=guild.channelids[location[5]]; + } + } + loaduser():void{ + (document.getElementById("username") as HTMLSpanElement).textContent=this.user.username; + (document.getElementById("userpfp") as HTMLImageElement).src=this.user.getpfpsrc(); + (document.getElementById("status") as HTMLSpanElement).textContent=this.status; + } + isAdmin():boolean{ + if(this.lookingguild){ + return this.lookingguild.isAdmin(); + }else{ + return false; + } + } + loadGuild(id:string):Guild|undefined{ + let guild=this.guildids.get(id); + if(!guild){ + guild=this.guildids.get("@me"); + } + if(this.lookingguild){ + this.lookingguild.html.classList.remove("serveropen"); + } + + if(!guild)return; + if(guild.html){ + guild.html.classList.add("serveropen"); + } + this.lookingguild=guild; + (document.getElementById("serverName") as HTMLElement).textContent=guild.properties.name; + //console.log(this.guildids,id) + const channels=document.getElementById("channels") as HTMLDivElement; + channels.innerHTML=""; + const html=guild.getHTML(); + channels.appendChild(html); + console.log("found :3",html); + return guild; + } + buildservers():void{ + const serverlist=document.getElementById("servers") as HTMLDivElement;// + const outdiv=document.createElement("div"); + const img=document.createElement("img"); + const div=document.createElement("div"); + div.classList.add("home","servericon"); + + img.src="/icons/home.svg"; + img.classList.add("svgtheme","svgicon"); + img.all=this.guildids.get("@me"); + (this.guildids.get("@me") as Guild).html=outdiv; + const unread=document.createElement("div"); + unread.classList.add("unread"); + outdiv.append(unread); + outdiv.append(div); + div.appendChild(img); + + outdiv.classList.add("servernoti"); + serverlist.append(outdiv); + img.onclick=function(){ + this.all.loadGuild(); + this.all.loadChannel(); + }; + const sentdms=document.createElement("div"); + sentdms.classList.add("sentdms"); + serverlist.append(sentdms); + sentdms.id="sentdms"; + + const br=document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + for(const thing of this.guilds){ + if(thing instanceof Direct){ + (thing as Direct).unreaddms(); + continue; + } + const divy=thing.generateGuildIcon(); + serverlist.append(divy); + } + { + const br=document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + br.id="bottomseparator"; + + const div=document.createElement("div"); + div.textContent="+"; + div.classList.add("home","servericon"); + serverlist.appendChild(div); + div.onclick=_=>{ + this.createGuild(); + }; + const guilddsdiv=document.createElement("div"); + const guildDiscoveryContainer=document.createElement("img"); + guildDiscoveryContainer.src="/icons/explore.svg"; + guildDiscoveryContainer.classList.add("svgtheme","svgicon"); + guilddsdiv.classList.add("home","servericon"); + guilddsdiv.appendChild(guildDiscoveryContainer); + serverlist.appendChild(guilddsdiv); + guildDiscoveryContainer.addEventListener("click", ()=>{ + this.guildDiscovery(); + }); + } + this.unreads(); + } + createGuild(){ + let inviteurl=""; + const error=document.createElement("span"); + const fields:{name:string,icon:string|null}={ + name: "", + icon: null, + }; + const full=new Dialog(["tabs",[ + ["Join using invite",[ + "vdiv", + ["textbox", + "Invite Link/Code", + "", + function(this:HTMLInputElement){ + inviteurl=this.value; + } + ], + ["html",error], + ["button", + "", + "Submit", + _=>{ + let parsed=""; + if(inviteurl.includes("/")){ + parsed=inviteurl.split("/")[inviteurl.split("/").length-1]; + }else{ + parsed=inviteurl; + } + fetch(this.info.api+"/invites/"+parsed,{ + method: "POST", + headers: this.headers, + }).then(r=>r.json()).then(_=>{ + if(_.message){ + error.textContent=_.message; + } + }); + } + ] + + ]], + ["Create Guild", + ["vdiv", + ["title","Create a guild"], + ["fileupload","Icon:",function(event:InputEvent){ + const target=event.target as HTMLInputElement; + if(!target.files)return; + const reader=new FileReader(); + reader.readAsDataURL(target.files[0]); + reader.onload=()=>{ + fields.icon=reader.result as string; + }; + }], + ["textbox","Name:","",function(event:InputEvent){ + const target=event.target as HTMLInputElement; + fields.name=target.value; + }], + ["button","","submit",()=>{ + this.makeGuild(fields).then(_=>{ + if(_.message){ + alert(_.errors.name._errors[0].message); + }else{ + full.hide(); + } + }); + }] + ]] + ]]); + full.show(); + } + async makeGuild(fields:{name:string,icon:string|null}){ + return await (await fetch(this.info.api+"/guilds",{ + method: "POST", + headers: this.headers, + body: JSON.stringify(fields), + })).json(); + } + async guildDiscovery(){ + const content=document.createElement("div"); + content.classList.add("guildy"); + content.textContent="Loading..."; + const full=new Dialog(["html", content]); + full.show(); + + const res=await fetch(this.info.api+"/discoverable-guilds?limit=50", { + headers: this.headers + }); + const json=await res.json(); + + content.innerHTML=""; + const title=document.createElement("h2"); + title.textContent="Guild discovery ("+json.total+" entries)"; + content.appendChild(title); + + const guilds=document.createElement("div"); + guilds.id="discovery-guild-content"; + + json.guilds.forEach(guild=>{ + const content=document.createElement("div"); + content.classList.add("discovery-guild"); + + if(guild.banner){ + const banner=document.createElement("img"); + banner.classList.add("banner"); + banner.crossOrigin="anonymous"; + banner.src=this.info.cdn+"/icons/"+guild.id+"/"+guild.banner+".png?size=256"; + banner.alt=""; + content.appendChild(banner); + } + + const nameContainer=document.createElement("div"); + nameContainer.classList.add("flex"); + const img=document.createElement("img"); + img.classList.add("icon"); + img.crossOrigin="anonymous"; + img.src=this.info.cdn+(guild.icon ? ("/icons/"+guild.id+"/"+guild.icon+".png?size=48") : "/embed/avatars/3.png"); + img.alt=""; + nameContainer.appendChild(img); + + const name=document.createElement("h3"); + name.textContent=guild.name; + nameContainer.appendChild(name); + content.appendChild(nameContainer); + const desc=document.createElement("p"); + desc.textContent=guild.description; + content.appendChild(desc); + + content.addEventListener("click", async ()=>{ + const joinRes=await fetch(this.info.api+"/guilds/"+guild.id+"/members/@me", { + method: "PUT", + headers: this.headers + }); + if(joinRes.ok) full.hide(); + }); + guilds.appendChild(content); + }); + content.appendChild(guilds); + } + messageCreate(messagep):void{ + messagep.d.guild_id??="@me"; + const guild=this.guildids.get(messagep.d.guild_id); + if(!guild)return; + guild.channelids[messagep.d.channel_id].messageCreate(messagep); + this.unreads(); + } + unreads():void{ + for(const thing of this.guilds){ + if(thing.id==="@me"){ + continue; + } + const html=this.guildhtml.get(thing.id); + thing.unreads(html); + } + } + async typingStart(typing):Promise{ + if(this.channelfocus?.id===typing.d.channel_id){ + const guild=this.guildids.get(typing.d.guild_id); + if(!guild)return; + const memb=await Member.new(typing.d.member,guild); + if(!memb)return; + if(memb.id===this.user.id){ + console.log("you is typing"); + return; + } + console.log("user is typing and you should see it"); + this.typing.set(memb,Date.now()); + setTimeout(this.rendertyping.bind(this),10000); + this.rendertyping(); + } + } + updatepfp(file:Blob):void{ + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api+"/users/@me",{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + avatar: reader.result, + }) + }); + }; + } + updatebanner(file:Blob|null):void{ + if(file){ + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = ()=>{ + fetch(this.info.api+"/users/@me",{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: reader.result, + }) + }); + }; + }else{ + fetch(this.info.api+"/users/@me",{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: null, + }) + }); + } + } + updateProfile(json:{bio?:string,pronouns?:string,accent_color?:number}){ + fetch(this.info.api+"/users/@me/profile",{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify(json) + }); + } + rendertyping():void{ + const typingtext=document.getElementById("typing") as HTMLDivElement; + let build=""; + let showing=false; + let i=0; + const curtime=Date.now()-5000; + for(const thing of this.typing.keys()){ + if(this.typing.get(thing) as number>curtime){ + if(i!==0){ + build+=", "; + } + i++; + if(thing.nick){ + build+=thing.nick; + }else{ + build+=thing.user.username; + } + showing=true; + }else{ + this.typing.delete(thing); + } + } + if(i>1){ + build+=" are typing"; + }else{ + build+=" is typing"; + } + if(showing){ + typingtext.classList.remove("hidden"); + const typingtext2=document.getElementById("typingtext") as HTMLDivElement; + typingtext2.textContent=build; + }else{ + typingtext.classList.add("hidden"); + } + } + async showusersettings(){ + const settings=new Settings("Settings"); + { + const userOptions=settings.addButton("User Settings",{ltr: true}); + const hypotheticalProfile=document.createElement("div"); + let file:undefined|File|null; + let newpronouns:string|undefined; + let newbio:string|undefined; + const hypouser=this.user.clone(); + let color:string; + async function regen(){ + hypotheticalProfile.textContent=""; + const hypoprofile=await hypouser.buildprofile(-1,-1); + + hypotheticalProfile.appendChild(hypoprofile); + } + regen(); + const settingsLeft=userOptions.addOptions(""); + const settingsRight=userOptions.addOptions(""); + settingsRight.addHTMLArea(hypotheticalProfile); + + const finput=settingsLeft.addFileInput("Upload pfp:",_=>{ + if(file){ + this.updatepfp(file); + } + },{clear: true}); + finput.watchForChange(_=>{ + if(!_){ + file=null; + hypouser.avatar = null; + hypouser.hypotheticalpfp=true; + regen(); + return; + } + if(_.length){ + file=_[0]; + const blob = URL.createObjectURL(file); + hypouser.avatar = blob; + hypouser.hypotheticalpfp=true; + regen(); + } + }); + let bfile:undefined|File|null; + const binput=settingsLeft.addFileInput("Upload banner:",_=>{ + if(bfile!==undefined){ + this.updatebanner(bfile); + } + },{clear: true}); + binput.watchForChange(_=>{ + if(!_){ + bfile=null; + hypouser.banner = undefined; + hypouser.hypotheticalbanner=true; + regen(); + return; + } + if(_.length){ + bfile=_[0]; + const blob = URL.createObjectURL(bfile); + hypouser.banner = blob; + hypouser.hypotheticalbanner=true; + regen(); + } + }); + let changed=false; + const pronounbox=settingsLeft.addTextInput("Pronouns",_=>{ + if(newpronouns||newbio||changed){ + this.updateProfile({pronouns: newpronouns,bio: newbio,accent_color: Number.parseInt("0x"+color.substr(1),16)}); + } + },{initText: this.user.pronouns}); + pronounbox.watchForChange(_=>{ + hypouser.pronouns=_; + newpronouns=_; + regen(); + }); + const bioBox=settingsLeft.addMDInput("Bio:",_=>{ + + },{initText: this.user.bio.rawString}); + bioBox.watchForChange(_=>{ + newbio=_; + hypouser.bio=new MarkDown(_,this); + regen(); + }); + + if(this.user.accent_color){ + color="#"+this.user.accent_color.toString(16); + }else{ + color="transparent"; + } + const colorPicker=settingsLeft.addColorInput("Profile color",_=>{},{initColor: color}); + colorPicker.watchForChange(_=>{ + console.log(); + color=_; + hypouser.accent_color=Number.parseInt("0x"+_.substr(1),16); + changed=true; + regen(); + }); + } + { + const tas=settings.addButton("Themes & sounds"); + { + const themes=["Dark","WHITE","Light"]; + tas.addSelect("Theme:",_=>{ + localStorage.setItem("theme",themes[_]); + setTheme(); + },themes,{defaultIndex: themes.indexOf(localStorage.getItem("theme") as string)}); + } + { + const sounds=Voice.sounds; + tas.addSelect("Notification sound:",_=>{ + Voice.setNotificationSound(sounds[_]); + },sounds,{defaultIndex: sounds.indexOf(Voice.getNotificationSound())}).watchForChange(_=>{ + Voice.noises(sounds[_]); + }); + } + + { + const userinfos=getBulkInfo(); + tas.addColorInput("Accent color:",_=>{ + fixsvgtheme(); + userinfos.accent_color=_; + localStorage.setItem("userinfos",JSON.stringify(userinfos)); + document.documentElement.style.setProperty("--accent-color", userinfos.accent_color); + },{initColor: userinfos.accent_color}); + } + } + { + const security=settings.addButton("Account Settings"); + const genSecurity=()=>{ + security.removeAll(); + if(this.mfa_enabled){ + security.addButtonInput("","Disable 2FA",()=>{ + const form=security.addSubForm("2FA Disable",(_:any)=>{ + if(_.message){ + switch(_.code){ + case 60008: + form.error("code","Invalid code"); + break; + } + }else{ + this.mfa_enabled=false; + security.returnFromSub(); + genSecurity(); + } + },{ + fetchURL: (this.info.api+"/users/@me/mfa/totp/disable"), + headers: this.headers + }); + form.addTextInput("Code:","code",{required: true}); + }); + }else{ + security.addButtonInput("","Enable 2FA",async ()=>{ + let secret=""; + for(let i=0;i<18;i++){ + secret+="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[Math.floor(Math.random()*32)]; + } + const form=security.addSubForm("2FA Setup",(_:any)=>{ + if(_.message){ + switch(_.code){ + case 60008: + form.error("code","Invalid code"); + break; + case 400: + form.error("password","Incorrect password"); + break; + } + }else{ + genSecurity(); + this.mfa_enabled=true; + security.returnFromSub(); + } + },{ + fetchURL: (this.info.api+"/users/@me/mfa/totp/enable/"), + headers: this.headers + }); + form.addTitle("Copy this secret into your totp(time-based one time password) app"); + form.addText(`Your secret is: ${secret} and it's 6 digits, with a 30 second token period`); + form.addTextInput("Account Password:","password",{required: true,password: true}); + form.addTextInput("Code:","code",{required: true}); + form.setValue("secret",secret); + }); + } + security.addButtonInput("","Change discriminator",()=>{ + const form=security.addSubForm("Change Discriminator",_=>{ + security.returnFromSub(); + },{ + fetchURL: (this.info.api+"/users/@me/"), + headers: this.headers, + method: "PATCH" + }); + form.addTextInput("New discriminator:","discriminator"); + }); + security.addButtonInput("","Change email",()=>{ + const form=security.addSubForm("Change Email",_=>{ + security.returnFromSub(); + },{ + fetchURL: (this.info.api+"/users/@me/"), + headers: this.headers, + method: "PATCH" + }); + form.addTextInput("Password:","password",{password: true}); + if(this.mfa_enabled){ + form.addTextInput("Code:","code"); + } + form.addTextInput("New email:","email"); + }); + security.addButtonInput("","Change username",()=>{ + const form=security.addSubForm("Change Username",_=>{ + security.returnFromSub(); + },{ + fetchURL: (this.info.api+"/users/@me/"), + headers: this.headers, + method: "PATCH" + }); + form.addTextInput("Password:","password",{password: true}); + if(this.mfa_enabled){ + form.addTextInput("Code:","code"); + } + form.addTextInput("New username:","username"); + }); + security.addButtonInput("","Change password",()=>{ + const form=security.addSubForm("Change Password",_=>{ + security.returnFromSub(); + },{ + fetchURL: (this.info.api+"/users/@me/"), + headers: this.headers, + method: "PATCH" + }); + form.addTextInput("Old password:","password",{password: true}); + if(this.mfa_enabled){ + form.addTextInput("Code:","code"); + } + let in1=""; + let in2=""; + form.addTextInput("New password:","").watchForChange(text=>{ + in1=text; + }); + const copy=form.addTextInput("New password again:",""); + copy.watchForChange(text=>{ + in2=text; + }); + form.setValue("new_password",()=>{ + if(in1===in2){ + return in1; + }else{ + throw new FormError(copy,"Passwords don't match"); + } + }); + }); + }; + genSecurity(); + } + { + const connections=settings.addButton("Connections"); + const connectionContainer=document.createElement("div"); + connectionContainer.id="connection-container"; + + fetch(this.info.api+"/connections", { + headers: this.headers + }).then(r=>r.json()).then(json=>{ + Object.keys(json).sort(key=>json[key].enabled ? -1 : 1).forEach(key=>{ + const connection=json[key]; + + const container=document.createElement("div"); + container.textContent=key.charAt(0).toUpperCase() + key.slice(1); + + if(connection.enabled){ + container.addEventListener("click", async ()=>{ + const connectionRes=await fetch(this.info.api+"/connections/"+key+"/authorize", { + headers: this.headers + }); + const connectionJSON=await connectionRes.json(); + window.open(connectionJSON.url, "_blank", "noopener noreferrer"); + }); + }else{ + container.classList.add("disabled"); + container.title="This connection has been disabled server-side."; + } + + connectionContainer.appendChild(container); + }); + }); + connections.addHTMLArea(connectionContainer); + } + { + const devPortal=settings.addButton("Developer Portal"); + + const teamsRes = await fetch(this.info.api + "/teams", { + headers: this.headers + }); + const teams = await teamsRes.json(); + + devPortal.addButtonInput("", "Create application", ()=>{ + const form = devPortal.addSubForm("Create application",(json:any)=>{ + if(json.message) form.error("name", json.message); + else{ + devPortal.returnFromSub(); + this.manageApplication(json.id); + } + }, { + fetchURL: this.info.api + "/applications", + headers: this.headers, + method: "POST" + }); + + form.addTextInput("Name", "name", { required: true }); + form.addSelect("Team", "team_id", ["Personal", ...teams.map(team=>team.name)], { + defaultIndex: 0 + }); + }); + + const appListContainer=document.createElement("div"); + appListContainer.id="app-list-container"; + fetch(this.info.api+"/applications", { + headers: this.headers + }).then(r=>r.json()).then(json=>{ + json.forEach(application=>{ + const container=document.createElement("div"); + + if(application.cover_image || application.icon){ + const cover=document.createElement("img"); + cover.crossOrigin="anonymous"; + cover.src=this.info.cdn+"/app-icons/"+application.id+"/"+(application.cover_image || application.icon)+".png?size=256"; + cover.alt=""; + cover.loading="lazy"; + container.appendChild(cover); + } + + const name=document.createElement("h2"); + name.textContent=application.name + (application.bot ? " (Bot)" : ""); + container.appendChild(name); + + container.addEventListener("click", async ()=>{ + this.manageApplication(application.id); + }); + appListContainer.appendChild(container); + }); + }); + devPortal.addHTMLArea(appListContainer); + } + settings.show(); + } + async manageApplication(appId=""){ + const res=await fetch(this.info.api+"/applications/" + appId, { + headers: this.headers + }); + const json=await res.json(); + + const fields: any={}; + const appDialog=new Dialog( + ["vdiv", + ["title", + "Editing " + json.name + ], + ["vdiv", + ["textbox", "Application name:", json.name, event=>{ + fields.name=event.target.value; + }], + ["mdbox", "Description:", json.description, event=>{ + fields.description=event.target.value; + }], + ["vdiv", + json.icon ? ["img", this.info.cdn+"/app-icons/" + appId + "/" + json.icon + ".png?size=128", [128, 128]] : ["text", "No icon"], + ["fileupload", "Application icon:", event=>{ + const reader=new FileReader(); + reader.readAsDataURL(event.target.files[0]); + reader.onload=()=>{ + fields.icon=reader.result; + }; + }] + ] + ], + ["hdiv", + ["textbox", "Privacy policy URL:", json.privacy_policy_url || "", event=>{ + fields.privacy_policy_url=event.target.value; + }], + ["textbox", "Terms of Service URL:", json.terms_of_service_url || "", event=>{ + fields.terms_of_service_url=event.target.value; + }] + ], + ["hdiv", + ["checkbox", "Make bot publicly inviteable?", json.bot_public, event=>{ + fields.bot_public=event.target.checked; + }], + ["checkbox", "Require code grant to invite the bot?", json.bot_require_code_grant, event=>{ + fields.bot_require_code_grant=event.target.checked; + }] + ], + ["hdiv", + ["button", + "", + "Save changes", + async ()=>{ + const updateRes=await fetch(this.info.api+"/applications/" + appId, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(fields) + }); + if(updateRes.ok) appDialog.hide(); + else{ + const updateJSON=await updateRes.json(); + alert("An error occurred: " + updateJSON.message); + } + } + ], + ["button", + "", + (json.bot ? "Manage" : "Add") + " bot", + async ()=>{ + if(!json.bot){ + if(!confirm("Are you sure you want to add a bot to this application? There's no going back."))return; + + const updateRes=await fetch(this.info.api+"/applications/" + appId + "/bot", { + method: "POST", + headers: this.headers + }); + const updateJSON=await updateRes.json(); + alert("Bot token:\n" + updateJSON.token); + } + + appDialog.hide(); + this.manageBot(appId); + } + ] + ] + ] + ); + appDialog.show(); + } + async manageBot(appId=""){ + const res=await fetch(this.info.api+"/applications/" + appId, { + headers: this.headers + }); + const json=await res.json(); + if(!json.bot)return alert("For some reason, this application doesn't have a bot (yet)."); + + const fields: any={ + username: json.bot.username, + avatar: json.bot.avatar ? (this.info.cdn+"/app-icons/" + appId + "/" + json.bot.avatar + ".png?size=256") : "" + }; + const botDialog=new Dialog( + ["vdiv", + ["title", + "Editing bot: " + json.bot.username + ], + ["hdiv", + ["textbox", "Bot username:", json.bot.username, event=>{ + fields.username=event.target.value; + }], + ["vdiv", + fields.avatar ? ["img", fields.avatar, [128, 128]] : ["text", "No avatar"], + ["fileupload", "Bot avatar:", event=>{ + const reader=new FileReader(); + reader.readAsDataURL(event.target.files[0]); + reader.onload=()=>{ + fields.avatar=reader.result; + }; + }] + ] + ], + ["hdiv", + ["button", + "", + "Save changes", + async ()=>{ + const updateRes=await fetch(this.info.api+"/applications/" + appId + "/bot", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(fields) + }); + if(updateRes.ok) botDialog.hide(); + else{ + const updateJSON=await updateRes.json(); + alert("An error occurred: " + updateJSON.message); + } + } + ], + ["button", + "", + "Reset token", + async ()=>{ + if(!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it."))return; + + const updateRes=await fetch(this.info.api+"/applications/" + appId + "/bot/reset", { + method: "POST", + headers: this.headers + }); + const updateJSON=await updateRes.json(); + alert("New token:\n" + updateJSON.token); + botDialog.hide(); + } + ] + ] + ] + ); + botDialog.show(); + } + + //---------- resolving members code ----------- + readonly waitingmembers:Mapvoid>>=new Map(); + readonly presences:Map=new Map(); + async resolvemember(id:string,guildid:string):Promise{ + if(guildid==="@me"){ + return undefined; + } + let guildmap=this.waitingmembers.get(guildid); + if(!guildmap){ + guildmap=new Map(); + this.waitingmembers.set(guildid,guildmap); + } + const promise:Promise=new Promise(res=>{ + guildmap.set(id,res); + this.getmembers(); + }); + return await promise; + } + fetchingmembers:Map=new Map(); + noncemap:Mapvoid>=new Map(); + noncebuild:Map=new Map(); + async gotChunk(chunk:{chunk_index:number,chunk_count:number,nonce:string,not_found?:string[],members?:memberjson[],presences:presencejson[]}){ + for(const thing of chunk.presences){ + if(thing.user){ + this.presences.set(thing.user.id,thing); + } + } + console.log(chunk); + chunk.members??=[]; + const arr=this.noncebuild.get(chunk.nonce); + if(!arr)return; + arr[0]=arr[0].concat(chunk.members); + if(chunk.not_found){ + arr[1]=chunk.not_found; + } + arr[2].push(chunk.chunk_index); + if(arr[2].length===chunk.chunk_count){ + console.log("got through"); + this.noncebuild.delete(chunk.nonce); + const func=this.noncemap.get(chunk.nonce); + if(!func)return; + func([arr[0],arr[1]]); + this.noncemap.delete(chunk.nonce); + } + } + async getmembers(){ + const promise=new Promise(res=>{ + setTimeout(res,10); + }); + await promise;//allow for more to be sent at once :P + if(this.ws){ + this.waitingmembers.forEach(async (value,guildid)=>{ + const keys=value.keys(); + if(this.fetchingmembers.has(guildid)){ + return; + } + const build:string[]=[]; + for(const key of keys){ + build.push(key);if(build.length===100){ + break; + } + } + if(!build.length){ + this.waitingmembers.delete(guildid); + return; + } + const promise:Promise<[memberjson[],string[]]>=new Promise(res=>{ + const nonce=""+Math.floor(Math.random()*100000000000); + this.noncemap.set(nonce,res); + this.noncebuild.set(nonce,[[],[],[]]); + if(!this.ws)return; + this.ws.send(JSON.stringify({ + op: 8, + d: { + user_ids: build, + guild_id: guildid, + limit: 100, + nonce, + presences: true + } + })); + this.fetchingmembers.set(guildid,true); + }); + const prom=await promise; + const data=prom[0]; + for(const thing of data){ + if(value.has(thing.id)){ + const func=value.get(thing.id); + if(!func)continue; + func(thing); + value.delete(thing.id); + } + } + for(const thing of prom[1]){ + if(value.has(thing)){ + const func=value.get(thing); + if(!func)continue; + func(); + value.delete(thing); + } + } + this.fetchingmembers.delete(guildid); + this.getmembers(); + }); + } + } + async pingEndpoint(){ + const userInfo = getBulkInfo(); + if(!userInfo.instances) userInfo.instances = {}; + const wellknown = this.info.wellknown; + if(!userInfo.instances[wellknown]){ + const pingRes = await fetch(this.info.api + "/ping"); + const pingJSON = await pingRes.json(); + userInfo.instances[wellknown] = pingJSON; + localStorage.setItem("userinfos", JSON.stringify(userInfo)); + } + this.instancePing = userInfo.instances[wellknown].instance; + + this.pageTitle("Loading..."); + } + pageTitle(channelName = "", guildName = ""){ + (document.getElementById("channelname") as HTMLSpanElement).textContent = channelName; + (document.getElementsByTagName("title")[0] as HTMLTitleElement).textContent = channelName + (guildName ? " | " + guildName : "") + " | " + this.instancePing.name + " | Jank Client (Tomato fork)"; + } + async instanceStats(){ + const res = await fetch(this.info.api + "/policies/stats", { + headers: this.headers + }); + const json = await res.json(); + + const dialog = new Dialog(["vdiv", + ["title", "Instance stats: " + this.instancePing.name], + ["text", "Registered users: " + json.counts.user], + ["text", "Servers: " + json.counts.guild], + ["text", "Messages: " + json.counts.message], + ["text", "Members: " + json.counts.members] + ]); + dialog.show(); + } } -export {Localuser}; +export{Localuser}; let fixsvgtheme:Function; { - let last:string; - const dud=document.createElement("p") - dud.classList.add("svgtheme") - document.body.append(dud); - const css=window.getComputedStyle(dud); - function fixsvgtheme_(){ - //console.log(things); - const color=css.color; - if(color===last) {return}; - last=color; - const thing=color.replace("rgb(","").replace(")","").split(","); - //sconsole.log(thing); - const r=+thing[0]/255; - const g=+thing[1]/255; - const b=+thing[2]/255; - const max=Math.max(r,g,b); - const min=Math.min(r,g,b); - const l=(max+min)/2; + let last:string; + const dud=document.createElement("p"); + dud.classList.add("svgtheme"); + document.body.append(dud); + const css=window.getComputedStyle(dud); + function fixsvgtheme_(){ + //console.log(things); + const color=css.color; + if(color===last){ + return; + } + last=color; + const thing=color.replace("rgb(","").replace(")","").split(","); + //sconsole.log(thing); + const r=Number(thing[0])/255; + const g=Number(thing[1])/255; + const b=Number(thing[2])/255; + const max=Math.max(r,g,b); + const min=Math.min(r,g,b); + const l=(max+min)/2; - let s:number; - let h:number; - if(max!==min){ - if(l<=.5){ - s=(max-min)/(max+min); - }else{ - s=(max-min)/(2.0-max-min); - } - if(r===max){ - h=(g-b)/(max-min); - }else if(g===max){ - h=2+(b-r)/(max-min); - }else{ - h=4+(r-g)/(max-min); - } - }else{ - s=0; - h=0; - } - const rot=Math.floor(h*60)+"deg"; - const invert=.5-(s/2)+""; - const brightness=Math.floor((l*200))+"%"; + let s:number; + let h:number; + if(max!==min){ + if(l<=0.5){ + s=(max-min)/(max+min); + }else{ + s=(max-min)/(2-max-min); + } + if(r===max){ + h=(g-b)/(max-min); + }else if(g===max){ + h=2+(b-r)/(max-min); + }else{ + h=4+(r-g)/(max-min); + } + }else{ + s=0; + h=0; + } + const rot=Math.floor(h*60)+"deg"; + const invert=0.5-(s/2)+""; + const brightness=Math.floor((l*200))+"%"; - document.documentElement.style.setProperty('--rot', rot); - document.documentElement.style.setProperty('--invert', invert); - document.documentElement.style.setProperty('--brightness', brightness); - - } - fixsvgtheme=fixsvgtheme_; - setTimeout(fixsvgtheme_,100); - fixsvgtheme_(); + document.documentElement.style.setProperty("--rot", rot); + document.documentElement.style.setProperty("--invert", invert); + document.documentElement.style.setProperty("--brightness", brightness); + } + fixsvgtheme=fixsvgtheme_; + setTimeout(fixsvgtheme_,100); + fixsvgtheme_(); } -export {fixsvgtheme}; +export{fixsvgtheme}; From 2150a4832e5e00be605ee310c770c7ccf4627563 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 31 Aug 2024 16:22:29 -0500 Subject: [PATCH 3/8] fix uptime calcuations --- stats.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stats.js b/stats.js index df58f21..dc581da 100644 --- a/stats.js +++ b/stats.js @@ -102,11 +102,18 @@ function calcStats(instance){ i++; } + console.log(daytime); instance.online=online; alltime/=totalTimePassed; if(totalTimePassed>1000*60*60*24){ + if(daytime===-1){ + daytime=online*1000*60*60*24; + } daytime/=1000*60*60*24; if(totalTimePassed>1000*60*60*24*7){ + if(weektime===-1){ + weektime=online*1000*60*60*24*7; + } weektime/=1000*60*60*24*7; }else{ weektime=alltime; From 2b69d338c321247c4d8ed7222aa18db9c03c0a15 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 31 Aug 2024 16:26:02 -0500 Subject: [PATCH 4/8] clean stats code a little --- stats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stats.js b/stats.js index dc581da..a0a33a9 100644 --- a/stats.js +++ b/stats.js @@ -72,8 +72,8 @@ function calcStats(instance){ let online=false; let i=0; for(const thing of obj){ - online=obj[i].online; - const stamp=obj[i].time; + online=thing.online; + const stamp=thing.time; if(alltime===-1){ alltime=0; } From a4477a8c5e2e5ce4fc192c2e602efbbf90b07628 Mon Sep 17 00:00:00 2001 From: TomatoCake <60300461+DEVTomatoCake@users.noreply.github.com> Date: Sun, 1 Sep 2024 18:14:56 +0200 Subject: [PATCH 5/8] hide context menu if empty --- webpage/contextmenu.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webpage/contextmenu.ts b/webpage/contextmenu.ts index 5260722..2dca231 100644 --- a/webpage/contextmenu.ts +++ b/webpage/contextmenu.ts @@ -30,8 +30,12 @@ class Contextmenu{ makemenu(x:number,y:number,addinfo:any,other:y){ const div=document.createElement("div"); div.classList.add("contextmenu","flexttb"); + + let visibleButtons=0; for(const thing of this.buttons){ - if(!thing[3].bind(addinfo)(other)){continue;} + if(!thing[3].bind(addinfo)(other))continue; + visibleButtons++; + const intext=document.createElement("button") intext.disabled=!thing[4].bind(addinfo)(other); intext.classList.add("contextbutton") @@ -43,6 +47,8 @@ class Contextmenu{ div.appendChild(intext); } + if (visibleButtons == 0) return; + if(Contextmenu.currentmenu!=""){ Contextmenu.currentmenu.remove(); } From 59a5d9a30bd530332f28cfa776ed625d283353d0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 2 Sep 2024 15:25:49 -0500 Subject: [PATCH 6/8] betting typing of the WS --- .dist/localuser.js | 28 +++++++++++++++++--- tsconfig.json | 2 +- webpage/channel.ts | 4 +-- webpage/jsontypes.ts | 61 +++++++++++++++++++++++++++++++++++++++++--- webpage/localuser.ts | 43 +++++++++++++++++++++++-------- 5 files changed, 118 insertions(+), 20 deletions(-) diff --git a/.dist/localuser.js b/.dist/localuser.js index 2a3fc87..e221584 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -305,15 +305,35 @@ class Localuser { break; case "MESSAGE_DELETE": console.log(temp.d); - SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject().deleteEvent(); + temp.d.guild_id ??= "@me"; + const guild = this.guildids.get(temp.d.guild_id); + if (guild) { + const channel = guild.channelids[temp.d.guild_id]; + if (channel) { + const message = channel.messages.get(temp.d.id); + if (message) { + message.deleteEvent(); + } + } + } break; case "READY": this.gottenReady(temp); break; - case "MESSAGE_UPDATE": - const message = SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject(); - message.giveData(temp.d); + case "MESSAGE_UPDATE": { + temp.d.guild_id ??= "@me"; + const guild = this.guildids.get(temp.d.guild_id); + if (guild) { + const channel = guild.channelids[temp.d.guild_id]; + if (channel) { + const message = channel.messages.get(temp.d.id); + if (message) { + message.giveData(temp.d); + } + } + } break; + } case "TYPING_START": if (this.initialized) { this.typingStart(temp); diff --git a/tsconfig.json b/tsconfig.json index 71aba3f..e59ead1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "removeComments": false, "noImplicitThis":true, "useUnknownInCatchVariables":true, - "strictNullChecks":false + "strictNullChecks":true }, "include": [ "./webpage/*.ts" diff --git a/webpage/channel.ts b/webpage/channel.ts index a6e8048..371b0ac 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -10,7 +10,7 @@ import { Settings } from "./settings.js"; import { Role,RoleList } from "./role.js"; import {InfiniteScroller} from "./infiniteScroller.js"; import { SnowFlake } from "./snowflake.js"; -import { channeljson, messagejson, readyjson } from "./jsontypes.js"; +import { channeljson, messageCreateJson, messagejson, readyjson } from "./jsontypes.js"; import { MarkDown } from "./markdown.js"; declare global { @@ -994,7 +994,7 @@ class Channel{ }); } } - messageCreate(messagep:any):void{ + messageCreate(messagep:messageCreateJson):void{ if(!this.hasPermission("VIEW_CHANNEL")){return} const messagez=new Message(messagep.d,this); this.lastmessage=messagez; diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index 13622f7..17a89bd 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -1,6 +1,6 @@ type readyjson={ - op:number; - t:string; + op:0; + t:"READY"; s:number; d:{ v:number; @@ -341,4 +341,59 @@ type presencejson={ afk: boolean, user?:userjson, } -export {readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson,emojijson,presencejson}; +type messageCreateJson={ + op:0, + d:{ + guild_id?:string, + channel_id?:string, + }&messagejson, + s:number, + t:"MESSAGE_CREATE" +} +type wsjson={ + op:0, + d:any, + s:number, + t:"TYPING_START"|"USER_UPDATE"|"CHANNEL_UPDATE"|"CHANNEL_CREATE"|"CHANNEL_DELETE"|"GUILD_DELETE"|"GUILD_CREATE"|"MESSAGE_REACTION_ADD"|"MESSAGE_REACTION_REMOVE"|"MESSAGE_REACTION_REMOVE_ALL"|"MESSAGE_REACTION_REMOVE_EMOJI" +}|{ + op:0, + t:"GUILD_MEMBERS_CHUNK", + d:memberChunk +}|{ + op:0, + d:{ + id:string, + guild_id?:string, + channel_id:string + }, + s:number, + t:"MESSAGE_DELETE" +}|{ + op:0, + d:{ + guild_id?:string, + channel_id:string + }&messagejson, + s:number, + t:"MESSAGE_UPDATE" +}|messageCreateJson|readyjson|{ + op:11, + s:undefined, + d:{} +}|{ + op:10, + s:undefined, + d:{ + heartbeat_interval:number + } +} +type memberChunk={ + guild_id: string, + nonce: string, + members: memberjson[], + presences: presencejson[], + chunk_index: number, + chunk_count: number, + not_found: string[] +} +export {readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson,emojijson,presencejson,wsjson,messageCreateJson,memberChunk}; diff --git a/webpage/localuser.ts b/webpage/localuser.ts index d004fed..b345b2c 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -7,7 +7,7 @@ import {Dialog} from "./dialog.js"; import {getapiurls, getBulkInfo, setTheme, Specialuser} from "./login.js"; import { SnowFlake } from "./snowflake.js"; import { Message } from "./message.js"; -import { channeljson, memberjson, presencejson, readyjson } from "./jsontypes.js"; +import { channeljson, memberChunk, memberjson, messageCreateJson, presencejson, readyjson, wsjson } from "./jsontypes.js"; import { Member } from "./member.js"; import { FormError, Settings } from "./settings.js"; import { MarkDown } from "./markdown.js"; @@ -220,7 +220,7 @@ class Localuser{ if(temp.op===0&&temp.t==="READY"){ returny(); } - await this.handleEvent(temp); + await this.handleEvent(temp as wsjson); }catch(e){ console.error(e); }finally{ @@ -300,7 +300,7 @@ class Localuser{ await promise; return; } - async handleEvent(temp){ + async handleEvent(temp:wsjson){ console.debug(temp); if (temp.s) this.lastSequence=temp.s; if(temp.op==0){ @@ -312,15 +312,38 @@ class Localuser{ break; case "MESSAGE_DELETE": console.log(temp.d); - SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent(); + temp.d.guild_id??="@me"; + const guild=this.guildids.get(temp.d.guild_id); + if(guild){ + const channel=guild.channelids[temp.d.guild_id] + if(channel){ + const message=channel.messages.get(temp.d.id); + if(message){ + message.deleteEvent(); + } + } + + } + break; case "READY": this.gottenReady(temp as readyjson); break; - case "MESSAGE_UPDATE": - const message=SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject(); - message.giveData(temp.d); + case "MESSAGE_UPDATE":{ + temp.d.guild_id??="@me"; + const guild=this.guildids.get(temp.d.guild_id); + if(guild){ + const channel=guild.channelids[temp.d.guild_id] + if(channel){ + const message=channel.messages.get(temp.d.id); + if(message){ + message.giveData(temp.d); + } + } + + } break; + } case "TYPING_START": if(this.initialized){ this.typingStart(temp); @@ -400,7 +423,7 @@ class Localuser{ const messageReactionRemoveEmoji = SnowFlake.getSnowFlakeFromID(temp.d.message_id, Message).getObject() messageReactionRemoveEmoji.reactionRemoveEmoji(temp.d.emoji) } - break + break; case "GUILD_MEMBERS_CHUNK": this.gotChunk(temp.d); break; @@ -702,7 +725,7 @@ class Localuser{ }) content.appendChild(guilds); } - messageCreate(messagep):void{ + messageCreate(messagep:messageCreateJson):void{ messagep.d.guild_id??="@me"; const guild=this.guildids.get(messagep.d.guild_id); if(!guild) return; @@ -1320,7 +1343,7 @@ class Localuser{ fetchingmembers:Map=new Map(); noncemap:Mapvoid>=new Map(); noncebuild:Map=new Map(); - async gotChunk(chunk:{chunk_index:number,chunk_count:number,nonce:string,not_found?:string[],members?:memberjson[],presences:presencejson[]}){ + async gotChunk(chunk:memberChunk){ for(const thing of chunk.presences){ if(thing.user){ this.presences.set(thing.user.id,thing); From f8b80b65fe1125647a3e2a0586fb1972da3f5d77 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 2 Sep 2024 15:32:53 -0500 Subject: [PATCH 7/8] compiled js changes --- .dist/contextmenu.js | 7 +- .dist/localuser.js | 183 ++++++++++++++++++++----------------------- 2 files changed, 90 insertions(+), 100 deletions(-) diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js index d741cd3..1fd5b6d 100644 --- a/.dist/contextmenu.js +++ b/.dist/contextmenu.js @@ -30,10 +30,11 @@ class Contextmenu { makemenu(x, y, addinfo, other) { const div = document.createElement("div"); div.classList.add("contextmenu", "flexttb"); + let visibleButtons = 0; for (const thing of this.buttons) { - if (!thing[3].bind(addinfo)(other)) { + if (!thing[3].bind(addinfo)(other)) continue; - } + visibleButtons++; const intext = document.createElement("button"); intext.disabled = !thing[4].bind(addinfo)(other); intext.classList.add("contextbutton"); @@ -44,6 +45,8 @@ class Contextmenu { } div.appendChild(intext); } + if (visibleButtons == 0) + return; if (Contextmenu.currentmenu != "") { Contextmenu.currentmenu.remove(); } diff --git a/.dist/localuser.js b/.dist/localuser.js index e221584..4be9c1f 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -125,7 +125,6 @@ class Localuser { async initwebsocket() { let returny; const ws = new WebSocket(this.serverurls.gateway.toString() + "?encoding=json&v=9" + (DecompressionStream ? "&compress=zlib-stream" : "")); - ; this.ws = ws; let ds; let w; @@ -138,27 +137,27 @@ class Localuser { r = ds.readable.getReader(); arr = new Uint8Array(); } - const promise = new Promise((res) => { + const promise = new Promise(res => { returny = res; - ws.addEventListener('open', (_event) => { - console.log('WebSocket connected'); + ws.addEventListener("open", _event => { + console.log("WebSocket connected"); ws.send(JSON.stringify({ - "op": 2, - "d": { - "token": this.token, - "capabilities": 16381, - "properties": { - "browser": "Jank Client", - "client_build_number": 0, //might update this eventually lol - "release_channel": "Custom", - "browser_user_agent": navigator.userAgent + op: 2, + d: { + token: this.token, + capabilities: 16381, + properties: { + browser: "Jank Client", + client_build_number: 0, //might update this eventually lol + release_channel: "Custom", + browser_user_agent: navigator.userAgent }, - "compress": !!DecompressionStream, - "presence": { - "status": "online", - "since": null, //new Date().getTime() - "activities": [], - "afk": false + compress: Boolean(DecompressionStream), + presence: { + status: "online", + since: null, //new Date().getTime() + activities: [], + afk: false } } })); @@ -183,8 +182,8 @@ class Localuser { })(); } }); - let order = new Promise((res) => (res())); - ws.addEventListener('message', async (event) => { + let order = new Promise(res => (res())); + ws.addEventListener("message", async (event) => { const temp2 = order; order = new Promise(async (res) => { await temp2; @@ -261,7 +260,7 @@ class Localuser { case 5: { const breakappart = new URL(this.info.wellknown).origin.split("."); - const url = "https://" + breakappart[breakappart.length - 2] + "." + breakappart[breakappart.length - 1]; + const url = "https://" + breakappart.at(-2) + "." + breakappart.at(-1); const newurls = await getapiurls(url); if (newurls) { this.info = newurls; @@ -290,7 +289,6 @@ class Localuser { document.getElementById("load-desc").textContent = "Unable to connect to the Spacebar server. Please try logging out and back in."; }); await promise; - return; } async handleEvent(temp) { console.debug(temp); @@ -305,35 +303,15 @@ class Localuser { break; case "MESSAGE_DELETE": console.log(temp.d); - temp.d.guild_id ??= "@me"; - const guild = this.guildids.get(temp.d.guild_id); - if (guild) { - const channel = guild.channelids[temp.d.guild_id]; - if (channel) { - const message = channel.messages.get(temp.d.id); - if (message) { - message.deleteEvent(); - } - } - } + SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject().deleteEvent(); break; case "READY": this.gottenReady(temp); break; - case "MESSAGE_UPDATE": { - temp.d.guild_id ??= "@me"; - const guild = this.guildids.get(temp.d.guild_id); - if (guild) { - const channel = guild.channelids[temp.d.guild_id]; - if (channel) { - const message = channel.messages.get(temp.d.id); - if (message) { - message.giveData(temp.d); - } - } - } + case "MESSAGE_UPDATE": + const message = SnowFlake.getSnowFlakeFromID(temp.d.id, Message).getObject(); + message.giveData(temp.d); break; - } case "TYPING_START": if (this.initialized) { this.typingStart(temp); @@ -438,7 +416,7 @@ class Localuser { } heartbeat_interval; resolveChannelFromID(ID) { - let resolve = this.guilds.find(guild => guild.channelids[ID]); + const resolve = this.guilds.find(guild => guild.channelids[ID]); if (resolve) { return resolve.channelids[ID]; } @@ -670,7 +648,7 @@ class Localuser { content.appendChild(title); const guilds = document.createElement("div"); guilds.id = "discovery-guild-content"; - json.guilds.forEach((guild) => { + json.guilds.forEach(guild => { const content = document.createElement("div"); content.classList.add("discovery-guild"); if (guild.banner) { @@ -738,13 +716,13 @@ class Localuser { return; } console.log("user is typing and you should see it"); - this.typing.set(memb, new Date().getTime()); + this.typing.set(memb, Date.now()); setTimeout(this.rendertyping.bind(this), 10000); this.rendertyping(); } } updatepfp(file) { - var reader = new FileReader(); + const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { fetch(this.info.api + "/users/@me", { @@ -758,7 +736,7 @@ class Localuser { } updatebanner(file) { if (file) { - var reader = new FileReader(); + const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { fetch(this.info.api + "/users/@me", { @@ -792,7 +770,7 @@ class Localuser { let build = ""; let showing = false; let i = 0; - const curtime = new Date().getTime() - 5000; + const curtime = Date.now() - 5000; for (const thing of this.typing.keys()) { if (this.typing.get(thing) > curtime) { if (i !== 0) { @@ -826,15 +804,15 @@ class Localuser { typingtext.classList.add("hidden"); } } - showusersettings() { + async showusersettings() { const settings = new Settings("Settings"); { const userOptions = settings.addButton("User Settings", { ltr: true }); const hypotheticalProfile = document.createElement("div"); - let file = undefined; - let newpronouns = undefined; - let newbio = undefined; - let hypouser = this.user.clone(); + let file; + let newpronouns; + let newbio; + const hypouser = this.user.clone(); let color; async function regen() { hypotheticalProfile.textContent = ""; @@ -858,7 +836,6 @@ class Localuser { regen(); return; } - ; if (_.length) { file = _[0]; const blob = URL.createObjectURL(file); @@ -867,7 +844,7 @@ class Localuser { regen(); } }); - let bfile = undefined; + let bfile; const binput = settingsLeft.addFileInput("Upload banner:", _ => { if (bfile !== undefined) { this.updatebanner(bfile); @@ -892,7 +869,7 @@ class Localuser { let changed = false; const pronounbox = settingsLeft.addTextInput("Pronouns", _ => { if (newpronouns || newbio || changed) { - this.updateProfile({ pronouns: newpronouns, bio: newbio, accent_color: parseInt("0x" + color.substr(1), 16) }); + this.updateProfile({ pronouns: newpronouns, bio: newbio, accent_color: Number.parseInt("0x" + color.substr(1), 16) }); } }, { initText: this.user.pronouns }); pronounbox.watchForChange(_ => { @@ -913,11 +890,11 @@ class Localuser { else { color = "transparent"; } - const colorPicker = settingsLeft.addColorInput("Profile color", (_) => { }, { initColor: color }); + const colorPicker = settingsLeft.addColorInput("Profile color", _ => { }, { initColor: color }); colorPicker.watchForChange(_ => { console.log(); color = _; - hypouser.accent_color = parseInt("0x" + _.substr(1), 16); + hypouser.accent_color = Number.parseInt("0x" + _.substr(1), 16); changed = true; regen(); }); @@ -945,7 +922,7 @@ class Localuser { fixsvgtheme(); userinfos.accent_color = _; localStorage.setItem("userinfos", JSON.stringify(userinfos)); - document.documentElement.style.setProperty('--accent-color', userinfos.accent_color); + document.documentElement.style.setProperty("--accent-color", userinfos.accent_color); }, { initColor: userinfos.accent_color }); } } @@ -1009,7 +986,9 @@ class Localuser { }); } security.addButtonInput("", "Change discriminator", () => { - const form = security.addSubForm("Change Discriminator", (_) => { security.returnFromSub(); }, { + const form = security.addSubForm("Change Discriminator", _ => { + security.returnFromSub(); + }, { fetchURL: (this.info.api + "/users/@me/"), headers: this.headers, method: "PATCH" @@ -1017,7 +996,9 @@ class Localuser { form.addTextInput("New discriminator:", "discriminator"); }); security.addButtonInput("", "Change email", () => { - const form = security.addSubForm("Change Email", (_) => { security.returnFromSub(); }, { + const form = security.addSubForm("Change Email", _ => { + security.returnFromSub(); + }, { fetchURL: (this.info.api + "/users/@me/"), headers: this.headers, method: "PATCH" @@ -1029,7 +1010,9 @@ class Localuser { form.addTextInput("New email:", "email"); }); security.addButtonInput("", "Change username", () => { - const form = security.addSubForm("Change Username", (_) => { security.returnFromSub(); }, { + const form = security.addSubForm("Change Username", _ => { + security.returnFromSub(); + }, { fetchURL: (this.info.api + "/users/@me/"), headers: this.headers, method: "PATCH" @@ -1041,7 +1024,9 @@ class Localuser { form.addTextInput("New username:", "username"); }); security.addButtonInput("", "Change password", () => { - const form = security.addSubForm("Change Password", (_) => { security.returnFromSub(); }, { + const form = security.addSubForm("Change Password", _ => { + security.returnFromSub(); + }, { fetchURL: (this.info.api + "/users/@me/"), headers: this.headers, method: "PATCH" @@ -1102,23 +1087,27 @@ class Localuser { } { const devPortal = settings.addButton("Developer Portal"); - let appName = ""; - devPortal.addTextInput("Name:", value => { - appName = value; + const teamsRes = await fetch(this.info.api + "/teams", { + headers: this.headers }); - devPortal.addButtonInput("", "Create application", async () => { - if (appName.trim().length == 0) { - return alert("Please enter a name for the application."); - } - const res = await fetch(this.info.api + "/applications", { - method: "POST", + const teams = await teamsRes.json(); + devPortal.addButtonInput("", "Create application", () => { + const form = devPortal.addSubForm("Create application", (json) => { + if (json.message) + form.error("name", json.message); + else { + devPortal.returnFromSub(); + this.manageApplication(json.id); + } + }, { + fetchURL: this.info.api + "/applications", headers: this.headers, - body: JSON.stringify({ - name: appName - }) + method: "POST" + }); + form.addTextInput("Name", "name", { required: true }); + form.addSelect("Team", "team_id", ["Personal", ...teams.map(team => team.name)], { + defaultIndex: 0 }); - const json = await res.json(); - this.manageApplication(json.id); }); const appListContainer = document.createElement("div"); appListContainer.id = "app-list-container"; @@ -1311,7 +1300,7 @@ class Localuser { guildmap = new Map(); this.waitingmembers.set(guildid, guildmap); } - const promise = new Promise((res) => { + const promise = new Promise(res => { guildmap.set(id, res); this.getmembers(); }); @@ -1347,7 +1336,9 @@ class Localuser { } } async getmembers() { - const promise = new Promise(res => { setTimeout(res, 10); }); + const promise = new Promise(res => { + setTimeout(res, 10); + }); await promise; //allow for more to be sent at once :P if (this.ws) { this.waitingmembers.forEach(async (value, guildid) => { @@ -1362,13 +1353,11 @@ class Localuser { break; } } - ; if (!build.length) { this.waitingmembers.delete(guildid); return; } - ; - const promise = new Promise((res) => { + const promise = new Promise(res => { const nonce = "" + Math.floor(Math.random() * 100000000000); this.noncemap.set(nonce, res); this.noncebuild.set(nonce, [[], [], []]); @@ -1387,7 +1376,6 @@ class Localuser { this.fetchingmembers.set(guildid, true); }); const prom = await promise; - ; const data = prom[0]; for (const thing of data) { if (value.has(thing.id)) { @@ -1459,24 +1447,23 @@ let fixsvgtheme; if (color === last) { return; } - ; last = color; const thing = color.replace("rgb(", "").replace(")", "").split(","); //sconsole.log(thing); - const r = +thing[0] / 255; - const g = +thing[1] / 255; - const b = +thing[2] / 255; + const r = Number(thing[0]) / 255; + const g = Number(thing[1]) / 255; + const b = Number(thing[2]) / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const l = (max + min) / 2; let s; let h; if (max !== min) { - if (l <= .5) { + if (l <= 0.5) { s = (max - min) / (max + min); } else { - s = (max - min) / (2.0 - max - min); + s = (max - min) / (2 - max - min); } if (r === max) { h = (g - b) / (max - min); @@ -1493,11 +1480,11 @@ let fixsvgtheme; h = 0; } const rot = Math.floor(h * 60) + "deg"; - const invert = .5 - (s / 2) + ""; + const invert = 0.5 - (s / 2) + ""; const brightness = Math.floor((l * 200)) + "%"; - document.documentElement.style.setProperty('--rot', rot); - document.documentElement.style.setProperty('--invert', invert); - document.documentElement.style.setProperty('--brightness', brightness); + document.documentElement.style.setProperty("--rot", rot); + document.documentElement.style.setProperty("--invert", invert); + document.documentElement.style.setProperty("--brightness", brightness); } fixsvgtheme = fixsvgtheme_; setTimeout(fixsvgtheme_, 100); From 19f08a64085f416bf21585d6216d4b91f2e6b608 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Mon, 2 Sep 2024 15:59:56 -0500 Subject: [PATCH 8/8] apply and fix ESlint --- .dist/audio.js | 52 +- .dist/channel.js | 143 +-- .dist/contextmenu.js | 14 +- .dist/dialog.js | 14 +- .dist/direct.js | 3 +- .dist/embed.js | 1 - .dist/emoji.js | 10 +- .dist/file.js | 10 +- .dist/guild.js | 24 +- .dist/home.js | 2 +- .dist/index.js | 27 +- .dist/infiniteScroller.js | 16 +- .dist/invite.js | 6 +- .dist/login.js | 51 +- .dist/markdown.js | 45 +- .dist/member.js | 5 +- .dist/message.js | 35 +- .dist/permissions.js | 4 +- .dist/register.js | 14 +- .dist/role.js | 5 - .dist/service.js | 9 +- .dist/settings.js | 19 +- .dist/user.js | 10 +- emoji-packer.js | 158 ++- eslint.config.js | 8 +- index.js | 293 +++-- stats.js | 284 ++--- webpage/audio.ts | 304 ++--- webpage/channel.ts | 2124 ++++++++++++++++++----------------- webpage/contextmenu.ts | 162 +-- webpage/dialog.ts | 483 ++++---- webpage/direct.ts | 380 +++---- webpage/embed.ts | 446 ++++---- webpage/emoji.ts | 396 +++---- webpage/file.ts | 282 ++--- webpage/guild.ts | 1057 ++++++++--------- webpage/home.html | 84 +- webpage/home.ts | 114 +- webpage/index.html | 144 +-- webpage/index.ts | 400 +++---- webpage/infiniteScroller.ts | 581 +++++----- webpage/invite.html | 40 +- webpage/invite.ts | 228 ++-- webpage/jsontypes.ts | 2 +- webpage/login.html | 60 +- webpage/login.ts | 718 ++++++------ webpage/markdown.ts | 1099 +++++++++--------- webpage/member.ts | 411 +++---- webpage/message.ts | 1149 ++++++++++--------- webpage/permissions.ts | 632 +++++------ webpage/register.html | 104 +- webpage/register.ts | 193 ++-- webpage/role.ts | 308 ++--- webpage/service.ts | 150 +-- webpage/settings.ts | 1697 ++++++++++++++-------------- webpage/snowflake.ts | 168 +-- webpage/user.ts | 835 +++++++------- 57 files changed, 8070 insertions(+), 7943 deletions(-) diff --git a/.dist/audio.js b/.dist/audio.js index 7ffb6fb..ea8fa37 100644 --- a/.dist/audio.js +++ b/.dist/audio.js @@ -9,7 +9,7 @@ class Voice { source; constructor(wave, freq, volume = 1) { this.audioCtx = new (window.AudioContext)(); - this.info = { wave: wave, freq: freq }; + this.info = { wave, freq }; this.playing = false; this.myArrayBuffer = this.audioCtx.createBuffer(1, this.audioCtx.sampleRate, this.audioCtx.sampleRate); this.gainNode = this.audioCtx.createGain(); @@ -43,7 +43,7 @@ class Voice { } } waveFunction() { - if (typeof this.wave === 'function') { + if (typeof this.wave === "function") { return this.wave; } switch (this.wave) { @@ -92,9 +92,15 @@ class Voice { case "three": { const voicy = new Voice("sin", 800); voicy.play(); - setTimeout(_ => { voicy.freq = 1000; }, 50); - setTimeout(_ => { voicy.freq = 1300; }, 100); - setTimeout(_ => { voicy.stop(); }, 150); + setTimeout(_ => { + voicy.freq = 1000; + }, 50); + setTimeout(_ => { + voicy.freq = 1300; + }, 100); + setTimeout(_ => { + voicy.stop(); + }, 150); break; } case "zip": { @@ -102,23 +108,37 @@ class Voice { return Math.sin(((t + 2) ** (Math.cos(t * 4))) * Math.PI * 2 * freq); }, 700); voicy.play(); - setTimeout(_ => { voicy.stop(); }, 150); + setTimeout(_ => { + voicy.stop(); + }, 150); break; } case "square": { - const voicy = new Voice("square", 600, .4); + const voicy = new Voice("square", 600, 0.4); voicy.play(); - setTimeout(_ => { voicy.freq = 800; }, 50); - setTimeout(_ => { voicy.freq = 1000; }, 100); - setTimeout(_ => { voicy.stop(); }, 150); + 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); + setTimeout(_ => { + voicy.stop(); + }, 50); + setTimeout(_ => { + voicy.play(); + }, 100); + setTimeout(_ => { + voicy.stop(); + }, 150); break; } } @@ -127,13 +147,13 @@ class Voice { return ["three", "zip", "square", "beep"]; } static setNotificationSound(sound) { - let userinfos = getBulkInfo(); + const userinfos = getBulkInfo(); userinfos.preferences.notisound = sound; localStorage.setItem("userinfos", JSON.stringify(userinfos)); } static getNotificationSound() { - let userinfos = getBulkInfo(); + const userinfos = getBulkInfo(); return userinfos.preferences.notisound; } } -export { Voice as Voice }; +export { Voice }; diff --git a/.dist/channel.js b/.dist/channel.js index 1946f1c..446c721 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -58,10 +58,14 @@ class Channel { this.contextmenu.addbutton("Delete channel", function () { console.log(this); this.deleteChannel(); - }, null, function () { return this.isAdmin(); }); + }, null, function () { + return this.isAdmin(); + }); this.contextmenu.addbutton("Edit channel", function () { this.editChannel(); - }, null, function () { return this.isAdmin(); }); + }, null, function () { + return this.isAdmin(); + }); this.contextmenu.addbutton("Make invite", function () { this.createInvite(); }, null, function () { @@ -148,12 +152,11 @@ class Channel { } sortPerms() { this.permission_overwritesar.sort((a, b) => { - const order = this.guild.roles.findIndex(_ => _.snowflake === a[0]) - this.guild.roles.findIndex(_ => _.snowflake === b[0]); - return order; + return this.guild.roles.findIndex(_ => _.snowflake === a[0]) - this.guild.roles.findIndex(_ => _.snowflake === b[0]); }); } setUpInfiniteScroller() { - this.infinite = new InfiniteScroller(async function (id, offset) { + this.infinite = new InfiniteScroller((async (id, offset) => { const snowflake = id; if (offset === 1) { if (this.idToPrev.has(snowflake)) { @@ -176,13 +179,12 @@ class Channel { console.log("at bottom"); } } - }.bind(this), async function (id) { + }), (async (id) => { //await new Promise(_=>{setTimeout(_,Math.random()*10)}) const messgage = this.messages.get(id); try { if (messgage) { - const html = messgage.buildhtml(); - return html; + return messgage.buildhtml(); } else { console.error(id + " not found"); @@ -191,18 +193,21 @@ class Channel { catch (e) { console.error(e); } - }.bind(this), async function (id) { + return document.createElement("div"); + }), (async (id) => { const message = this.messages.get(id); try { if (message) { message.deleteDiv(); + return true; } } catch (e) { console.error(e); } finally { } - }.bind(this), this.readbottom.bind(this)); + return false; + }), this.readbottom.bind(this)); } constructor(json, owner) { if (json === -1) { @@ -227,7 +232,6 @@ class Channel { if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") { continue; } - ; this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny)); const permission = this.permission_overwrites.get(thing.id); if (permission) { @@ -268,7 +272,7 @@ class Channel { if (!this.hasPermission("VIEW_CHANNEL")) { return false; } - return this.lastmessageid !== this.lastreadmessageid && this.type !== 4 && !!this.lastmessageid; + return this.lastmessageid !== this.lastreadmessageid && this.type !== 4 && Boolean(this.lastmessageid); } hasPermission(name, member = this.guild.member) { if (member.isAdmin()) { @@ -277,7 +281,7 @@ class Channel { for (const thing of member.roles) { const premission = this.permission_overwrites.get(thing.id); if (premission) { - let perm = premission.getPermission(name); + const perm = premission.getPermission(name); if (perm) { return perm === 1; } @@ -289,7 +293,7 @@ class Channel { return false; } get canMessage() { - if ((0 === this.permission_overwritesar.length) && this.hasPermission("MANAGE_CHANNELS")) { + if ((this.permission_overwritesar.length === 0) && this.hasPermission("MANAGE_CHANNELS")) { const role = this.guild.roles.find(_ => _.name === "@everyone"); if (role) { this.addRoleToPerms(role); @@ -298,7 +302,9 @@ class Channel { return this.hasPermission("SEND_MESSAGES"); } sortchildren() { - this.children.sort((a, b) => { return a.position - b.position; }); + this.children.sort((a, b) => { + return a.position - b.position; + }); } resolveparent(guild) { const parentid = this.parent_id?.id; @@ -313,7 +319,7 @@ class Channel { } calculateReorder() { let position = -1; - let build = []; + const build = []; for (const thing of this.children) { const thisthing = { id: thing.snowflake, position: undefined, parent_id: undefined }; if (thing.position < position) { @@ -348,8 +354,13 @@ class Channel { } div["all"] = this; div.draggable = admin; - div.addEventListener("dragstart", (e) => { Channel.dragged = [this, div]; e.stopImmediatePropagation(); }); - div.addEventListener("dragend", () => { Channel.dragged = []; }); + div.addEventListener("dragstart", e => { + Channel.dragged = [this, div]; + e.stopImmediatePropagation(); + }); + div.addEventListener("dragend", () => { + Channel.dragged = []; + }); if (this.type === 4) { this.sortchildren(); const caps = document.createElement("div"); @@ -383,17 +394,19 @@ class Channel { childrendiv.appendChild(channel.createguildHTML(admin)); } childrendiv.classList.add("channels"); - setTimeout(_ => { childrendiv.style.height = childrendiv.scrollHeight + 'px'; }, 100); + setTimeout(_ => { + childrendiv.style.height = childrendiv.scrollHeight + "px"; + }, 100); decdiv.onclick = function () { - if (childrendiv.style.height !== '0px') { + if (childrendiv.style.height !== "0px") { decoration.classList.add("hiddencat"); //childrendiv.classList.add("colapsediv"); - childrendiv.style.height = '0px'; + childrendiv.style.height = "0px"; } else { decoration.classList.remove("hiddencat"); //childrendiv.classList.remove("colapsediv") - childrendiv.style.height = childrendiv.scrollHeight + 'px'; + childrendiv.style.height = childrendiv.scrollHeight + "px"; } }; div.appendChild(childrendiv); @@ -479,14 +492,14 @@ class Channel { } } coatDropDiv(div, container = false) { - div.addEventListener("dragenter", (event) => { + div.addEventListener("dragenter", event => { console.log("enter"); event.preventDefault(); }); - div.addEventListener("dragover", (event) => { + div.addEventListener("dragover", event => { event.preventDefault(); }); - div.addEventListener("drop", (event) => { + div.addEventListener("drop", event => { const that = Channel.dragged[0]; if (!that) return; @@ -543,8 +556,8 @@ class Channel { method: "POST", headers: this.headers, body: JSON.stringify({ - name: name, - type: type, + name, + type, parent_id: this.snowflake, permission_overwrites: [], }) @@ -558,22 +571,28 @@ class Channel { const thistype = this.type; const full = new Dialog(["hdiv", ["vdiv", - ["textbox", "Channel name:", this.name, function () { name = this.value; }], - ["mdbox", "Channel topic:", this.topic, function () { topic = this.value; }], - ["checkbox", "NSFW Channel", this.nsfw, function () { nsfw = this.checked; }], + ["textbox", "Channel name:", this.name, function () { + name = this.value; + }], + ["mdbox", "Channel topic:", this.topic, function () { + topic = this.value; + }], + ["checkbox", "NSFW Channel", this.nsfw, function () { + nsfw = this.checked; + }], ["button", "", "submit", () => { fetch(this.info.api + "/channels/" + thisid, { method: "PATCH", headers: this.headers, body: JSON.stringify({ - "name": name, - "type": thistype, - "topic": topic, - "bitrate": 64000, - "user_limit": 0, - "nsfw": nsfw, - "flags": 0, - "rate_limit_per_user": 0 + name, + type: thistype, + topic, + bitrate: 64000, + user_limit: 0, + nsfw, + flags: 0, + rate_limit_per_user: 0 }) }); console.log(full); @@ -683,7 +702,7 @@ class Channel { for (let i = 0; i < 15; i++) { const div = document.createElement("div"); div.classList.add("loadingmessage"); - if (Math.random() < .5) { + if (Math.random() < 0.5) { const pfp = document.createElement("div"); pfp.classList.add("loadingpfp"); const username = document.createElement("div"); @@ -704,7 +723,6 @@ class Channel { if (this.allthewayup) { return; } - ; if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { return; } @@ -715,7 +733,7 @@ class Channel { if (response.length !== 100) { this.allthewayup = true; } - let prev = undefined; + let prev; for (const thing of response) { const message = new Message(thing, this); if (prev) { @@ -747,7 +765,9 @@ class Channel { } await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100&after=" + id, { headers: this.headers - }).then((j) => { return j.json(); }).then(response => { + }).then(j => { + return j.json(); + }).then(response => { let previd = id; for (const i in response) { let messager; @@ -769,7 +789,6 @@ class Channel { } //out.buildmessages(); }); - return; } topid; async grabBefore(id) { @@ -778,7 +797,9 @@ class Channel { } await fetch(this.info.api + "/channels/" + this.id + "/messages?before=" + id + "&limit=100", { headers: this.headers - }).then((j) => { return j.json(); }).then((response) => { + }).then(j => { + return j.json(); + }).then((response) => { if (response.length < 100) { this.allthewayup = true; if (response.length === 0) { @@ -801,7 +822,7 @@ class Channel { this.idToPrev.set(previd, messager.id); previd = messager.id; this.messageids.set(messager.snowflake, messager); - if (+i === response.length - 1 && response.length < 100) { + if (Number(i) === response.length - 1 && response.length < 100) { this.topid = previd; } if (willbreak) { @@ -809,7 +830,6 @@ class Channel { } } }); - return; } /** * Please dont use this, its not implemented. @@ -900,7 +920,7 @@ class Channel { while (flake && time < flaketime) { flake = this.idToPrev.get(flake); if (!flake) { - return undefined; + return; } flaketime = Number((BigInt(flake) >> 22n) + 1420070400000n); } @@ -919,7 +939,6 @@ class Channel { if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") { continue; } - ; this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny)); const permisions = this.permission_overwrites.get(thing.id); if (permisions) { @@ -930,10 +949,10 @@ class Channel { this.nsfw = json.nsfw; } typingstart() { - if (this.typing > new Date().getTime()) { + if (this.typing > Date.now()) { return; } - this.typing = new Date().getTime() + 6000; + this.typing = Date.now() + 6000; fetch(this.info.api + "/channels/" + this.snowflake + "/typing", { method: "POST", headers: this.headers @@ -941,11 +960,11 @@ class Channel { } get notification() { let notinumber = this.message_notifications; - if (+notinumber === 3) { + if (Number(notinumber) === 3) { notinumber = null; } notinumber ??= this.guild.message_notifications; - switch (+notinumber) { + switch (Number(notinumber)) { case 0: return "all"; case 1: @@ -961,15 +980,14 @@ class Channel { if (replyingto) { replyjson = { - "guild_id": replyingto.guild.id, - "channel_id": replyingto.channel.id, - "message_id": replyingto.id, + guild_id: replyingto.guild.id, + channel_id: replyingto.channel.id, + message_id: replyingto.id, }; } - ; if (attachments.length === 0) { const body = { - content: content, + content, nonce: Math.floor(Math.random() * 1000000000), message_reference: undefined }; @@ -985,21 +1003,21 @@ class Channel { else { const formData = new FormData(); const body = { - content: content, + content, nonce: Math.floor(Math.random() * 1000000000), message_reference: undefined }; if (replyjson) { body.message_reference = replyjson; } - formData.append('payload_json', JSON.stringify(body)); + formData.append("payload_json", JSON.stringify(body)); for (const i in attachments) { formData.append("files[" + i + "]", attachments[i]); } return await fetch(this.info.api + "/channels/" + this.snowflake + "/messages", { - method: 'POST', + method: "POST", body: formData, - headers: { "Authorization": this.headers.Authorization } + headers: { Authorization: this.headers.Authorization } }); } } @@ -1084,7 +1102,6 @@ class Channel { if (deep === 3) { return; } - ; this.notify(message, deep + 1); }); } @@ -1115,7 +1132,7 @@ class Channel { body: JSON.stringify({ allow: permission.allow.toString(), deny: permission.deny.toString(), - id: id, + id, type: 0 }) }); diff --git a/.dist/contextmenu.js b/.dist/contextmenu.js index 1fd5b6d..0095626 100644 --- a/.dist/contextmenu.js +++ b/.dist/contextmenu.js @@ -5,7 +5,7 @@ class Contextmenu { div; static setup() { Contextmenu.currentmenu = ""; - document.addEventListener('click', function (event) { + document.addEventListener("click", event => { if (Contextmenu.currentmenu == "") { return; } @@ -50,8 +50,8 @@ class Contextmenu { if (Contextmenu.currentmenu != "") { Contextmenu.currentmenu.remove(); } - div.style.top = y + 'px'; - div.style.left = x + 'px'; + div.style.top = y + "px"; + div.style.left = x + "px"; document.body.appendChild(div); Contextmenu.keepOnScreen(div); console.log(div); @@ -59,7 +59,7 @@ class Contextmenu { return this.div; } bindContextmenu(obj, addinfo, other) { - const func = (event) => { + const func = event => { event.preventDefault(); event.stopImmediatePropagation(); this.makemenu(event.clientX, event.clientY, addinfo, other); @@ -75,12 +75,12 @@ class Contextmenu { console.log(box, docheight, docwidth); if (box.right > docwidth) { console.log("test"); - obj.style.left = docwidth - box.width + 'px'; + obj.style.left = docwidth - box.width + "px"; } if (box.bottom > docheight) { - obj.style.top = docheight - box.height + 'px'; + obj.style.top = docheight - box.height + "px"; } } } Contextmenu.setup(); -export { Contextmenu as Contextmenu }; +export { Contextmenu }; diff --git a/.dist/dialog.js b/.dist/dialog.js index 120e90c..1a8c2c9 100644 --- a/.dist/dialog.js +++ b/.dist/dialog.js @@ -58,7 +58,7 @@ class Dialog { case "checkbox": { const div = document.createElement("div"); - const checkbox = document.createElement('input'); + const checkbox = document.createElement("input"); div.appendChild(checkbox); const label = document.createElement("span"); checkbox.checked = array[2]; @@ -71,7 +71,7 @@ class Dialog { case "button": { const div = document.createElement("div"); - const input = document.createElement('button'); + const input = document.createElement("button"); const label = document.createElement("span"); input.textContent = array[2]; label.textContent = array[1]; @@ -134,7 +134,7 @@ class Dialog { case "radio": { const div = document.createElement("div"); const fieldset = document.createElement("fieldset"); - fieldset.addEventListener("change", function () { + fieldset.addEventListener("change", () => { let i = -1; for (const thing of fieldset.children) { i++; @@ -173,9 +173,8 @@ class Dialog { div.appendChild(fieldset); return div; } - case "html": { + case "html": return array[1]; - } case "select": { const div = document.createElement("div"); const label = document.createElement("label"); @@ -227,7 +226,6 @@ class Dialog { } default: console.error("can't find element:" + array[0], " full element:" + array); - return; } } show() { @@ -237,7 +235,9 @@ class Dialog { this.background.classList.add("background"); document.body.appendChild(this.background); document.body.appendChild(this.html); - this.background.onclick = _ => { this.hide(); }; + this.background.onclick = _ => { + this.hide(); + }; } hide() { document.body.removeChild(this.background); diff --git a/.dist/direct.js b/.dist/direct.js index 3ba97c7..c6bf18c 100644 --- a/.dist/direct.js +++ b/.dist/direct.js @@ -96,7 +96,6 @@ class Group extends Channel { this.messageids = new Map(); this.permission_overwrites = new Map(); this.lastmessageid = json.last_message_id; - this.lastmessageid ??= null; this.mentions = 0; this.setUpInfiniteScroller(); if (this.lastmessageid) { @@ -140,8 +139,8 @@ class Group extends Channel { const messagez = new Message(messagep.d, this); if (this.lastmessageid) { this.idToNext.set(this.lastmessageid, messagez.id); + this.idToPrev.set(messagez.id, this.lastmessageid); } - this.idToPrev.set(messagez.id, this.lastmessageid); this.lastmessageid = messagez.id; this.messageids.set(messagez.snowflake, messagez); if (messagez.author === this.localuser.user) { diff --git a/.dist/embed.js b/.dist/embed.js index a531077..66e0f2b 100644 --- a/.dist/embed.js +++ b/.dist/embed.js @@ -119,7 +119,6 @@ class Embed { if (this.json?.timestamp) { const span = document.createElement("span"); span.textContent = new Date(this.json.timestamp).toLocaleString(); - ; footer.append(span); } embed.append(footer); diff --git a/.dist/emoji.js b/.dist/emoji.js index c31e10c..55d61b0 100644 --- a/.dist/emoji.js +++ b/.dist/emoji.js @@ -62,10 +62,8 @@ class Emoji { for (let i = 0; i < length; i++) { array[i] = read8(); } - const decoded = new TextDecoder("utf-8").decode(array.buffer); - ; //console.log(array); - return decoded; + return new TextDecoder("utf-8").decode(array.buffer); } const build = []; let cats = read16(); @@ -78,7 +76,7 @@ class Emoji { const name = readString8(); const len = read8(); const skin_tone_support = len > 127; - const emoji = readStringNo(len - (+skin_tone_support * 128)); + const emoji = readStringNo(len - (Number(skin_tone_support) * 128)); emojis.push({ name, skin_tone_support, @@ -102,7 +100,9 @@ class Emoji { } static async emojiPicker(x, y, localuser) { let res; - const promise = new Promise((r) => { res = r; }); + const promise = new Promise(r => { + res = r; + }); const menu = document.createElement("div"); menu.classList.add("flextttb", "emojiPicker"); menu.style.top = y + "px"; diff --git a/.dist/file.js b/.dist/file.js index fefacb8..b70f062 100644 --- a/.dist/file.js +++ b/.dist/file.js @@ -31,7 +31,7 @@ class File { this.width /= scale; this.height /= scale; } - if (this.content_type.startsWith('image/')) { + if (this.content_type.startsWith("image/")) { const div = document.createElement("div"); const img = document.createElement("img"); img.classList.add("messageimg"); @@ -50,7 +50,7 @@ class File { console.log(this.width, this.height); return div; } - else if (this.content_type.startsWith('video/')) { + else if (this.content_type.startsWith("video/")) { const video = document.createElement("video"); const source = document.createElement("source"); source.src = src; @@ -63,7 +63,7 @@ class File { } return video; } - else if (this.content_type.startsWith('audio/')) { + else if (this.content_type.startsWith("audio/")) { const audio = document.createElement("audio"); const source = document.createElement("source"); source.src = src; @@ -138,8 +138,8 @@ class File { return div; } static filesizehuman(fsize) { - var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); - return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; + const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i]; } } export { File }; diff --git a/.dist/guild.js b/.dist/guild.js index 56453c0..19f744b 100644 --- a/.dist/guild.js +++ b/.dist/guild.js @@ -149,7 +149,7 @@ class Guild { method: "PATCH", headers: this.headers, body: JSON.stringify({ - "message_notifications": noti + message_notifications: noti }) }); this.message_notifications = noti; @@ -202,7 +202,7 @@ class Guild { } calculateReorder() { let position = -1; - let build = []; + const build = []; for (const thing of this.headchannels) { const thisthing = { id: thing.snowflake, position: undefined, parent_id: undefined }; if (thing.position <= position) { @@ -256,7 +256,9 @@ class Guild { return this.owner.info; } sortchannels() { - this.headchannels.sort((a, b) => { return a.position - b.position; }); + this.headchannels.sort((a, b) => { + return a.position - b.position; + }); } generateGuildIcon() { const divy = document.createElement("div"); @@ -279,7 +281,7 @@ class Guild { } else { const div = document.createElement("div"); - let build = this.properties.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, ""); + const build = this.properties.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, ""); div.textContent = build; div.classList.add("blankserver", "servericon"); divy.appendChild(div); @@ -336,7 +338,7 @@ class Guild { headers: this.headers, }); } - unreads(html = undefined) { + unreads(html) { if (html) { this.html = html; } @@ -399,7 +401,7 @@ class Guild { } return this.member.hasRole(r); } - loadChannel(ID = undefined) { + loadChannel(ID) { if (ID && this.channelids[ID]) { this.channelids[ID].getHTML(); return; @@ -451,7 +453,7 @@ class Guild { ["voice", "text", "announcement"], function (e) { console.log(e); - category = { "text": 0, "voice": 2, "announcement": 5, "category": 4 }[e]; + category = { text: 0, voice: 2, announcement: 5, category: 4 }[e]; }, 1 ], @@ -463,12 +465,12 @@ class Guild { console.log(name, category); func(name, category); channelselect.hide(); - }.bind(this)]]); + }]]); channelselect.show(); } createcategory() { let name = ""; - let category = 4; + const category = 4; const channelselect = new Dialog(["vdiv", ["textbox", "Name of category", "", function () { console.log(this); @@ -510,7 +512,7 @@ class Guild { fetch(this.info.api + "/guilds/" + this.snowflake + "/channels", { method: "POST", headers: this.headers, - body: JSON.stringify({ name: name, type: type }) + body: JSON.stringify({ name, type }) }); } async createRole(name) { @@ -518,7 +520,7 @@ class Guild { method: "POST", headers: this.headers, body: JSON.stringify({ - name: name, + name, color: 0, permissions: "0" }) diff --git a/.dist/home.js b/.dist/home.js index b54844f..7161449 100644 --- a/.dist/home.js +++ b/.dist/home.js @@ -33,7 +33,7 @@ fetch("/instances.json").then(_ => _.json()).then((json) => { if (instance.descriptionLong) { p.innerText = instance.descriptionLong; } - else { + else if (instance.description) { p.innerText = instance.description; } textbox.append(p); diff --git a/.dist/index.js b/.dist/index.js index dd8f41a..f87945c 100644 --- a/.dist/index.js +++ b/.dist/index.js @@ -6,8 +6,10 @@ import { File } from "./file.js"; (async () => { async function waitforload() { let res; - new Promise(r => { res = r; }); - document.addEventListener("DOMContentLoaded", function () { + new Promise(r => { + res = r; + }); + document.addEventListener("DOMContentLoaded", () => { res(); }); await res; @@ -15,7 +17,7 @@ import { File } from "./file.js"; await waitforload(); const users = getBulkUsers(); if (!users.currentuser) { - window.location.href = '/login.html'; + window.location.href = "/login.html"; } function showAccountSwitcher() { const table = document.createElement("div"); @@ -45,7 +47,7 @@ import { File } from "./file.js"; loading.classList.remove("doneloading"); loading.classList.add("loading"); thisuser = new Localuser(specialuser); - users["currentuser"] = specialuser.uid; + users.currentuser = specialuser.uid; localStorage.setItem("userinfos", JSON.stringify(users)); thisuser.initwebsocket().then(_ => { thisuser.loaduser(); @@ -107,16 +109,20 @@ import { File } from "./file.js"; } { const menu = new Contextmenu("create rightclick"); //Really should go into the localuser class, but that's a later thing - menu.addbutton("Create channel", function () { + menu.addbutton("Create channel", () => { if (thisuser.lookingguild) { thisuser.lookingguild.createchannels(); } - }, null, _ => { return thisuser.isAdmin(); }); - menu.addbutton("Create category", function () { + }, null, _ => { + return thisuser.isAdmin(); + }); + menu.addbutton("Create category", () => { if (thisuser.lookingguild) { thisuser.lookingguild.createcategory(); } - }, null, _ => { return thisuser.isAdmin(); }); + }, null, _ => { + return thisuser.isAdmin(); + }); menu.bindContextmenu(document.getElementById("channels"), 0, 0); } const pasteimage = document.getElementById("pasteimage"); @@ -134,7 +140,7 @@ import { File } from "./file.js"; } else { replyingto = thisuser.channelfocus.replyingto; - let replying = replyingto; + const replying = replyingto; if (replyingto?.div) { replyingto.div.classList.remove("replying"); } @@ -151,7 +157,6 @@ import { File } from "./file.js"; pasteimage.removeChild(imageshtml.pop()); } typebox.innerHTML = ""; - return; } } const typebox = document.getElementById("typebox"); @@ -174,7 +179,7 @@ import { File } from "./file.js"; */ const images = []; const imageshtml = []; - document.addEventListener('paste', async (e) => { + document.addEventListener("paste", async (e) => { if (!e.clipboardData) return; Array.from(e.clipboardData.files).forEach(async (f) => { diff --git a/.dist/infiniteScroller.js b/.dist/infiniteScroller.js index 5526f3c..91410b1 100644 --- a/.dist/infiniteScroller.js +++ b/.dist/infiniteScroller.js @@ -32,7 +32,7 @@ class InfiniteScroller { this.watchForChange(); }); this.scroll.addEventListener("scroll", _ => { - if (null === this.timeout) { + if (this.timeout === null) { this.timeout = setTimeout(this.updatestuff.bind(this), 300); } this.watchForChange(); @@ -71,10 +71,8 @@ class InfiniteScroller { this.averageheight = 60; } this.scrollTop = this.scroll.scrollTop; - if (!this.scrollBottom) { - if (!await this.watchForChange()) { - this.reachesBottom(); - } + if (!this.scrollBottom && !await this.watchForChange()) { + this.reachesBottom(); } if (!this.scrollTop) { await this.watchForChange(); @@ -129,7 +127,6 @@ class InfiniteScroller { this.HTMLElements.unshift([html, nextid]); this.scrollTop += this.averageheight; } - ; } if (this.scrollTop > this.maxDist) { const html = this.HTMLElements.shift(); @@ -176,7 +173,6 @@ class InfiniteScroller { this.HTMLElements.push([html, nextid]); this.scrollBottom += this.averageheight; } - ; } if (scrollBottom > this.maxDist) { const html = this.HTMLElements.pop(); @@ -225,14 +221,14 @@ class InfiniteScroller { } const out = await Promise.allSettled([this.watchForTop(), this.watchForBottom()]); const changed = (out[0].value || out[1].value); - if (null === this.timeout && changed) { + if (this.timeout === null && changed) { this.timeout = setTimeout(this.updatestuff.bind(this), 300); } if (!this.currrunning) { console.error("something really bad happened"); } - res(!!changed); - return !!changed; + res(Boolean(changed)); + return Boolean(changed); } catch (e) { console.error(e); diff --git a/.dist/invite.js b/.dist/invite.js index 62d511a..91001ed 100644 --- a/.dist/invite.js +++ b/.dist/invite.js @@ -11,7 +11,7 @@ import { getBulkUsers, getapiurls } from "./login.js"; console.log(users.users[thing]); } let urls; - if (!joinable.length) { + if (!joinable.length && well) { const out = await getapiurls(well); if (out) { urls = out; @@ -24,7 +24,7 @@ import { getBulkUsers, getapiurls } from "./login.js"; } } else { - throw Error("someone needs to handle the case where the servers don't exist"); + throw new Error("someone needs to handle the case where the servers don't exist"); } } else { @@ -86,7 +86,7 @@ import { getBulkUsers, getapiurls } from "./login.js"; Authorization: thing.token } }).then(_ => { - users["currentuser"] = specialuser.uid; + users.currentuser = specialuser.uid; localStorage.setItem("userinfos", JSON.stringify(users)); window.location.href = "/channels/" + guildinfo.id; }); diff --git a/.dist/login.js b/.dist/login.js index 5ff4e0b..2a18c73 100644 --- a/.dist/login.js +++ b/.dist/login.js @@ -22,13 +22,13 @@ function trimswitcher() { for (const thing in json.users) { const user = json.users[thing]; let wellknown = user.serverurls.wellknown; - if (wellknown[wellknown.length - 1] !== "/") { + if (wellknown.at(-1) !== "/") { wellknown += "/"; } wellknown += user.username; if (map.has(wellknown)) { const otheruser = map.get(wellknown); - if (otheruser[1].serverurls.wellknown[otheruser[1].serverurls.wellknown.length - 1] === "/") { + if (otheruser[1].serverurls.wellknown.at(-1) === "/") { delete json.users[otheruser[0]]; map.set(wellknown, [thing, user]); } @@ -41,7 +41,7 @@ function trimswitcher() { } } for (const thing in json.users) { - if (thing[thing.length - 1] === "/") { + if (thing.at(-1) === "/") { const user = json.users[thing]; delete json.users[thing]; json.users[thing.slice(0, -1)] = user; @@ -73,7 +73,7 @@ function setDefaults() { if (userinfos.accent_color === undefined) { userinfos.accent_color = "#242443"; } - document.documentElement.style.setProperty('--accent-color', userinfos.accent_color); + document.documentElement.style.setProperty("--accent-color", userinfos.accent_color); if (userinfos.preferences === undefined) { userinfos.preferences = { theme: "Dark", @@ -103,11 +103,8 @@ class Specialuser { 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; @@ -178,12 +175,12 @@ async function getapiurls(str) { } } } - if (str[str.length - 1] !== "/") { + if (str.at(-1) !== "/") { str += "/"; } let api; try { - const info = await fetch(`${str}/.well-known/spacebar`).then((x) => x.json()); + const info = await fetch(`${str}/.well-known/spacebar`).then(x => x.json()); api = info.api; } catch { @@ -191,7 +188,7 @@ async function getapiurls(str) { } const url = new URL(api); try { - const info = await fetch(`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`).then((x) => x.json()); + const info = await fetch(`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`).then(x => x.json()); return { api: info.apiEndpoint, gateway: info.gateway, @@ -226,8 +223,8 @@ async function checkInstance(e) { instanceinfo.value = instancein.value; localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); verify.textContent = "Instance is all good"; - if (checkInstance["alt"]) { - checkInstance["alt"](); + if (checkInstance.alt) { + checkInstance.alt(); } setTimeout(_ => { console.log(verify.textContent); @@ -238,7 +235,7 @@ async function checkInstance(e) { verify.textContent = "Invalid Instance, try again"; } } - catch (e) { + catch { console.log("catch"); verify.textContent = "Invalid Instance, try again"; } @@ -271,10 +268,10 @@ async function login(username, password, captcha) { const options = { method: "POST", body: JSON.stringify({ - "login": username, - "password": password, - "undelete": false, - "captcha_key": captcha + login: username, + password, + undelete: false, + captcha_key: captcha }), headers: { "Content-type": "application/json; charset=UTF-8", @@ -283,10 +280,10 @@ async function login(username, password, captcha) { try { const info = JSON.parse(localStorage.getItem("instanceinfo")); const api = info.login + (info.login.startsWith("/") ? "/" : ""); - return await fetch(api + '/auth/login', options).then(response => response.json()) - .then((response) => { + return await fetch(api + "/auth/login", options).then(response => response.json()) + .then(response => { console.log(response, response.message); - if ("Invalid Form Body" === response.message) { + if (response.message === "Invalid Form Body") { return response.errors.login._errors[0].message; console.log("test"); } @@ -306,13 +303,14 @@ async function login(username, password, captcha) { else { eval("hcaptcha.reset()"); } - return; } else { console.log(response); if (response.ticket) { let onetimecode = ""; - new Dialog(["vdiv", ["title", "2FA code:"], ["textbox", "", "", function () { onetimecode = this.value; }], ["button", "", "Submit", function () { + new Dialog(["vdiv", ["title", "2FA code:"], ["textbox", "", "", function () { + onetimecode = this.value; + }], ["button", "", "Submit", function () { fetch(api + "/auth/mfa/totp", { method: "POST", headers: { @@ -336,7 +334,7 @@ async function login(username, password, captcha) { window.location.href = redir; } else { - window.location.href = '/channels/@me'; + window.location.href = "/channels/@me"; } } }); @@ -352,7 +350,7 @@ async function login(username, password, captcha) { window.location.href = redir; } else { - window.location.href = '/channels/@me'; + window.location.href = "/channels/@me"; } return ""; } @@ -360,13 +358,12 @@ async function login(username, password, captcha) { }); } catch (error) { - console.error('Error:', error); + console.error("Error:", error); } - ; } async function check(e) { e.preventDefault(); - let h = await login(e.srcElement[1].value, e.srcElement[2].value, e.srcElement[3].value); + const h = await login(e.srcElement[1].value, e.srcElement[2].value, e.srcElement[3].value); document.getElementById("wrong").textContent = h; console.log(h); } diff --git a/.dist/markdown.js b/.dist/markdown.js index cf2eccc..f2d6da1 100644 --- a/.dist/markdown.js +++ b/.dist/markdown.js @@ -28,7 +28,7 @@ class MarkDown { return this.makeHTML().textContent; } makeHTML({ keep = this.keep, stdsize = this.stdsize } = {}) { - return this.markdown(this.txt, { keep: keep, stdsize: stdsize }); + return this.markdown(this.txt, { keep, stdsize }); } markdown(text, { keep = false, stdsize = false } = {}) { let txt; @@ -105,7 +105,7 @@ class MarkDown { if (keep) { element.append(keepys); } - element.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + element.appendChild(this.markdown(build, { keep, stdsize })); span.append(element); } finally { @@ -179,7 +179,7 @@ class MarkDown { } else { const pre = document.createElement("pre"); - if (build[build.length - 1] === "\n") { + if (build.at(-1) === "\n") { build = build.substring(0, build.length - 1); } if (txt[i] === "\n") { @@ -224,7 +224,7 @@ class MarkDown { if (keep) { i.append(stars); } - i.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + i.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { i.append(stars); } @@ -235,7 +235,7 @@ class MarkDown { if (keep) { b.append(stars); } - b.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + b.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { b.append(stars); } @@ -247,7 +247,7 @@ class MarkDown { if (keep) { b.append(stars); } - b.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + b.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { b.append(stars); } @@ -290,7 +290,7 @@ class MarkDown { if (keep) { i.append(underscores); } - i.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + i.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { i.append(underscores); } @@ -301,7 +301,7 @@ class MarkDown { if (keep) { u.append(underscores); } - u.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + u.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { u.append(underscores); } @@ -313,7 +313,7 @@ class MarkDown { if (keep) { i.append(underscores); } - i.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + i.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { i.append(underscores); } @@ -325,7 +325,7 @@ class MarkDown { } } if (txt[i] === "~" && txt[i + 1] === "~") { - let count = 2; + const count = 2; let build = []; let find = 0; let j = i + 2; @@ -350,7 +350,7 @@ class MarkDown { if (keep) { s.append(tildes); } - s.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + s.appendChild(this.markdown(build, { keep, stdsize })); if (keep) { s.append(tildes); } @@ -360,7 +360,7 @@ class MarkDown { } } if (txt[i] === "|" && txt[i + 1] === "|") { - let count = 2; + const count = 2; let build = []; let find = 0; let j = i + 2; @@ -385,7 +385,7 @@ class MarkDown { if (keep) { j.append(pipes); } - j.appendChild(this.markdown(build, { keep: keep, stdsize: stdsize })); + j.appendChild(this.markdown(build, { keep, stdsize })); j.classList.add("spoiler"); j.onclick = MarkDown.unspoil; if (keep) { @@ -459,7 +459,7 @@ class MarkDown { i = j; const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); const owner = (this.owner instanceof Channel) ? this.owner.guild : this.owner; - const emoji = new Emoji({ name: buildjoin, id: parts[2], animated: !!parts[1] }, owner); + const emoji = new Emoji({ name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, owner); span.appendChild(emoji.getHTML(isEmojiOnly)); continue; } @@ -479,7 +479,6 @@ class MarkDown { else { break; } - ; } else if (partsFound === 1 && txt[j] === ")") { partsFound++; @@ -529,7 +528,7 @@ class MarkDown { return; console.log(_.clipboardData.types); const data = _.clipboardData.getData("text"); - document.execCommand('insertHTML', false, data); + document.execCommand("insertHTML", false, data); _.preventDefault(); if (!box.onkeyup) return; @@ -568,25 +567,25 @@ class MarkDown { } //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div function saveCaretPosition(context) { - var selection = window.getSelection(); + const selection = window.getSelection(); if (!selection) return; - var range = selection.getRangeAt(0); + const range = selection.getRangeAt(0); range.setStart(context, 0); - var len = range.toString().length; + const len = range.toString().length; return function restore() { if (!selection) return; - var pos = getTextNodeAtPosition(context, len); + const pos = getTextNodeAtPosition(context, len); selection.removeAllRanges(); - var range = new Range(); + const range = new Range(); range.setStart(pos.node, pos.position); selection.addRange(range); }; } function getTextNodeAtPosition(root, index) { const NODE_TYPE = NodeFilter.SHOW_TEXT; - var treeWalker = document.createTreeWalker(root, NODE_TYPE, function next(elem) { + const treeWalker = document.createTreeWalker(root, NODE_TYPE, elem => { if (!elem.textContent) return 0; if (index > elem.textContent.length) { @@ -595,7 +594,7 @@ function getTextNodeAtPosition(root, index) { } return NodeFilter.FILTER_ACCEPT; }); - var c = treeWalker.nextNode(); + const c = treeWalker.nextNode(); return { node: c ? c : root, position: index diff --git a/.dist/member.js b/.dist/member.js index 334ab1d..dccde08 100644 --- a/.dist/member.js +++ b/.dist/member.js @@ -28,7 +28,7 @@ class Member { continue; } if (thing === "roles") { - for (const strrole of memberjson["roles"]) { + for (const strrole of memberjson.roles) { const role = SnowFlake.getSnowFlakeFromID(strrole, Role).getObject(); this.roles.push(role); } @@ -38,7 +38,6 @@ class Member { } if (this.localuser.userMap.has(this?.id)) { this.user = this.localuser.userMap.get(this?.id); - return; } } get guild() { @@ -89,7 +88,6 @@ class Member { const membjson = await membpromise; if (membjson === undefined) { res(undefined); - return undefined; } else { const member = new Member(membjson, guild); @@ -149,7 +147,6 @@ class Member { if (!this) { return; } - ; /* if(this.error){ diff --git a/.dist/message.js b/.dist/message.js index 0294ed0..fdd2b35 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -42,7 +42,9 @@ class Message { return this.snowflake.id; } static setup() { - this.del = new Promise(_ => { this.resolve = _; }); + this.del = new Promise(_ => { + this.resolve = _; + }); Message.setupcmenu(); } static setupcmenu() { @@ -63,7 +65,7 @@ class Message { Message.contextmenu.addbutton("Edit", function () { this.channel.editing = this; const markdown = document.getElementById("typebox")["markdown"]; - markdown.txt = this.content.rawString.split(''); + markdown.txt = this.content.rawString.split(""); markdown.boxupdate(document.getElementById("typebox")); }, null, function () { return this.author.id === this.localuser.user.id; @@ -199,7 +201,7 @@ class Message { getimages() { const build = []; for (const thing of this.attachments) { - if (thing.content_type.startsWith('image/')) { + if (thing.content_type.startsWith("image/")) { build.push(thing); } } @@ -209,7 +211,7 @@ class Message { return await fetch(this.info.api + "/channels/" + this.channel.snowflake + "/messages/" + this.id, { method: "PATCH", headers: this.headers, - body: JSON.stringify({ content: content }) + body: JSON.stringify({ content }) }); } delete() { @@ -266,7 +268,7 @@ class Message { this.generateMessage(); } } - generateMessage(premessage = undefined, ignoredblock = false) { + generateMessage(premessage, ignoredblock = false) { if (!this.div) return; if (!premessage) { @@ -277,21 +279,21 @@ class Message { div.classList.add("replying"); } div.innerHTML = ""; - const build = document.createElement('div'); + const build = document.createElement("div"); build.classList.add("flexltr", "message"); div.classList.remove("zeroheight"); if (this.author.relationshipType === 2) { if (ignoredblock) { if (premessage?.author !== this.author) { const span = document.createElement("span"); - span.textContent = `You have this user blocked, click to hide these messages.`; + span.textContent = "You have this user blocked, click to hide these messages."; div.append(span); span.classList.add("blocked"); span.onclick = _ => { const scroll = this.channel.infinite.scrollTop; let next = this; while (next?.author === this.author) { - next.generateMessage(undefined); + next.generateMessage(); next = this.channel.messages.get(this.channel.idToNext.get(next.id)); } if (this.channel.infinite.scroll && scroll) { @@ -375,7 +377,7 @@ class Message { } div.appendChild(build); 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"); let pfpparent, current; if (premessage != null) { @@ -545,7 +547,7 @@ class Message { if (thing.emoji.name === data.name) { thing.count--; if (thing.count === 0) { - this.reactions.splice(+i, 1); + this.reactions.splice(Number(i), 1); this.updateReactions(); return; } @@ -565,16 +567,16 @@ class Message { for (const i in this.reactions) { const reaction = this.reactions[i]; if ((reaction.emoji.id && reaction.emoji.id == emoji.id) || (!reaction.emoji.id && reaction.emoji.name == emoji.name)) { - this.reactions.splice(+i, 1); + this.reactions.splice(Number(i), 1); this.updateReactions(); break; } } } - buildhtml(premessage = undefined) { + buildhtml(premessage) { if (this.div) { console.error(`HTML for ${this.snowflake} already exists, aborting`); - return; + return this.div; } try { const div = document.createElement("div"); @@ -585,15 +587,16 @@ class Message { catch (e) { console.error(e); } + return this.div; } } -let now = new Date().toLocaleDateString(); +const now = new Date().toLocaleDateString(); const yesterday = new Date(now); yesterday.setDate(new Date().getDate() - 1); -let yesterdayStr = yesterday.toLocaleDateString(); +const yesterdayStr = yesterday.toLocaleDateString(); function formatTime(date) { const datestring = date.toLocaleDateString(); - const formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + const formatTime = (date) => date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); if (datestring === now) { return `Today at ${formatTime(date)}`; } diff --git a/.dist/permissions.js b/.dist/permissions.js index 0b166aa..e52a770 100644 --- a/.dist/permissions.js +++ b/.dist/permissions.js @@ -3,12 +3,12 @@ class Permissions { deny; hasDeny; constructor(allow, deny = "") { - this.hasDeny = !!deny; + this.hasDeny = Boolean(deny); try { this.allow = BigInt(allow); this.deny = BigInt(deny); } - catch (e) { + catch { this.allow = 0n; this.deny = 0n; console.error(`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`); diff --git a/.dist/register.js b/.dist/register.js index aa351a8..8f8dc58 100644 --- a/.dist/register.js +++ b/.dist/register.js @@ -17,9 +17,9 @@ async function registertry(e) { await fetch(apiurl + "/auth/register", { body: JSON.stringify({ date_of_birth: dateofbirth, - email: email, - username: username, - password: password, + email, + username, + password, consent: elements[6].checked, captcha_key: elements[7]?.value }), @@ -67,14 +67,14 @@ async function registertry(e) { } } else { - adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: email, token: e.token }).username = username; + adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email, token: e.token }).username = username; localStorage.setItem("token", e.token); const redir = new URLSearchParams(window.location.search).get("goback"); if (redir) { window.location.href = redir; } else { - window.location.href = '/channels/@me'; + window.location.href = "/channels/@me"; } } }); @@ -93,7 +93,9 @@ function error(e, message) { } else { element.classList.remove("suberror"); - setTimeout(_ => { element.classList.add("suberror"); }, 100); + setTimeout(_ => { + element.classList.add("suberror"); + }, 100); } element.textContent = message; } diff --git a/.dist/role.js b/.dist/role.js index f8cf996..0d9053f 100644 --- a/.dist/role.js +++ b/.dist/role.js @@ -38,7 +38,6 @@ class Role { if (this.color === 0) { return null; } - ; return `#${this.color.toString(16)}`; } } @@ -55,7 +54,6 @@ class PermissionToggle { this.owner = owner; } watchForChange() { } - ; generateHTML() { const div = document.createElement("div"); div.classList.add("setting"); @@ -80,7 +78,6 @@ class PermissionToggle { if (state === 1) { on.checked = true; } - ; on.onclick = _ => { this.permissions.setPermission(this.rolejson.name, 1); this.owner.changed(); @@ -92,7 +89,6 @@ class PermissionToggle { if (state === 0) { no.checked = true; } - ; no.onclick = _ => { this.permissions.setPermission(this.rolejson.name, 0); this.owner.changed(); @@ -105,7 +101,6 @@ class PermissionToggle { if (state === -1) { off.checked = true; } - ; off.onclick = _ => { this.permissions.setPermission(this.rolejson.name, -1); this.owner.changed(); diff --git a/.dist/service.js b/.dist/service.js index 789db17..335d72a 100644 --- a/.dist/service.js +++ b/.dist/service.js @@ -4,7 +4,7 @@ function deleteoldcache() { } async function putInCache(request, response) { console.log(request, response); - const cache = await caches.open('cache'); + const cache = await caches.open("cache"); console.log("Grabbed"); try { console.log(await cache.put(request, response)); @@ -13,7 +13,6 @@ async function putInCache(request, response) { console.error(error); } } -; console.log("test"); let lastcache; self.addEventListener("activate", async (event) => { @@ -37,7 +36,9 @@ async function checkCache() { putInCache("/getupdates", data.clone()); } checkedrecently = true; - setTimeout(_ => { checkedrecently = false; }, 1000 * 60 * 30); + setTimeout(_ => { + checkedrecently = false; + }, 1000 * 60 * 30); }); } var checkedrecently = false; @@ -83,7 +84,7 @@ async function getfile(event) { console.error(e); } } -self.addEventListener('fetch', (event) => { +self.addEventListener("fetch", (event) => { try { event.respondWith(getfile(event)); } diff --git a/.dist/settings.js b/.dist/settings.js index ed36a36..82fade4 100644 --- a/.dist/settings.js +++ b/.dist/settings.js @@ -9,7 +9,7 @@ class Buttons { this.buttons = []; this.name = name; } - add(name, thing = undefined) { + add(name, thing) { if (!thing) { thing = new Options(name, this); } @@ -380,7 +380,6 @@ class FileInput { if (this.onchange) { this.onchange(null); } - ; this.value = null; this.owner.changed(); }; @@ -424,7 +423,6 @@ class HtmlArea { } } watchForChange() { } - ; } class Options { name; @@ -451,7 +449,6 @@ class Options { } } watchForChange() { } - ; addOptions(name, { ltr = false } = {}) { const options = new Options(name, this, { ltr }); this.options.push(options); @@ -467,7 +464,7 @@ class Options { this.generateContainter(); } else { - throw Error("Tried to make a subOptions when the options weren't rendered"); + throw new Error("Tried to make a subOptions when the options weren't rendered"); } return options; } @@ -479,7 +476,7 @@ class Options { this.generateContainter(); } else { - throw Error("Tried to make a subForm when the options weren't rendered"); + throw new Error("Tried to make a subForm when the options weren't rendered"); } return options; } @@ -758,7 +755,6 @@ class Form { watchForChange(func) { this.onSubmit = func; } - ; changed() { if (this.traditionalSubmit) { this.owner.changed(); @@ -819,7 +815,6 @@ class Form { if (!(errors instanceof Object)) { return; } - ; for (const error of Object.keys(errors)) { const elm = this.names.get(error); if (elm) { @@ -858,7 +853,9 @@ class Form { } else { element.classList.remove("suberror"); - setTimeout(_ => { element.classList.add("suberror"); }, 100); + setTimeout(_ => { + element.classList.add("suberror"); + }, 100); } element.textContent = message; } @@ -887,7 +884,9 @@ class Settings extends Buttons { exit.textContent = "✖"; exit.classList.add("exitsettings"); background.append(exit); - exit.onclick = _ => { this.hide(); }; + exit.onclick = _ => { + this.hide(); + }; document.body.append(background); this.html = background; } diff --git a/.dist/user.js b/.dist/user.js index c407678..1527b15 100644 --- a/.dist/user.js +++ b/.dist/user.js @@ -72,7 +72,7 @@ class User { }); this.contextmenu.addbutton("Message user", function () { fetch(this.info.api + "/users/@me/channels", { method: "POST", - body: JSON.stringify({ "recipients": [this.id] }), + body: JSON.stringify({ recipients: [this.id] }), headers: this.localuser.headers }); }); @@ -97,7 +97,7 @@ class User { }); this.contextmenu.addbutton("Kick member", function (member) { member.kick(); - }, null, function (member) { + }, null, member => { if (!member) return false; const us = member.guild.member; @@ -111,7 +111,7 @@ class User { }); this.contextmenu.addbutton("Ban member", function (member) { member.ban(); - }, null, function (member) { + }, null, member => { if (!member) return false; const us = member.guild.member; @@ -192,7 +192,7 @@ class User { } } buildpfp() { - const pfp = document.createElement('img'); + const pfp = document.createElement("img"); pfp.loading = "lazy"; pfp.src = this.getpfpsrc(); pfp.classList.add("pfp"); @@ -419,7 +419,7 @@ class User { } return div; } - profileclick(obj, guild = undefined) { + profileclick(obj, guild) { obj.onclick = e => { this.buildprofile(e.clientX, e.clientY, guild); e.stopPropagation(); diff --git a/emoji-packer.js b/emoji-packer.js index 2c0845f..46be3c7 100644 --- a/emoji-packer.js +++ b/emoji-packer.js @@ -5,111 +5,109 @@ const buffer=new ArrayBuffer(2**26); const view = new DataView(buffer, 0); let i=0; function write16(numb){ - view.setUint16(i,numb); - i+=2; + view.setUint16(i,numb); + i+=2; } function write8(numb){ - view.setUint8(i,numb); - i+=1; + view.setUint8(i,numb); + i+=1; } function writeString8(str){ - const encode=new TextEncoder("utf-8").encode(str); - write8(encode.length); - for(const thing of encode){ - write8(thing); - } + const encode=new TextEncoder("utf-8").encode(str); + write8(encode.length); + for(const thing of encode){ + write8(thing); + } } function writeString16(str){ - const encode=new TextEncoder("utf-8").encode(str); - write16(encode.length); - for(const thing of encode){ - write8(thing); - } + const encode=new TextEncoder("utf-8").encode(str); + write16(encode.length); + for(const thing of encode){ + write8(thing); + } } function writeStringNo(str){ - const encode=new TextEncoder("utf-8").encode(str); - for(const thing of encode){ - write8(thing); - } + const encode=new TextEncoder("utf-8").encode(str); + for(const thing of encode){ + write8(thing); + } } write16(emojilist.length); for(const thing of emojilist){ - writeString16(thing.name); - write16(thing.emojis.length); - for(const emoji of thing.emojis){ - writeString8(emoji.name); - write8(new TextEncoder("utf-8").encode(emoji.emoji).length+128*emoji.skin_tone_support); - writeStringNo(emoji.emoji); - } + writeString16(thing.name); + write16(thing.emojis.length); + for(const emoji of thing.emojis){ + writeString8(emoji.name); + write8(new TextEncoder("utf-8").encode(emoji.emoji).length+128*emoji.skin_tone_support); + writeStringNo(emoji.emoji); + } } const out=new ArrayBuffer(i); const ar=new Uint8Array(out); -const br=new Uint8Array(buffer) +const br=new Uint8Array(buffer); for(const thing in ar){ - ar[thing]=br[thing]; + ar[thing]=br[thing]; } console.log(i,ar); function decodeEmojiList(buffer){ - const view = new DataView(buffer, 0); - let i=0; - function read16(){ - const int=view.getUint16(i); - i+=2; - return int; - } - function read8(){ - const int=view.getUint8(i); - i+=1; - return int; - } - function readString8(){ - return readStringNo(read8()); - } - function readString16(){ - return readStringNo(read16()); - } - function readStringNo(length){ - const array=new Uint8Array(length); + const view = new DataView(buffer, 0); + let i=0; + function read16(){ + const int=view.getUint16(i); + i+=2; + return int; + } + function read8(){ + const int=view.getUint8(i); + i+=1; + return int; + } + function readString8(){ + return readStringNo(read8()); + } + function readString16(){ + return readStringNo(read16()); + } + function readStringNo(length){ + const array=new Uint8Array(length); - for(let i=0;i127; - const emoji=readStringNo(len-skin_tone_support*128); - emojis.push({ - name, - skin_tone_support, - emoji - }) - } - build.push({ - name, - emojis - }) - } - return build; + for(;cats!==0;cats--){ + const name=readString16(); + const emojis=[]; + let emojinumber=read16(); + for(;emojinumber!==0;emojinumber--){ + //console.log(emojis); + const name=readString8(); + const len=read8(); + const skin_tone_support=len>127; + const emoji=readStringNo(len-skin_tone_support*128); + emojis.push({ + name, + skin_tone_support, + emoji + }); + } + build.push({ + name, + emojis + }); + } + return build; } console.log(JSON.stringify(decodeEmojiList(out))); -const fs = require('fs'); +const fs = require("node:fs"); fs.writeFile("./webpage/emoji.bin",new Uint8Array(out),_=>{ }); diff --git a/eslint.config.js b/eslint.config.js index fb92f96..43a3d0b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,10 +10,10 @@ const tsParser = require("@typescript-eslint/parser"); const linterOptions = { reportUnusedDisableDirectives: "error" -} +}; const global = { ...globals.browser -} +}; const rules = { "array-callback-return": 2, @@ -227,7 +227,7 @@ const rules = { "sonarjs/prefer-while": 2, "sonarjs/no-gratuitous-expressions": 2, "sonarjs/no-duplicated-branches": 2 -} +}; module.exports = [ { @@ -307,4 +307,4 @@ module.exports = [ "@html-eslint/require-img-alt": 1 } } -] +]; diff --git a/index.js b/index.js index b5ddf81..737a1d5 100755 --- a/index.js +++ b/index.js @@ -1,119 +1,118 @@ #! /usr/bin/env node -const compression = require('compression') +const compression = require("compression"); -const express = require('express'); -const fs = require('fs'); +const express = require("express"); +const fs = require("node:fs"); const app = express(); const instances=require("./webpage/instances.json"); const stats=require("./stats.js"); const instancenames=new Map(); for(const instance of instances){ - instancenames.set(instance.name,instance); + instancenames.set(instance.name,instance); } -app.use(compression()) +app.use(compression()); fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json").then(_=>_.json()).then(json=>{ - for(const instance of json){ - if(!instancenames.has(instance.name)){ - instances.push(instance); - }else{ - const ofinst=instancenames.get(instance.name) - for(const key of Object.keys(instance)){ - if(!ofinst[key]){ - ofinst[key]=instance[key]; - } - } - } - } - stats.observe(instances) -}) - -app.use("/getupdates",(req, res) => { - const out=fs.statSync(`${__dirname}/webpage`); - res.send(out.mtimeMs+""); + for(const instance of json){ + if(!instancenames.has(instance.name)){ + instances.push(instance); + }else{ + const ofinst=instancenames.get(instance.name); + for(const key of Object.keys(instance)){ + if(!ofinst[key]){ + ofinst[key]=instance[key]; + } + } + } + } + stats.observe(instances); }); -let debugging=true;//Do not turn this off, the service worker is all kinds of jank as is, it'll really mess your day up if you disable this + +app.use("/getupdates",(req, res)=>{ + const out=fs.statSync(`${__dirname}/webpage`); + res.send(out.mtimeMs+""); +}); +const debugging=true;//Do not turn this off, the service worker is all kinds of jank as is, it'll really mess your day up if you disable this function isembed(str){ - return str.includes("discord")||str.includes("Spacebar"); + return str.includes("discord")||str.includes("Spacebar"); } async function getapiurls(str){ - if(str[str.length-1]!=="/"){ - str+="/" - } - let api; - try{ - const info=await fetch(`${str}/.well-known/spacebar`).then((x) => x.json()); - api=info.api; - }catch{ - return false - } - const url = new URL(api); - try{ - - const info=await fetch(`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`).then((x) => x.json()); - return { - api: info.apiEndpoint, - gateway: info.gateway, - cdn: info.cdn, - wellknown: str, - }; - }catch{ - return false; - } + if(str.at(-1)!=="/"){ + str+="/"; + } + let api; + try{ + const info=await fetch(`${str}/.well-known/spacebar`).then(x=>x.json()); + api=info.api; + }catch{ + return false; + } + const url = new URL(api); + try{ + const info=await fetch(`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`).then(x=>x.json()); + return{ + api: info.apiEndpoint, + gateway: info.gateway, + cdn: info.cdn, + wellknown: str, + }; + }catch{ + return false; + } } async function inviteres(req,res){ - let url; - if(URL.canParse(req.query.url)){ - url=new URL(req.query.url); - }else{ - const scheme = req.secure ? 'https' : 'http'; - const host=`${scheme}://${req.get("Host")}`; - url=new URL(host); - } - try{ - if(url.pathname.startsWith("invite")){ - throw -1; - } - const code=url.pathname.split("/")[2]; - let title=""; - let description=""; - let thumbnail=""; - const urls=await getapiurls(url.searchParams.get("instance")); - await fetch(`${urls.api}/invites/${code}`,{ - method:"GET" - }).then(_=>_.json()).then(json=>{ - title=json.guild.name; - if(json.inviter){ - description=json.inviter.username+" Has invited you to "+json.guild.name+(json.guild.description?json.guild.description+"\n":""); - }else{ - description="you've been invited to "+json.guild.name+(json.guild.description?json.guild.description+"\n":""); - } - if(json.guild.icon){ - thumbnail=`${urls.cdn}/icons/${json.guild.id}/${json.guild.icon}.png`; - } - }); - const json={ - type:"link", - version:"1.0", - title, - thumbnail, - description, - }; - res.send(JSON.stringify(json)); - }catch(e){ - console.error(e); - const json={ - type:"link", - version:"1.0", - title:"Jank Client", - thumbnail:"/logo.webp", - description:"A spacebar client that has DMs, replying and more", - url:url.toString() - }; - res.send(JSON.stringify(json)); - } + let url; + if(URL.canParse(req.query.url)){ + url=new URL(req.query.url); + }else{ + const scheme = req.secure ? "https" : "http"; + const host=`${scheme}://${req.get("Host")}`; + url=new URL(host); + } + try{ + if(url.pathname.startsWith("invite")){ + throw-1; + } + const code=url.pathname.split("/")[2]; + let title=""; + let description=""; + let thumbnail=""; + const urls=await getapiurls(url.searchParams.get("instance")); + await fetch(`${urls.api}/invites/${code}`,{ + method: "GET" + }).then(_=>_.json()).then(json=>{ + title=json.guild.name; + if(json.inviter){ + description=json.inviter.username+" Has invited you to "+json.guild.name+(json.guild.description?json.guild.description+"\n":""); + }else{ + description="you've been invited to "+json.guild.name+(json.guild.description?json.guild.description+"\n":""); + } + if(json.guild.icon){ + thumbnail=`${urls.cdn}/icons/${json.guild.id}/${json.guild.icon}.png`; + } + }); + const json={ + type: "link", + version: "1.0", + title, + thumbnail, + description, + }; + res.send(JSON.stringify(json)); + }catch(e){ + console.error(e); + const json={ + type: "link", + version: "1.0", + title: "Jank Client", + thumbnail: "/logo.webp", + description: "A spacebar client that has DMs, replying and more", + url: url.toString() + }; + res.send(JSON.stringify(json)); + } } - /* +/* function htmlEnc(s) {//https://stackoverflow.com/a/11561642 return s.replaceAll(/&/g, '&') .replaceAll(/ { - inviteres(req, res); -}) +app.use("/services/oembed", (req, res)=>{ + inviteres(req, res); +}); app.use("/uptime",(req,res)=>{ - console.log(req.query.name) - const uptime=stats.uptime[req.query.name]; - console.log(req.query.name,uptime,stats.uptime) - res.send(uptime); -}) -app.use('/', async (req, res) => { - const scheme = req.secure ? 'https' : 'http'; - const host=`${scheme}://${req.get("Host")}`; - const ref=host+req.originalUrl; - if(host&&ref){ - - const link=`${host}/services/oembed?url=${encodeURIComponent(ref)}`; - res.set("Link",`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`); - }else{ - console.log(req); - } - if(req.path==="/"){ - res.sendFile(`./webpage/home.html`, {root: __dirname}); - return; - } - if(debugging&&req.path.startsWith("/service.js")){ - res.send("dud"); - return; - } - if(req.path.startsWith("/instances.json")){ - res.send(JSON.stringify(instances)); - return; - } - if(req.path.startsWith("/invite/")){ - res.sendFile(`./webpage/invite.html`, {root: __dirname}); - return; - } - if(fs.existsSync(`${__dirname}/webpage${req.path}`)) { - res.sendFile(`./webpage${req.path}`, {root: __dirname}); - }else if(req.path.endsWith(".js") && fs.existsSync(`${__dirname}/.dist${req.path}`)){ - const dir=`./.dist${req.path}`; - res.sendFile(dir, {root: __dirname}); - return; - } - else if(fs.existsSync(`${__dirname}/webpage${req.path}.html`)) { - res.sendFile(`./webpage${req.path}.html`, {root: __dirname}); - } - else { - res.sendFile("./webpage/index.html", {root: __dirname}); - } + console.log(req.query.name); + const uptime=stats.uptime[req.query.name]; + console.log(req.query.name,uptime,stats.uptime); + res.send(uptime); +}); +app.use("/", async (req, res)=>{ + const scheme = req.secure ? "https" : "http"; + const host=`${scheme}://${req.get("Host")}`; + const ref=host+req.originalUrl; + if(host&&ref){ + const link=`${host}/services/oembed?url=${encodeURIComponent(ref)}`; + res.set("Link",`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`); + }else{ + console.log(req); + } + if(req.path==="/"){ + res.sendFile("./webpage/home.html", {root: __dirname}); + return; + } + if(debugging&&req.path.startsWith("/service.js")){ + res.send("dud"); + return; + } + if(req.path.startsWith("/instances.json")){ + res.send(JSON.stringify(instances)); + return; + } + if(req.path.startsWith("/invite/")){ + res.sendFile("./webpage/invite.html", {root: __dirname}); + return; + } + if(fs.existsSync(`${__dirname}/webpage${req.path}`)){ + res.sendFile(`./webpage${req.path}`, {root: __dirname}); + }else if(req.path.endsWith(".js") && fs.existsSync(`${__dirname}/.dist${req.path}`)){ + const dir=`./.dist${req.path}`; + res.sendFile(dir, {root: __dirname}); + }else if(fs.existsSync(`${__dirname}/webpage${req.path}.html`)){ + res.sendFile(`./webpage${req.path}.html`, {root: __dirname}); + }else{ + res.sendFile("./webpage/index.html", {root: __dirname}); + } }); -const PORT = process.env.PORT || +process.argv[1] || 8080; -app.listen(PORT, () => {}); +const PORT = process.env.PORT || Number(process.argv[1]) || 8080; +app.listen(PORT, ()=>{}); console.log("this ran :P"); exports.getapiurls=getapiurls; diff --git a/stats.js b/stats.js index a0a33a9..9482964 100644 --- a/stats.js +++ b/stats.js @@ -1,160 +1,164 @@ -const index = require('./index.js'); -const fs=require("fs"); +const index = require("./index.js"); +const fs=require("node:fs"); let uptimeObject={}; if(fs.existsSync("./uptime.json")){ - try{ - uptimeObject=JSON.parse(fs.readFileSync('./uptime.json', 'utf8')); - - }catch{ - uptimeObject={}; - } + try{ + uptimeObject=JSON.parse(fs.readFileSync("./uptime.json", "utf8")); + }catch{ + uptimeObject={}; + } } -if(uptimeObject["undefined"]){ - delete uptimeObject["undefined"]; - updatejson(); +if(uptimeObject.undefined){ + delete uptimeObject.undefined; + updatejson(); } async function observe(instances){ - const active=new Set(); - async function resolveinstance(instance){ - try{calcStats(instance)}catch(e){console.error(e)} - let api; - if(instance.urls){ - api=instance.urls.api; - }else if(instance.url){ - const urls=await index.getapiurls(instance.url); - if(urls){ - api=urls.api; - } - } - if(!api||api===""){ - - setStatus(instance,false); - console.warn(instance.name+" does not resolve api URL"); - setTimeout(_=>{resolveinstance(instance)},1000*60*30,); - return - } - active.add(instance.name); - api+=api.endsWith("/")?"":"/" - function check(){ - fetch(api+"ping",{method:"HEAD"}).then(_=>{ - setStatus(instance,_.ok); - }) - } - setTimeout( - _=>{ - check(); - setInterval(_=>{ - check(); - },1000*60*30) - },Math.random()*1000*60*10 - ) - } - const promlist=[]; - for(const instance of instances){ - promlist.push(resolveinstance(instance)); - } - await Promise.allSettled(promlist); - for(const key of Object.keys(uptimeObject)){ - if(!active.has(key)){ - setStatus(key,false); - } - } + const active=new Set(); + async function resolveinstance(instance){ + try{ + calcStats(instance); + }catch(e){ + console.error(e); + } + let api; + if(instance.urls){ + api=instance.urls.api; + }else if(instance.url){ + const urls=await index.getapiurls(instance.url); + if(urls){ + api=urls.api; + } + } + if(!api||api===""){ + setStatus(instance,false); + console.warn(instance.name+" does not resolve api URL"); + setTimeout(_=>{ + resolveinstance(instance); + },1000*60*30,); + return; + } + active.add(instance.name); + api+=api.endsWith("/")?"":"/"; + function check(){ + fetch(api+"ping",{method: "HEAD"}).then(_=>{ + setStatus(instance,_.ok); + }); + } + setTimeout( + _=>{ + check(); + setInterval(_=>{ + check(); + },1000*60*30); + },Math.random()*1000*60*10 + ); + } + const promlist=[]; + for(const instance of instances){ + promlist.push(resolveinstance(instance)); + } + await Promise.allSettled(promlist); + for(const key of Object.keys(uptimeObject)){ + if(!active.has(key)){ + setStatus(key,false); + } + } } function calcStats(instance){ - let obj=uptimeObject[instance.name]; - if(!obj) return; - const day=Date.now()-1000*60*60*24; - const week=Date.now()-1000*60*60*24*7; - let alltime=-1; - let totalTimePassed=0; - let daytime=-1; - let weektime=-1; - let online=false; - let i=0; - for(const thing of obj){ - online=thing.online; - const stamp=thing.time; - if(alltime===-1){ - alltime=0; - } - let timepassed; - if(obj[i+1]){ - timepassed=obj[i+1].time-stamp; - }else{ - timepassed=Date.now()-stamp; - } - totalTimePassed+=timepassed; - alltime+=online*timepassed; - if(stamp>week){ - if(weektime===-1){ - weektime=online*(stamp-week); - }else{ - weektime+=online*timepassed; - } - if(stamp>day){ - if(daytime===-1){ - daytime=online*(stamp-day); - }else{ - daytime+=online*timepassed; - } - } - } + const obj=uptimeObject[instance.name]; + if(!obj)return; + const day=Date.now()-1000*60*60*24; + const week=Date.now()-1000*60*60*24*7; + let alltime=-1; + let totalTimePassed=0; + let daytime=-1; + let weektime=-1; + let online=false; + let i=0; + for(const thing of obj){ + online=thing.online; + const stamp=thing.time; + if(alltime===-1){ + alltime=0; + } + let timepassed; + if(obj[i+1]){ + timepassed=obj[i+1].time-stamp; + }else{ + timepassed=Date.now()-stamp; + } + totalTimePassed+=timepassed; + alltime+=online*timepassed; + if(stamp>week){ + if(weektime===-1){ + weektime=online*(stamp-week); + }else{ + weektime+=online*timepassed; + } + if(stamp>day){ + if(daytime===-1){ + daytime=online*(stamp-day); + }else{ + daytime+=online*timepassed; + } + } + } - i++; - } - console.log(daytime); - instance.online=online; - alltime/=totalTimePassed; - if(totalTimePassed>1000*60*60*24){ - if(daytime===-1){ - daytime=online*1000*60*60*24; - } - daytime/=1000*60*60*24; - if(totalTimePassed>1000*60*60*24*7){ - if(weektime===-1){ - weektime=online*1000*60*60*24*7; - } - weektime/=1000*60*60*24*7; - }else{ - weektime=alltime; - } - }else{ - weektime=alltime - daytime=alltime; - } - instance.uptime={daytime,weektime,alltime} + i++; + } + console.log(daytime); + instance.online=online; + alltime/=totalTimePassed; + if(totalTimePassed>1000*60*60*24){ + if(daytime===-1){ + daytime=online*1000*60*60*24; + } + daytime/=1000*60*60*24; + if(totalTimePassed>1000*60*60*24*7){ + if(weektime===-1){ + weektime=online*1000*60*60*24*7; + } + weektime/=1000*60*60*24*7; + }else{ + weektime=alltime; + } + }else{ + weektime=alltime; + daytime=alltime; + } + instance.uptime={daytime,weektime,alltime}; } /** * @param {string|Object} instance * @param {boolean} status */ function setStatus(instance,status){ - let name=instance.name; - if(typeof instance==="string" ){ - name=instance; - } + let name=instance.name; + if(typeof instance==="string"){ + name=instance; + } - let obj=uptimeObject[name]; - let needSetting=false; - if(!obj){ - obj=[]; - uptimeObject[name]=obj; - needSetting=true; - }else{ - if(obj[obj.length-1].online!==status){ - needSetting=true; - } - } - if(needSetting){ - obj.push({time:Date.now(),online:status}); - updatejson(); - } - if(typeof instance!=="string" ){ - calcStats(instance); - } + let obj=uptimeObject[name]; + let needSetting=false; + if(!obj){ + obj=[]; + uptimeObject[name]=obj; + needSetting=true; + }else{ + if(obj.at(-1).online!==status){ + needSetting=true; + } + } + if(needSetting){ + obj.push({time: Date.now(),online: status}); + updatejson(); + } + if(typeof instance!=="string"){ + calcStats(instance); + } } function updatejson(){ - fs.writeFile('./uptime.json',JSON.stringify(uptimeObject),_=>{}); + fs.writeFile("./uptime.json",JSON.stringify(uptimeObject),_=>{}); } exports.observe=observe; exports.uptime=uptimeObject; diff --git a/webpage/audio.ts b/webpage/audio.ts index e4993b1..2b259ab 100644 --- a/webpage/audio.ts +++ b/webpage/audio.ts @@ -1,146 +1,164 @@ -import {getBulkInfo} from "./login.js"; +import{getBulkInfo}from"./login.js"; class Voice{ - audioCtx:AudioContext; - info:{wave:string|Function,freq:number}; - playing:boolean; - myArrayBuffer:AudioBuffer; - gainNode:GainNode; - buffer:Float32Array; - source:AudioBufferSourceNode; - constructor(wave:string|Function,freq:number,volume=1){ - this.audioCtx = new (window.AudioContext)(); - this.info={wave:wave,freq:freq} - this.playing=false; - this.myArrayBuffer=this.audioCtx.createBuffer( - 1, - this.audioCtx.sampleRate, - this.audioCtx.sampleRate, - ); - this.gainNode = this.audioCtx.createGain(); - this.gainNode.gain.value=volume; - this.gainNode.connect(this.audioCtx.destination); - this.buffer=this.myArrayBuffer.getChannelData(0); - this.source = this.audioCtx.createBufferSource(); - this.source.buffer = this.myArrayBuffer; - this.source.loop=true; - this.source.start(); - this.updateWave(); - } - get wave():string|Function{ - return this.info.wave; - } - get freq():number{ - return this.info.freq; - } - set wave(wave:string|Function){ - this.info.wave=wave; - this.updateWave(); - } - set freq(freq:number){ - this.info.freq=freq; - this.updateWave(); - } - updateWave():void{ - const func=this.waveFunction(); - for (let i = 0; i < this.buffer.length; i++) { - this.buffer[i]=func(i/this.audioCtx.sampleRate,this.freq); - } - - } - waveFunction():Function{ - if(typeof this.wave === 'function'){ - return this.wave; - } - switch(this.wave){ - case "sin": - return (t:number,freq:number)=>{ - return Math.sin(t*Math.PI*2*freq); - } - case "triangle": - return (t:number,freq:number)=>{ - return Math.abs((4*t*freq)%4-2)-1; - } - case "sawtooth": - return (t:number,freq:number)=>{ - return ((t*freq)%1)*2-1; - } - case "square": - return (t:number,freq:number)=>{ - return (t*freq)%2<1?1:-1; - } - case "white": - return (_t:number,_freq:number)=>{ - return Math.random()*2-1; - } - case "noise": - return (_t:number,_freq:number)=>{ - return 0; - } - } - return new Function(); - } - play():void{ - if(this.playing){ - return; - } - this.source.connect(this.gainNode); - this.playing=true; - - } - stop():void{ - if(this.playing){ - this.source.disconnect(); - this.playing=false; - } - } - static noises(noise:string):void{ - switch(noise){ - case "three":{ - const voicy=new Voice("sin",800); - voicy.play(); - setTimeout(_=>{voicy.freq=1000},50); - setTimeout(_=>{voicy.freq=1300},100); - setTimeout(_=>{voicy.stop()},150); - break; - } - case "zip":{ - const voicy=new Voice((t:number,freq:number)=>{ - return Math.sin(((t+2)**(Math.cos(t*4)))*Math.PI*2*freq); - },700); - voicy.play(); - setTimeout(_=>{voicy.stop()},150); - break; - } - case "square":{ - const voicy=new Voice("square",600,.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){ - let userinfos=getBulkInfo(); - userinfos.preferences.notisound=sound; - localStorage.setItem("userinfos",JSON.stringify(userinfos)); - } - static getNotificationSound(){ - let userinfos=getBulkInfo(); - return userinfos.preferences.notisound; - } + audioCtx:AudioContext; + info:{wave:string|Function,freq:number}; + playing:boolean; + myArrayBuffer:AudioBuffer; + gainNode:GainNode; + buffer:Float32Array; + source:AudioBufferSourceNode; + constructor(wave:string|Function,freq:number,volume=1){ + this.audioCtx = new (window.AudioContext)(); + this.info={wave,freq}; + this.playing=false; + this.myArrayBuffer=this.audioCtx.createBuffer( + 1, + this.audioCtx.sampleRate, + this.audioCtx.sampleRate, + ); + this.gainNode = this.audioCtx.createGain(); + this.gainNode.gain.value=volume; + this.gainNode.connect(this.audioCtx.destination); + this.buffer=this.myArrayBuffer.getChannelData(0); + this.source = this.audioCtx.createBufferSource(); + this.source.buffer = this.myArrayBuffer; + this.source.loop=true; + this.source.start(); + this.updateWave(); + } + get wave():string|Function{ + return this.info.wave; + } + get freq():number{ + return this.info.freq; + } + set wave(wave:string|Function){ + this.info.wave=wave; + this.updateWave(); + } + set freq(freq:number){ + this.info.freq=freq; + this.updateWave(); + } + updateWave():void{ + const func=this.waveFunction(); + for(let i = 0; i < this.buffer.length; i++){ + this.buffer[i]=func(i/this.audioCtx.sampleRate,this.freq); + } + } + waveFunction():Function{ + if(typeof this.wave === "function"){ + return this.wave; + } + switch(this.wave){ + case"sin": + return(t:number,freq:number)=>{ + return Math.sin(t*Math.PI*2*freq); + }; + case"triangle": + return(t:number,freq:number)=>{ + return Math.abs((4*t*freq)%4-2)-1; + }; + case"sawtooth": + return(t:number,freq:number)=>{ + return((t*freq)%1)*2-1; + }; + case"square": + return(t:number,freq:number)=>{ + return(t*freq)%2<1?1:-1; + }; + case"white": + return(_t:number,_freq:number)=>{ + return Math.random()*2-1; + }; + case"noise": + return(_t:number,_freq:number)=>{ + return 0; + }; + } + return new Function(); + } + play():void{ + if(this.playing){ + return; + } + this.source.connect(this.gainNode); + this.playing=true; + } + stop():void{ + if(this.playing){ + this.source.disconnect(); + this.playing=false; + } + } + static noises(noise:string):void{ + switch(noise){ + case"three":{ + const voicy=new Voice("sin",800); + voicy.play(); + setTimeout(_=>{ + voicy.freq=1000; + },50); + setTimeout(_=>{ + voicy.freq=1300; + },100); + setTimeout(_=>{ + voicy.stop(); + },150); + break; + } + case"zip":{ + const voicy=new Voice((t:number,freq:number)=>{ + return Math.sin(((t+2)**(Math.cos(t*4)))*Math.PI*2*freq); + },700); + voicy.play(); + setTimeout(_=>{ + voicy.stop(); + },150); + break; + } + case"square":{ + const voicy=new Voice("square",600,0.4); + voicy.play(); + setTimeout(_=>{ + voicy.freq=800; + },50); + setTimeout(_=>{ + voicy.freq=1000; + },100); + setTimeout(_=>{ + voicy.stop(); + },150); + break; + } + case"beep":{ + const voicy=new Voice("sin",800); + voicy.play(); + setTimeout(_=>{ + voicy.stop(); + },50); + setTimeout(_=>{ + voicy.play(); + },100); + setTimeout(_=>{ + voicy.stop(); + },150); + break; + } + } + } + static get sounds(){ + return["three","zip","square","beep"]; + } + static setNotificationSound(sound:string){ + const userinfos=getBulkInfo(); + userinfos.preferences.notisound=sound; + localStorage.setItem("userinfos",JSON.stringify(userinfos)); + } + static getNotificationSound(){ + const userinfos=getBulkInfo(); + return userinfos.preferences.notisound; + } } -export {Voice as Voice}; +export{Voice}; diff --git a/webpage/channel.ts b/webpage/channel.ts index 371b0ac..174125c 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -1,17 +1,17 @@ -"use strict" -import { Message } from "./message.js"; -import {Voice} from "./audio.js"; -import {Contextmenu} from "./contextmenu.js"; -import {Dialog} from "./dialog.js"; -import {Guild} from "./guild.js"; -import { Localuser } from "./localuser.js"; -import { Permissions } from "./permissions.js"; -import { Settings } from "./settings.js"; -import { Role,RoleList } from "./role.js"; -import {InfiniteScroller} from "./infiniteScroller.js"; -import { SnowFlake } from "./snowflake.js"; -import { channeljson, messageCreateJson, messagejson, readyjson } from "./jsontypes.js"; -import { MarkDown } from "./markdown.js"; +"use strict"; +import{ Message }from"./message.js"; +import{Voice}from"./audio.js"; +import{Contextmenu}from"./contextmenu.js"; +import{Dialog}from"./dialog.js"; +import{Guild}from"./guild.js"; +import{ Localuser }from"./localuser.js"; +import{ Permissions }from"./permissions.js"; +import{ Settings }from"./settings.js"; +import{ Role,RoleList }from"./role.js"; +import{InfiniteScroller}from"./infiniteScroller.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ channeljson, messageCreateJson, messagejson, readyjson }from"./jsontypes.js"; +import{ MarkDown }from"./markdown.js"; declare global { interface NotificationOptions { @@ -19,69 +19,73 @@ declare global { } } class Channel{ - editing:Message|null; - type:number; - owner:Guild; - headers:Localuser["headers"]; - name:string; - snowflake:SnowFlake; - parent_id:SnowFlake|null; - parent:Channel|null; - children:Channel[]; - guild_id:string; - messageids:Map,Message>; - permission_overwrites:Map; - permission_overwritesar:[SnowFlake,Permissions][] - topic:string; - nsfw:boolean; - position:number; - lastreadmessageid:string|undefined; - lastmessageid:string|undefined; - mentions:number; - lastpin:string; - move_id:SnowFlake|null; - typing:number; - message_notifications:number; - allthewayup:boolean; - static contextmenu=new Contextmenu("channel menu"); - replyingto:Message|null; - infinite:InfiniteScroller; - idToPrev:Map=new Map(); - idToNext:Map=new Map(); - messages:Map=new Map(); - get id(){ - return this.snowflake.id; - } - static setupcontextmenu(){ - this.contextmenu.addbutton("Copy channel id",function(this:Channel){ - console.log(this) - navigator.clipboard.writeText(this.id); - }); + editing:Message|null; + type:number; + owner:Guild; + headers:Localuser["headers"]; + name:string; + snowflake:SnowFlake; + parent_id:SnowFlake|null; + parent:Channel|null; + children:Channel[]; + guild_id:string; + messageids:Map,Message>; + permission_overwrites:Map; + permission_overwritesar:[SnowFlake,Permissions][]; + topic:string; + nsfw:boolean; + position:number; + lastreadmessageid:string|undefined; + lastmessageid:string|undefined; + mentions:number; + lastpin:string; + move_id:SnowFlake|null; + typing:number; + message_notifications:number; + allthewayup:boolean; + static contextmenu=new Contextmenu("channel menu"); + replyingto:Message|null; + infinite:InfiniteScroller; + idToPrev:Map=new Map(); + idToNext:Map=new Map(); + messages:Map=new Map(); + get id(){ + return this.snowflake.id; + } + static setupcontextmenu(){ + this.contextmenu.addbutton("Copy channel id",function(this:Channel){ + console.log(this); + navigator.clipboard.writeText(this.id); + }); - this.contextmenu.addbutton("Mark as read",function(this:Channel){ - console.log(this) - this.readbottom(); - }); + this.contextmenu.addbutton("Mark as read",function(this:Channel){ + console.log(this); + this.readbottom(); + }); - this.contextmenu.addbutton("Settings[temp]",function(this:Channel){ - this.generateSettings(); - }); + this.contextmenu.addbutton("Settings[temp]",function(this:Channel){ + this.generateSettings(); + }); - this.contextmenu.addbutton("Delete channel",function(this:Channel){ - console.log(this) - this.deleteChannel(); - },null,function(){return this.isAdmin()}); + this.contextmenu.addbutton("Delete channel",function(this:Channel){ + console.log(this); + this.deleteChannel(); + },null,function(){ + return this.isAdmin(); + }); - this.contextmenu.addbutton("Edit channel",function(this:Channel){ - this.editChannel(); - },null,function(){return this.isAdmin()}); + this.contextmenu.addbutton("Edit channel",function(this:Channel){ + this.editChannel(); + },null,function(){ + return this.isAdmin(); + }); - this.contextmenu.addbutton("Make invite",function(this:Channel){ - this.createInvite(); - },null,function(){ - return this.hasPermission("CREATE_INSTANT_INVITE")&&this.type!==4 - }); - /* + this.contextmenu.addbutton("Make invite",function(this:Channel){ + this.createInvite(); + },null,function(){ + return this.hasPermission("CREATE_INSTANT_INVITE")&&this.type!==4; + }); + /* this.contextmenu.addbutton("Test button",function(){ this.localuser.ws.send(JSON.stringify({ "op": 14, @@ -99,1013 +103,1045 @@ class Channel{ })) },null); /**/ - } - createInvite(){ - const div=document.createElement("div"); - div.classList.add("invitediv"); - const text=document.createElement("span"); - div.append(text); - let uses=0; - let expires=1800; - const copycontainer=document.createElement("div"); - copycontainer.classList.add("copycontainer") - const copy=document.createElement("img"); - copy.src="/icons/copy.svg"; - copy.classList.add("copybutton","svgtheme"); - copycontainer.append(copy); - copycontainer.onclick=_=>{ - if(text.textContent){ - navigator.clipboard.writeText(text.textContent); - } - } - div.append(copycontainer); - const update=()=>{ - fetch(`${this.info.api}/channels/${this.id}/invites`,{ - method:"POST", - headers:this.headers, - body:JSON.stringify({ - flags: 0, - target_type: null, - target_user_id: null, - max_age: expires+"", - max_uses: uses, - temporary: uses!==0 - }) - }).then(_=>_.json()).then(json=>{ - const params=new URLSearchParams(""); - params.set("instance",this.info.wellknown) - const encoded=params.toString(); - text.textContent=`${location.origin}/invite/${json.code}?${encoded}` - }) - } - update(); - new Dialog(["vdiv", - ["title","Invite people"], - ["text",`to #${this.name} in ${this.guild.properties.name}`], - ["select","Expire after:",["30 Minutes","1 Hour","6 Hours","12 Hours","1 Day","7 Days","30 Days","Never"],function(e){ - expires=[1800,3600,21600,43200,86400,604800,2592000,0][e.srcElement.selectedIndex]; - update(); - },0], - ["select","Max uses:",["No limit","1 use","5 uses","10 uses","25 uses","50 uses","100 uses"],function(e){ - uses=[0,1,5,10,25,50,100][e.srcElement.selectedIndex]; - update(); - },0], - ["html",div] - ]).show() - } - generateSettings(){ - this.sortPerms(); - const settings=new Settings("Settings for "+this.name); + } + createInvite(){ + const div=document.createElement("div"); + div.classList.add("invitediv"); + const text=document.createElement("span"); + div.append(text); + let uses=0; + let expires=1800; + const copycontainer=document.createElement("div"); + copycontainer.classList.add("copycontainer"); + const copy=document.createElement("img"); + copy.src="/icons/copy.svg"; + copy.classList.add("copybutton","svgtheme"); + copycontainer.append(copy); + copycontainer.onclick=_=>{ + if(text.textContent){ + navigator.clipboard.writeText(text.textContent); + } + }; + div.append(copycontainer); + const update=()=>{ + fetch(`${this.info.api}/channels/${this.id}/invites`,{ + method: "POST", + headers: this.headers, + body: JSON.stringify({ + flags: 0, + target_type: null, + target_user_id: null, + max_age: expires+"", + max_uses: uses, + temporary: uses!==0 + }) + }).then(_=>_.json()).then(json=>{ + const params=new URLSearchParams(""); + params.set("instance",this.info.wellknown); + const encoded=params.toString(); + text.textContent=`${location.origin}/invite/${json.code}?${encoded}`; + }); + }; + update(); + new Dialog(["vdiv", + ["title","Invite people"], + ["text",`to #${this.name} in ${this.guild.properties.name}`], + ["select","Expire after:",["30 Minutes","1 Hour","6 Hours","12 Hours","1 Day","7 Days","30 Days","Never"],function(e){ + expires=[1800,3600,21600,43200,86400,604800,2592000,0][e.srcElement.selectedIndex]; + update(); + },0], + ["select","Max uses:",["No limit","1 use","5 uses","10 uses","25 uses","50 uses","100 uses"],function(e){ + uses=[0,1,5,10,25,50,100][e.srcElement.selectedIndex]; + update(); + },0], + ["html",div] + ]).show(); + } + generateSettings(){ + this.sortPerms(); + const settings=new Settings("Settings for "+this.name); - const s1=settings.addButton("roles"); + const s1=settings.addButton("roles"); - s1.options.push(new RoleList(this.permission_overwritesar,this.guild,this.updateRolePermissions.bind(this),true)) - settings.show(); - } - sortPerms(){ - this.permission_overwritesar.sort((a,b)=>{ - const order=this.guild.roles.findIndex(_=>_.snowflake===a[0])-this.guild.roles.findIndex(_=>_.snowflake===b[0]); - return order; - }) - } - setUpInfiniteScroller(){ - this.infinite=new InfiniteScroller(async function(this:Channel,id:string,offset:number):Promise{ - const snowflake=id; - if(offset===1){ - if(this.idToPrev.has(snowflake)){ - return this.idToPrev.get(snowflake); - }else{ - await this.grabBefore(id); - return this.idToPrev.get(snowflake); - } - }else{ - if(this.idToNext.has(snowflake)){ - return this.idToNext.get(snowflake); - }else if(this.lastmessage?.id!==id){ - await this.grabAfter(id); - return this.idToNext.get(snowflake); - }else{ - console.log("at bottom") - } - } - }.bind(this), - async function(this:Channel,id:string){ - //await new Promise(_=>{setTimeout(_,Math.random()*10)}) - const messgage=this.messages.get(id); - try{ - if(messgage){ - const html=messgage.buildhtml(); - return html; - }else{ - console.error(id+" not found") - } - }catch(e){ - console.error(e); - } - }.bind(this), - async function(this:Channel,id:string){ - const message=this.messages.get(id); - try{ - if(message){ - message.deleteDiv(); - } - }catch(e){console.error(e)}finally{} - }.bind(this), - this.readbottom.bind(this) - ); - } - constructor(json:channeljson|-1,owner:Guild){ + s1.options.push(new RoleList(this.permission_overwritesar,this.guild,this.updateRolePermissions.bind(this),true)); + settings.show(); + } + sortPerms(){ + this.permission_overwritesar.sort((a,b)=>{ + return this.guild.roles.findIndex(_=>_.snowflake===a[0])-this.guild.roles.findIndex(_=>_.snowflake===b[0]); + }); + } + setUpInfiniteScroller(){ + this.infinite=new InfiniteScroller((async (id:string,offset:number):Promise=>{ + const snowflake=id; + if(offset===1){ + if(this.idToPrev.has(snowflake)){ + return this.idToPrev.get(snowflake); + }else{ + await this.grabBefore(id); + return this.idToPrev.get(snowflake); + } + }else{ + if(this.idToNext.has(snowflake)){ + return this.idToNext.get(snowflake); + }else if(this.lastmessage?.id!==id){ + await this.grabAfter(id); + return this.idToNext.get(snowflake); + }else{ + console.log("at bottom"); + } + } + }), + (async (id:string):Promise=>{ + //await new Promise(_=>{setTimeout(_,Math.random()*10)}) + const messgage=this.messages.get(id); + try{ + if(messgage){ + return messgage.buildhtml(); + }else{ + console.error(id+" not found"); + } + }catch(e){ + console.error(e); + } + return document.createElement("div"); + }), + (async (id:string)=>{ + const message=this.messages.get(id); + try{ + if(message){ + message.deleteDiv(); + return true; + } + }catch(e){ + console.error(e); + }finally{} + return false; + }), + this.readbottom.bind(this) + ); + } + constructor(json:channeljson|-1,owner:Guild){ + if(json===-1){ + return; + } + this.editing; + this.type=json.type; + this.owner=owner; + this.headers=this.owner.headers; + this.name=json.name; + this.snowflake=new SnowFlake(json.id,this); + if(json.parent_id){ + this.parent_id=SnowFlake.getSnowFlakeFromID(json.parent_id,Channel); + } + this.parent=null; + this.children=[]; + this.guild_id=json.guild_id; + this.messageids=new Map(); + this.permission_overwrites=new Map(); + this.permission_overwritesar=[]; + for(const thing of json.permission_overwrites){ + if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){ + continue; + } + this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny)); + const permission=this.permission_overwrites.get(thing.id); + if(permission){ + this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id,Role),permission]); + } + } - if(json===-1){ - return; - } - this.editing; - this.type=json.type; - this.owner=owner; - this.headers=this.owner.headers; - this.name=json.name; - this.snowflake=new SnowFlake(json.id,this); - if(json.parent_id){ - this.parent_id=SnowFlake.getSnowFlakeFromID(json.parent_id,Channel); - } - this.parent=null; - this.children=[]; - this.guild_id=json.guild_id; - this.messageids=new Map(); - this.permission_overwrites=new Map(); - this.permission_overwritesar=[]; - for(const thing of json.permission_overwrites){ - if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){continue;}; - this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny)); - const permission=this.permission_overwrites.get(thing.id); - if(permission){ - this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id,Role),permission]); - } - } + this.topic=json.topic; + this.nsfw=json.nsfw; + this.position=json.position; + this.lastreadmessageid=undefined; + if(json.last_message_id){ + this.lastmessageid=json.last_message_id; + }else{ + this.lastmessageid=undefined; + } + this.setUpInfiniteScroller(); + } + isAdmin(){ + return this.guild.isAdmin(); + } + get guild(){ + return this.owner; + } + get localuser(){ + return this.guild.localuser; + } + get info(){ + return this.owner.info; + } + readStateInfo(json:readyjson["d"]["read_state"]["entries"][0]){ + this.lastreadmessageid=json.last_message_id; + this.mentions=json.mention_count; + this.mentions??=0; + this.lastpin=json.last_pin_timestamp; + } + get hasunreads():boolean{ + if(!this.hasPermission("VIEW_CHANNEL")){ + return false; + } + return this.lastmessageid!==this.lastreadmessageid&&this.type!==4&&Boolean(this.lastmessageid); + } + hasPermission(name:string,member=this.guild.member):boolean{ + if(member.isAdmin()){ + return true; + } + for(const thing of member.roles){ + const premission=this.permission_overwrites.get(thing.id); + if(premission){ + const perm=premission.getPermission(name); + if(perm){ + return perm===1; + } + } + if(thing.permissions.getPermission(name)){ + return true; + } + } + return false; + } + get canMessage():boolean{ + if((this.permission_overwritesar.length===0)&&this.hasPermission("MANAGE_CHANNELS")){ + const role=this.guild.roles.find(_=>_.name==="@everyone"); + if(role){ + this.addRoleToPerms(role); + } + } + return this.hasPermission("SEND_MESSAGES"); + } + sortchildren(){ + this.children.sort((a,b)=>{ + return a.position-b.position; + }); + } + resolveparent(guild:Guild){ + const parentid=this.parent_id?.id; + if(!parentid)return false; + this.parent=guild.channelids[parentid]; + this.parent??=null; + if(this.parent!==null){ + this.parent.children.push(this); + } + return this.parent!==null; + } + calculateReorder(){ + let position=-1; + const build:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}[]=[]; + for(const thing of this.children){ + const thisthing:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}={id: thing.snowflake,position: undefined,parent_id: undefined}; + if(thing.position{ + Channel.dragged=[this,div];e.stopImmediatePropagation(); + }); + div.addEventListener("dragend",()=>{ + Channel.dragged=[]; + }); + if(this.type===4){ + this.sortchildren(); + const caps=document.createElement("div"); - this.topic=json.topic; - this.nsfw=json.nsfw; - this.position=json.position; - this.lastreadmessageid=undefined; - if(json.last_message_id){ - this.lastmessageid=json.last_message_id; - }else{ - this.lastmessageid=undefined; - } - this.setUpInfiniteScroller(); - } - isAdmin(){ - return this.guild.isAdmin(); - } - get guild(){ - return this.owner; - } - get localuser(){ - return this.guild.localuser; - } - get info(){ - return this.owner.info; - } - readStateInfo(json:readyjson["d"]["read_state"]["entries"][0]){ - this.lastreadmessageid=json.last_message_id; - this.mentions=json.mention_count; - this.mentions??=0; - this.lastpin=json.last_pin_timestamp; - } - get hasunreads():boolean{ - if(!this.hasPermission("VIEW_CHANNEL")){return false;} - return this.lastmessageid!==this.lastreadmessageid&&this.type!==4&&!!this.lastmessageid; - } - hasPermission(name:string,member=this.guild.member):boolean{ - if(member.isAdmin()){ - return true; - } - for(const thing of member.roles){ - const premission=this.permission_overwrites.get(thing.id); - if(premission){ - let perm=premission.getPermission(name); - if(perm){ - return perm===1; - } - } - if(thing.permissions.getPermission(name)){ - return true; - } - } - return false; - } - get canMessage():boolean{ - if((0===this.permission_overwritesar.length)&&this.hasPermission("MANAGE_CHANNELS")){ - const role=this.guild.roles.find(_=>_.name==="@everyone"); - if(role){ - this.addRoleToPerms(role); - } - } - return this.hasPermission("SEND_MESSAGES"); - } - sortchildren(){ - this.children.sort((a,b)=>{return a.position-b.position}); - } - resolveparent(guild:Guild){ - const parentid=this.parent_id?.id; - if(!parentid) return false; - this.parent=guild.channelids[parentid]; - this.parent??=null; - if(this.parent!==null){ - this.parent.children.push(this); - } - return this.parent!==null; - } - calculateReorder(){ - let position=-1; - let build:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}[]=[]; - for(const thing of this.children){ - const thisthing:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}={id:thing.snowflake,position:undefined,parent_id:undefined}; - if(thing.position{Channel.dragged=[this,div];e.stopImmediatePropagation()}) - div.addEventListener("dragend",()=>{Channel.dragged=[]}) - if(this.type===4){ - this.sortchildren(); - const caps=document.createElement("div"); + const decdiv=document.createElement("div"); + const decoration=document.createElement("img"); + decoration.src="/icons/category.svg"; + decoration.classList.add("svgtheme","colaspeicon"); + decdiv.appendChild(decoration); - const decdiv=document.createElement("div"); - const decoration=document.createElement("img"); - decoration.src="/icons/category.svg"; - decoration.classList.add("svgtheme","colaspeicon") - decdiv.appendChild(decoration) + const myhtml=document.createElement("p2"); + myhtml.textContent=this.name; + decdiv.appendChild(myhtml); + caps.appendChild(decdiv); + const childrendiv=document.createElement("div"); + if(admin){ + const addchannel=document.createElement("span"); + addchannel.textContent="+"; + addchannel.classList.add("addchannel"); + caps.appendChild(addchannel); + addchannel.onclick=_=>{ + this.guild.createchannels(this.createChannel.bind(this)); + }; + this.coatDropDiv(decdiv,childrendiv); + } + div.appendChild(caps); + caps.classList.add("capsflex"); + decdiv.classList.add("channeleffects"); + decdiv.classList.add("channel"); - const myhtml=document.createElement("p2"); - myhtml.textContent=this.name; - decdiv.appendChild(myhtml); - caps.appendChild(decdiv); - const childrendiv=document.createElement("div"); - if(admin){ - const addchannel=document.createElement("span"); - addchannel.textContent="+"; - addchannel.classList.add("addchannel"); - caps.appendChild(addchannel); - addchannel.onclick=_=>{ - this.guild.createchannels(this.createChannel.bind(this)); - } - this.coatDropDiv(decdiv,childrendiv); - } - div.appendChild(caps) - caps.classList.add("capsflex") - decdiv.classList.add("channeleffects"); - decdiv.classList.add("channel"); - - Channel.contextmenu.bindContextmenu(decdiv,this,undefined); - decdiv["all"]=this; + Channel.contextmenu.bindContextmenu(decdiv,this,undefined); + decdiv["all"]=this; - for(const channel of this.children){ - childrendiv.appendChild(channel.createguildHTML(admin)); - } - childrendiv.classList.add("channels"); - setTimeout(_=>{childrendiv.style.height = childrendiv.scrollHeight + 'px';},100) - decdiv.onclick=function(){ - if(childrendiv.style.height!=='0px'){ - decoration.classList.add("hiddencat"); - //childrendiv.classList.add("colapsediv"); - childrendiv.style.height = '0px'; - }else{ - decoration.classList.remove("hiddencat"); - //childrendiv.classList.remove("colapsediv") - childrendiv.style.height = childrendiv.scrollHeight + 'px'; - } - } - div.appendChild(childrendiv); - }else{ - div.classList.add("channel"); - if(this.hasunreads){ - div.classList.add("cunread"); - } - Channel.contextmenu.bindContextmenu(div,this,undefined); - if(admin){this.coatDropDiv(div);} - div["all"]=this; - const myhtml=document.createElement("span"); - myhtml.textContent=this.name; - if(this.type===0){ - const decoration=document.createElement("img"); - decoration.src="/icons/channel.svg"; - div.appendChild(decoration) - decoration.classList.add("space","svgtheme"); - }else if(this.type===2){// - const decoration=document.createElement("img"); - decoration.src="/icons/voice.svg"; - div.appendChild(decoration) - decoration.classList.add("space","svgtheme"); - }else if(this.type===5){// - const decoration=document.createElement("img"); - decoration.src="/icons/announce.svg"; - div.appendChild(decoration) - decoration.classList.add("space","svgtheme"); - }else{ - console.log(this.type) - } - div.appendChild(myhtml); - div.onclick=_=>{ - this.getHTML(); - } - } - return div; - } - get myhtml(){ - const search=(document.getElementById("channels") as HTMLDivElement).children[0].children - if(this.guild!==this.localuser.lookingguild){ - return null - }else if(this.parent){ - for(const thing of search){ - if(thing["all"]===this.parent){ - for(const thing2 of thing.children[1].children){ - if(thing2["all"]===this){ - return thing2; - } - } - } - } - }else{ - for(const thing of search){ - if(thing["all"]===this){ - return thing; - } - } - } - return null; - } - readbottom(){ - if(!this.hasunreads){ - return; - } - fetch(this.info.api+"/channels/"+this.snowflake+"/messages/"+this.lastmessageid+"/ack",{ - method:"POST", - headers:this.headers, - body:JSON.stringify({}) - }); - this.lastreadmessageid=this.lastmessageid; - this.guild.unreads(); - if(this.myhtml!==null){ - this.myhtml.classList.remove("cunread"); - } - } - coatDropDiv(div:HTMLDivElement,container:HTMLElement|boolean=false){ - div.addEventListener("dragenter", (event) => { - console.log("enter") - event.preventDefault(); - }); + for(const channel of this.children){ + childrendiv.appendChild(channel.createguildHTML(admin)); + } + childrendiv.classList.add("channels"); + setTimeout(_=>{ + childrendiv.style.height = childrendiv.scrollHeight + "px"; + },100); + decdiv.onclick=function(){ + if(childrendiv.style.height!=="0px"){ + decoration.classList.add("hiddencat"); + //childrendiv.classList.add("colapsediv"); + childrendiv.style.height = "0px"; + }else{ + decoration.classList.remove("hiddencat"); + //childrendiv.classList.remove("colapsediv") + childrendiv.style.height = childrendiv.scrollHeight + "px"; + } + }; + div.appendChild(childrendiv); + }else{ + div.classList.add("channel"); + if(this.hasunreads){ + div.classList.add("cunread"); + } + Channel.contextmenu.bindContextmenu(div,this,undefined); + if(admin){ + this.coatDropDiv(div); + } + div["all"]=this; + const myhtml=document.createElement("span"); + myhtml.textContent=this.name; + if(this.type===0){ + const decoration=document.createElement("img"); + decoration.src="/icons/channel.svg"; + div.appendChild(decoration); + decoration.classList.add("space","svgtheme"); + }else if(this.type===2){// + const decoration=document.createElement("img"); + decoration.src="/icons/voice.svg"; + div.appendChild(decoration); + decoration.classList.add("space","svgtheme"); + }else if(this.type===5){// + const decoration=document.createElement("img"); + decoration.src="/icons/announce.svg"; + div.appendChild(decoration); + decoration.classList.add("space","svgtheme"); + }else{ + console.log(this.type); + } + div.appendChild(myhtml); + div.onclick=_=>{ + this.getHTML(); + }; + } + return div; + } + get myhtml(){ + const search=(document.getElementById("channels") as HTMLDivElement).children[0].children; + if(this.guild!==this.localuser.lookingguild){ + return null; + }else if(this.parent){ + for(const thing of search){ + if(thing["all"]===this.parent){ + for(const thing2 of thing.children[1].children){ + if(thing2["all"]===this){ + return thing2; + } + } + } + } + }else{ + for(const thing of search){ + if(thing["all"]===this){ + return thing; + } + } + } + return null; + } + readbottom(){ + if(!this.hasunreads){ + return; + } + fetch(this.info.api+"/channels/"+this.snowflake+"/messages/"+this.lastmessageid+"/ack",{ + method: "POST", + headers: this.headers, + body: JSON.stringify({}) + }); + this.lastreadmessageid=this.lastmessageid; + this.guild.unreads(); + if(this.myhtml!==null){ + this.myhtml.classList.remove("cunread"); + } + } + coatDropDiv(div:HTMLDivElement,container:HTMLElement|boolean=false){ + div.addEventListener("dragenter", event=>{ + console.log("enter"); + event.preventDefault(); + }); - div.addEventListener("dragover", (event) => { - event.preventDefault(); - }); + div.addEventListener("dragover", event=>{ + event.preventDefault(); + }); - div.addEventListener("drop", (event) => { - const that=Channel.dragged[0]; - if(!that) return; - event.preventDefault(); - if(container){ - that.move_id=this.snowflake; - if(that.parent){ - that.parent.children.splice(that.parent.children.indexOf(that),1); - } - that.parent=this; - (container as HTMLElement).prepend(Channel.dragged[1] as HTMLDivElement); - this.children.unshift(that); - }else{ - console.log(this,Channel.dragged); - that.move_id=this.parent_id; - if(that.parent){ - that.parent.children.splice(that.parent.children.indexOf(that),1); - }else{ - this.guild.headchannels.splice(this.guild.headchannels.indexOf(that),1); - } - that.parent=this.parent; - if(that.parent){ - const build:Channel[]=[]; - for(let i=0;i{ + const that=Channel.dragged[0]; + if(!that)return; + event.preventDefault(); + if(container){ + that.move_id=this.snowflake; + if(that.parent){ + that.parent.children.splice(that.parent.children.indexOf(that),1); + } + that.parent=this; + (container as HTMLElement).prepend(Channel.dragged[1] as HTMLDivElement); + this.children.unshift(that); + }else{ + console.log(this,Channel.dragged); + that.move_id=this.parent_id; + if(that.parent){ + that.parent.children.splice(that.parent.children.indexOf(that),1); + }else{ + this.guild.headchannels.splice(this.guild.headchannels.indexOf(that),1); + } + that.parent=this.parent; + if(that.parent){ + const build:Channel[]=[]; + for(let i=0;i{ - fetch(this.info.api+"/channels/"+thisid,{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({ - "name": name, - "type": thistype, - "topic": topic, - "bitrate": 64000, - "user_limit": 0, - "nsfw": nsfw, - "flags": 0, - "rate_limit_per_user": 0 - }) - }) - console.log(full) - full.hide(); - }] - ] + return div; + } + createChannel(name:string,type:number){ + fetch(this.info.api+"/guilds/"+this.guild.id+"/channels",{ + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + type, + parent_id: this.snowflake, + permission_overwrites: [], + }) + }); + } + editChannel(){ + let name=this.name; + let topic=this.topic; + let nsfw=this.nsfw; + const thisid=this.snowflake; + const thistype=this.type; + const full=new Dialog( + ["hdiv", + ["vdiv", + ["textbox","Channel name:",this.name,function(this:HTMLInputElement){ + name=this.value; + }], + ["mdbox","Channel topic:",this.topic,function(this:HTMLTextAreaElement){ + topic=this.value; + }], + ["checkbox","NSFW Channel",this.nsfw,function(this:HTMLInputElement){ + nsfw=this.checked; + }], + ["button","","submit",()=>{ + fetch(this.info.api+"/channels/"+thisid,{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + name, + type: thistype, + topic, + bitrate: 64000, + user_limit: 0, + nsfw, + flags: 0, + rate_limit_per_user: 0 + }) + }); + console.log(full); + full.hide(); + }] + ] - ]); - full.show(); - console.log(full) - } - deleteChannel(){ - fetch(this.info.api+"/channels/"+this.snowflake,{ - method:"DELETE", - headers:this.headers - }) - } - setReplying(message:Message){ + ]); + full.show(); + console.log(full); + } + deleteChannel(){ + fetch(this.info.api+"/channels/"+this.snowflake,{ + method: "DELETE", + headers: this.headers + }); + } + setReplying(message:Message){ + if(this.replyingto?.div){ + this.replyingto.div.classList.remove("replying"); + } + this.replyingto=message; + if(!this.replyingto?.div)return; + console.log(message); + this.replyingto.div.classList.add("replying"); + this.makereplybox(); + } + makereplybox(){ + const replybox=document.getElementById("replybox") as HTMLElement; + if(this.replyingto){ + replybox.innerHTML=""; + const span=document.createElement("span"); + span.textContent="Replying to "+this.replyingto.author.username; + const X=document.createElement("button"); + X.onclick=_=>{ + if(this.replyingto?.div){ + this.replyingto.div.classList.remove("replying"); + } + replybox.classList.add("hideReplyBox"); + this.replyingto=null; + replybox.innerHTML=""; + }; + replybox.classList.remove("hideReplyBox"); + X.textContent="⦻"; + X.classList.add("cancelReply"); + replybox.append(span); + replybox.append(X); + }else{ + replybox.classList.add("hideReplyBox"); + } + } + async getmessage(id:string):Promise{ + const message=this.messages.get(id); + if(message){ + return message; + }else{ + const gety=await fetch(this.info.api+"/channels/"+this.snowflake+"/messages?limit=1&around="+id,{headers: this.headers}); + const json=await gety.json(); + return new Message(json[0],this); + } + } + static genid:number=0; + async getHTML(){ + const id=++Channel.genid; + if(this.guild!==this.localuser.lookingguild){ + this.guild.loadGuild(); + } + if(this.localuser.channelfocus){ + this.localuser.channelfocus.infinite.delete(); + } + if(this.localuser.channelfocus&&this.localuser.channelfocus.myhtml){ + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + if(this.myhtml){ + this.myhtml.classList.add("viewChannel"); + } + this.guild.prevchannel=this; + this.localuser.channelfocus=this; + const prom=this.infinite.delete(); + history.pushState(null, "","/channels/"+this.guild_id+"/"+this.snowflake); - if(this.replyingto?.div){ - this.replyingto.div.classList.remove("replying"); - } - this.replyingto=message; - if(!this.replyingto?.div) return; - console.log(message); - this.replyingto.div.classList.add("replying"); - this.makereplybox(); + this.localuser.pageTitle("#"+this.name); + const channelTopic=document.getElementById("channelTopic") as HTMLSpanElement; + if(this.topic){ + channelTopic.innerHTML=new MarkDown(this.topic, this).makeHTML().innerHTML; + channelTopic.removeAttribute("hidden"); + }else channelTopic.setAttribute("hidden",""); - } - makereplybox(){ - const replybox=document.getElementById("replybox") as HTMLElement; - if(this.replyingto){ - replybox.innerHTML=""; - const span=document.createElement("span"); - span.textContent="Replying to "+this.replyingto.author.username; - const X=document.createElement("button"); - X.onclick=_=>{ - if(this.replyingto?.div){ - this.replyingto.div.classList.remove("replying"); - } - replybox.classList.add("hideReplyBox"); - this.replyingto=null; - replybox.innerHTML=""; - } - replybox.classList.remove("hideReplyBox"); - X.textContent="⦻"; - X.classList.add("cancelReply"); - replybox.append(span); - replybox.append(X); - }else{ - replybox.classList.add("hideReplyBox"); - } - } - async getmessage(id:string):Promise{ - const message=this.messages.get(id); - if(message){ - return message; - }else{ - const gety=await fetch(this.info.api+"/channels/"+this.snowflake+"/messages?limit=1&around="+id,{headers:this.headers}) - const json=await gety.json(); - return new Message(json[0],this); - } - } - static genid:number=0; - async getHTML(){ - const id=++Channel.genid; - if(this.guild!==this.localuser.lookingguild){ - this.guild.loadGuild(); - } - if(this.localuser.channelfocus){ - this.localuser.channelfocus.infinite.delete(); - } - if(this.localuser.channelfocus&&this.localuser.channelfocus.myhtml){ - this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); - } - if(this.myhtml){ - this.myhtml.classList.add("viewChannel") - } - this.guild.prevchannel=this; - this.localuser.channelfocus=this; - const prom=this.infinite.delete(); - history.pushState(null, "","/channels/"+this.guild_id+"/"+this.snowflake); + const loading=document.getElementById("loadingdiv") as HTMLDivElement; + Channel.regenLoadingMessages(); + loading.classList.add("loading"); + await this.putmessages(); + await prom; + if(id!==Channel.genid){ + return; + } + this.makereplybox(); - this.localuser.pageTitle("#"+this.name); - const channelTopic=document.getElementById("channelTopic") as HTMLSpanElement; - if (this.topic) { - channelTopic.innerHTML=new MarkDown(this.topic, this).makeHTML().innerHTML; - channelTopic.removeAttribute("hidden"); - } else channelTopic.setAttribute("hidden",""); + await this.buildmessages(); + //loading.classList.remove("loading"); + console.log(this); + (document.getElementById("typebox") as HTMLDivElement).contentEditable=""+this.canMessage; + } + static regenLoadingMessages(){ + const loading=document.getElementById("loadingdiv") as HTMLDivElement; + loading.innerHTML=""; + for(let i=0;i<15;i++){ + const div=document.createElement("div"); + div.classList.add("loadingmessage"); + if(Math.random()<0.5){ + const pfp=document.createElement("div"); + pfp.classList.add("loadingpfp"); + const username=document.createElement("div"); + username.style.width=Math.floor(Math.random()*96*1.5+40)+"px"; + username.classList.add("loadingcontent"); + div.append(pfp,username); + } + const content=document.createElement("div"); + content.style.width=Math.floor(Math.random()*96*3+40)+"px"; + content.style.height=Math.floor(Math.random()*3+1)*20+"px"; + content.classList.add("loadingcontent"); + div.append(content); + loading.append(div); + } + } + lastmessage:Message|undefined; + async putmessages(){ + if(this.allthewayup){ + return; + } + if(this.lastreadmessageid&&this.messages.has(this.lastreadmessageid)){ + return; + } + const j=await fetch(this.info.api+"/channels/"+this.snowflake+"/messages?limit=100",{ + headers: this.headers, + }); - const loading=document.getElementById("loadingdiv") as HTMLDivElement; - Channel.regenLoadingMessages(); - loading.classList.add("loading"); - await this.putmessages(); - await prom; - if(id!==Channel.genid){ - return; - } - this.makereplybox(); + const response=await j.json(); + if(response.length!==100){ + this.allthewayup=true; + } + let prev:Message|undefined; + for(const thing of response){ + const message=new Message(thing,this); + if(prev){ + this.idToNext.set(message.id,prev.id); + this.idToPrev.set(prev.id,message.id); + }else{ + this.lastmessage=message; + this.lastmessageid=message.id; + } + prev=message; + if(this.messageids.get(message.snowflake)===undefined){ + this.messageids.set(message.snowflake,message); + } + } + } + delChannel(json:channeljson){ + const build:Channel[]=[]; + for(const thing of this.children){ + if(thing.id!==json.id){ + build.push(thing); + } + } + this.children=build; + } + async grabAfter(id:string){ + if(id===this.lastmessage?.id){ + return; + } + await fetch(this.info.api+"/channels/"+this.id+"/messages?limit=100&after="+id,{ + headers: this.headers + }).then(j=>{ + return j.json(); + }).then(response=>{ + let previd:string=id; + for(const i in response){ + let messager:Message; + let willbreak=false; + if(!SnowFlake.hasSnowFlakeFromID(response[i].id,Message)){ + messager=new Message(response[i],this); + }else{ + messager=SnowFlake.getSnowFlakeFromID(response[i].id,Message).getObject(); + willbreak=true; + } + this.idToPrev.set(messager.id,previd); + this.idToNext.set(previd,messager.id); + previd=messager.id; + this.messageids.set(messager.snowflake,messager); + if(willbreak){ + break; + } + } + //out.buildmessages(); + }); + } + topid:string; + async grabBefore(id:string){ + if(this.topid&&id===this.topid){ + return; + } - await this.buildmessages(); - //loading.classList.remove("loading"); - console.log(this); - (document.getElementById("typebox") as HTMLDivElement).contentEditable=""+this.canMessage; - } - static regenLoadingMessages(){ - const loading=document.getElementById("loadingdiv") as HTMLDivElement; - loading.innerHTML=""; - for(let i=0;i<15;i++){ - const div=document.createElement("div"); - div.classList.add("loadingmessage"); - if(Math.random()<.5){ - const pfp=document.createElement("div") - pfp.classList.add("loadingpfp"); - const username=document.createElement("div"); - username.style.width=Math.floor(Math.random()*96*1.5+40)+"px"; - username.classList.add("loadingcontent"); - div.append(pfp,username); - } - const content=document.createElement("div"); - content.style.width=Math.floor(Math.random()*96*3+40)+"px"; - content.style.height=Math.floor(Math.random()*3+1)*20+"px"; - content.classList.add("loadingcontent"); - div.append(content); - loading.append(div); - } - } - lastmessage:Message|undefined; - async putmessages(){ - if(this.allthewayup){return}; - if(this.lastreadmessageid&&this.messages.has(this.lastreadmessageid)){ - return - } - const j=await fetch(this.info.api+"/channels/"+this.snowflake+"/messages?limit=100",{ - headers: this.headers, - }); + await fetch(this.info.api+"/channels/"+this.id+"/messages?before="+id+"&limit=100",{ + headers: this.headers + }).then(j=>{ + return j.json(); + }).then((response:messagejson[])=>{ + if(response.length<100){ + this.allthewayup=true; + if(response.length===0){ + this.topid=id; + } + } + let previd=id; + for(const i in response){ + let messager:Message; + let willbreak=false; + if(this.messages.has(response[i].id)){ + console.log("flaky"); + messager=this.messages.get(response[i].id) as Message; + willbreak=true; + }else{ + messager=new Message(response[i],this); + } - const response=await j.json(); - if(response.length!==100){ - this.allthewayup=true; - } - let prev:Message|undefined=undefined; - for(const thing of response){ - const message=new Message(thing,this); - if(prev){ - this.idToNext.set(message.id,prev.id); - this.idToPrev.set(prev.id,message.id); - }else{ - this.lastmessage=message; - this.lastmessageid=message.id; - } - prev=message; - if(this.messageids.get(message.snowflake)===undefined){ - this.messageids.set(message.snowflake,message); - } - } - } - delChannel(json:channeljson){ - const build:Channel[]=[]; - for(const thing of this.children){ - if(thing.id!==json.id){ - build.push(thing) - } - } - this.children=build; - } - async grabAfter(id:string){ - if(id===this.lastmessage?.id){ - return; - } - await fetch(this.info.api+"/channels/"+this.id+"/messages?limit=100&after="+id,{ - headers:this.headers - }).then((j)=>{return j.json()}).then(response=>{ - let previd:string=id; - for(const i in response){ - let messager:Message; - let willbreak=false - if(!SnowFlake.hasSnowFlakeFromID(response[i].id,Message)){ - messager=new Message(response[i],this); - }else{ - messager=SnowFlake.getSnowFlakeFromID(response[i].id,Message).getObject(); - willbreak=true; - } - this.idToPrev.set(messager.id,previd); - this.idToNext.set(previd,messager.id); - previd=messager.id; - this.messageids.set(messager.snowflake,messager); - if(willbreak){ - break; - } - } - //out.buildmessages(); - }) - return; - } - topid:string; - async grabBefore(id:string){ - if(this.topid&&id===this.topid){ - return; - } + this.idToNext.set(messager.id,previd); + this.idToPrev.set(previd,messager.id); + previd=messager.id; + this.messageids.set(messager.snowflake,messager); - await fetch(this.info.api+"/channels/"+this.id+"/messages?before="+id+"&limit=100",{ - headers:this.headers - }).then((j)=>{return j.json()}).then((response:messagejson[])=>{ - if(response.length<100){ - this.allthewayup=true; - if(response.length===0){ - this.topid=id; - } - } - let previd=id; - for(const i in response){ - let messager:Message; - let willbreak=false; - if(this.messages.has(response[i].id)){ - console.log("flaky") - messager=this.messages.get(response[i].id) as Message; - willbreak=true; - }else{ - messager=new Message(response[i],this); - } - - this.idToNext.set(messager.id,previd); - this.idToPrev.set(previd,messager.id); - previd=messager.id; - this.messageids.set(messager.snowflake,messager); - - if(+i===response.length-1&&response.length<100){ - this.topid=previd; - } - if(willbreak){ - break; - } - } - }) - return; - } - /** + if(Number(i)===response.length-1&&response.length<100){ + this.topid=previd; + } + if(willbreak){ + break; + } + } + }); + } + /** * Please dont use this, its not implemented. * @deprecated * @todo **/ - async grabArround(id:string){//currently unused and no plans to use it yet - throw new Error("please don't call this, no one has implemented it :P") - } - async buildmessages(){ - /* + async grabArround(id:string){//currently unused and no plans to use it yet + throw new Error("please don't call this, no one has implemented it :P"); + } + async buildmessages(){ + /* if(((!this.lastmessage)||(!this.lastmessage.snowflake)||(!this.goBackIds(this.lastmessage.snowflake,50,false)))&&this.lastreadmessageid){ await this.grabAfter(this.lastreadmessageid.id); } */ - this.infinitefocus=false; - this.tryfocusinfinate(); - } - infinitefocus=false; - private async tryfocusinfinate(){ - if(this.infinitefocus) return; - this.infinitefocus=true; - const messages=document.getElementById("channelw") as HTMLDivElement; - for(const thing of messages.getElementsByClassName("messagecontainer")){ - thing.remove(); - } - const loading=document.getElementById("loadingdiv") as HTMLDivElement; - const removetitle=document.getElementById("removetitle"); - //messages.innerHTML=""; - let id:string|undefined; - if(this.lastreadmessageid&&this.messages.has(this.lastreadmessageid)){ - id=this.lastreadmessageid; - }else if(this.lastreadmessageid&&(id=this.findClosest(this.lastreadmessageid))){ + this.infinitefocus=false; + this.tryfocusinfinate(); + } + infinitefocus=false; + private async tryfocusinfinate(){ + if(this.infinitefocus)return; + this.infinitefocus=true; + const messages=document.getElementById("channelw") as HTMLDivElement; + for(const thing of messages.getElementsByClassName("messagecontainer")){ + thing.remove(); + } + const loading=document.getElementById("loadingdiv") as HTMLDivElement; + const removetitle=document.getElementById("removetitle"); + //messages.innerHTML=""; + let id:string|undefined; + if(this.lastreadmessageid&&this.messages.has(this.lastreadmessageid)){ + id=this.lastreadmessageid; + }else if(this.lastreadmessageid&&(id=this.findClosest(this.lastreadmessageid))){ - }else if(this.lastmessageid&&this.messages.has(this.lastmessageid)){ - id=this.goBackIds(this.lastmessageid,50); - } - if(!id){ - if(!removetitle){ - const title=document.createElement("h2"); - title.id="removetitle"; - title.textContent="No messages appear to be here, be the first to say something!" - title.classList.add("titlespace"); - messages.append(title); - } - this.infinitefocus=false; - loading.classList.remove("loading"); - return; - }else if(removetitle){ - removetitle.remove(); - } - messages.append(await this.infinite.getDiv(id)); - this.infinite.updatestuff(); - this.infinite.watchForChange().then(async _=>{ - //await new Promise(resolve => setTimeout(resolve, 0)); - this.infinite.focus(id,false);//if someone could figure out how to make this work correctly without this, that's be great :P - loading.classList.remove("loading"); - }); - //this.infinite.focus(id.id,false); + }else if(this.lastmessageid&&this.messages.has(this.lastmessageid)){ + id=this.goBackIds(this.lastmessageid,50); + } + if(!id){ + if(!removetitle){ + const title=document.createElement("h2"); + title.id="removetitle"; + title.textContent="No messages appear to be here, be the first to say something!"; + title.classList.add("titlespace"); + messages.append(title); + } + this.infinitefocus=false; + loading.classList.remove("loading"); + return; + }else if(removetitle){ + removetitle.remove(); + } + messages.append(await this.infinite.getDiv(id)); + this.infinite.updatestuff(); + this.infinite.watchForChange().then(async _=>{ + //await new Promise(resolve => setTimeout(resolve, 0)); + this.infinite.focus(id,false);//if someone could figure out how to make this work correctly without this, that's be great :P + loading.classList.remove("loading"); + }); + //this.infinite.focus(id.id,false); + } + private goBackIds(id:string,back:number,returnifnotexistant=true):string|undefined{ + while(back!==0){ + const nextid=this.idToPrev.get(id); + if(nextid){ + id=nextid; + back--; + }else{ + if(returnifnotexistant){ + break; + }else{ + return undefined; + } + } + } + return id; + } + private findClosest(id:string|undefined){ + if(!this.lastmessageid||!id)return; + let flake:string|undefined=this.lastmessageid; + const time=Number((BigInt(id)>>22n)+1420070400000n); + let flaketime=Number((BigInt(flake)>>22n)+1420070400000n); + while(flake&&time>22n)+1420070400000n); - let flaketime=Number((BigInt(flake)>>22n)+1420070400000n); - while(flake&&time>22n)+1420070400000n); - } - return flake; - } - updateChannel(json:channeljson){ - this.type=json.type; - this.name=json.name; - this.parent_id=SnowFlake.getSnowFlakeFromID(json.parent_id,Channel); - this.parent=null; - this.children=[]; - this.guild_id=json.guild_id; - this.messageids=new Map(); - this.permission_overwrites=new Map(); - for(const thing of json.permission_overwrites){ - if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){continue;}; - this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny)); - const permisions=this.permission_overwrites.get(thing.id); - if(permisions){ - this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id,Role),permisions]); - } - } - this.topic=json.topic; - this.nsfw=json.nsfw; - } - typingstart(){ - if(this.typing>new Date().getTime()){ - return; - } - this.typing=new Date().getTime()+6000; - fetch(this.info.api+"/channels/"+this.snowflake+"/typing",{ - method:"POST", - headers:this.headers - }) - } - get notification(){ - let notinumber:number|null=this.message_notifications; - if(+notinumber===3){notinumber=null;} - notinumber??=this.guild.message_notifications; - switch(+notinumber){ - case 0: - return "all"; - case 1: - return "mentions"; - case 2: - return "none"; - case 3: - return "default"; - } - } - async sendMessage(content:string,{attachments=[],embeds=[],replyingto=null}: + if(!flake){ + return; + } + flaketime=Number((BigInt(flake)>>22n)+1420070400000n); + } + return flake; + } + updateChannel(json:channeljson){ + this.type=json.type; + this.name=json.name; + this.parent_id=SnowFlake.getSnowFlakeFromID(json.parent_id,Channel); + this.parent=null; + this.children=[]; + this.guild_id=json.guild_id; + this.messageids=new Map(); + this.permission_overwrites=new Map(); + for(const thing of json.permission_overwrites){ + if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){ + continue; + } + this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny)); + const permisions=this.permission_overwrites.get(thing.id); + if(permisions){ + this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id,Role),permisions]); + } + } + this.topic=json.topic; + this.nsfw=json.nsfw; + } + typingstart(){ + if(this.typing>Date.now()){ + return; + } + this.typing=Date.now()+6000; + fetch(this.info.api+"/channels/"+this.snowflake+"/typing",{ + method: "POST", + headers: this.headers + }); + } + get notification(){ + let notinumber:number|null=this.message_notifications; + if(Number(notinumber)===3){ + notinumber=null; + } + notinumber??=this.guild.message_notifications; + switch(Number(notinumber)){ + case 0: + return"all"; + case 1: + return"mentions"; + case 2: + return"none"; + case 3: + return"default"; + } + } + async sendMessage(content:string,{attachments=[],embeds=[],replyingto=null}: {attachments:Blob[],embeds,replyingto:Message|null}){ - let replyjson:any; - if(replyingto){ - replyjson= + let replyjson:any; + if(replyingto){ + replyjson= { - "guild_id":replyingto.guild.id, - "channel_id": replyingto.channel.id, - "message_id": replyingto.id, + guild_id: replyingto.guild.id, + channel_id: replyingto.channel.id, + message_id: replyingto.id, }; - }; - if(attachments.length===0){ - const body={ - content:content, - nonce:Math.floor(Math.random()*1000000000), - message_reference:undefined - }; - if(replyjson){ - body.message_reference=replyjson; - } - return await fetch(this.info.api+"/channels/"+this.snowflake+"/messages",{ - method:"POST", - headers:this.headers, - body:JSON.stringify(body) - }) - }else{ - const formData = new FormData(); - const body={ - content:content, - nonce:Math.floor(Math.random()*1000000000), - message_reference:undefined - } - if(replyjson){ - body.message_reference=replyjson; - } - formData.append('payload_json', JSON.stringify(body)); - for(const i in attachments){ - formData.append("files["+i+"]",attachments[i]); - } - return await fetch(this.info.api+"/channels/"+this.snowflake+"/messages", { - method: 'POST', - body: formData, - headers:{"Authorization":this.headers.Authorization} - }); - } - } - messageCreate(messagep:messageCreateJson):void{ - if(!this.hasPermission("VIEW_CHANNEL")){return} - const messagez=new Message(messagep.d,this); - this.lastmessage=messagez; - if(this.lastmessageid){ - this.idToNext.set(this.lastmessageid,messagez.id); - this.idToPrev.set(messagez.id,this.lastmessageid); - } + } + if(attachments.length===0){ + const body={ + content, + nonce: Math.floor(Math.random()*1000000000), + message_reference: undefined + }; + if(replyjson){ + body.message_reference=replyjson; + } + return await fetch(this.info.api+"/channels/"+this.snowflake+"/messages",{ + method: "POST", + headers: this.headers, + body: JSON.stringify(body) + }); + }else{ + const formData = new FormData(); + const body={ + content, + nonce: Math.floor(Math.random()*1000000000), + message_reference: undefined + }; + if(replyjson){ + body.message_reference=replyjson; + } + formData.append("payload_json", JSON.stringify(body)); + for(const i in attachments){ + formData.append("files["+i+"]",attachments[i]); + } + return await fetch(this.info.api+"/channels/"+this.snowflake+"/messages", { + method: "POST", + body: formData, + headers: {Authorization: this.headers.Authorization} + }); + } + } + messageCreate(messagep:messageCreateJson):void{ + if(!this.hasPermission("VIEW_CHANNEL")){ + return; + } + const messagez=new Message(messagep.d,this); + this.lastmessage=messagez; + if(this.lastmessageid){ + this.idToNext.set(this.lastmessageid,messagez.id); + this.idToPrev.set(messagez.id,this.lastmessageid); + } - this.lastmessageid=messagez.id; - this.messageids.set(messagez.snowflake,messagez); + this.lastmessageid=messagez.id; + this.messageids.set(messagez.snowflake,messagez); - if(messagez.author===this.localuser.user){ - this.lastreadmessageid=messagez.id; - if(this.myhtml){ - this.myhtml.classList.remove("cunread"); - } - }else{ - if(this.myhtml){ - this.myhtml.classList.add("cunread"); - } - } - this.guild.unreads(); - if(this===this.localuser.channelfocus){ - if(!this.infinitefocus){this.tryfocusinfinate();} - this.infinite.addedBottom(); - } - if(messagez.author===this.localuser.user){ - return; - } - if(this.localuser.lookingguild?.prevchannel===this&&document.hasFocus()){ - return; - } - if(this.notification==="all"){ - this.notify(messagez); - }else if(this.notification==="mentions"&&messagez.mentionsuser(this.localuser.user)){ - this.notify(messagez); - } + if(messagez.author===this.localuser.user){ + this.lastreadmessageid=messagez.id; + if(this.myhtml){ + this.myhtml.classList.remove("cunread"); + } + }else{ + if(this.myhtml){ + this.myhtml.classList.add("cunread"); + } + } + this.guild.unreads(); + if(this===this.localuser.channelfocus){ + if(!this.infinitefocus){ + this.tryfocusinfinate(); + } + this.infinite.addedBottom(); + } + if(messagez.author===this.localuser.user){ + return; + } + if(this.localuser.lookingguild?.prevchannel===this&&document.hasFocus()){ + return; + } + if(this.notification==="all"){ + this.notify(messagez); + }else if(this.notification==="mentions"&&messagez.mentionsuser(this.localuser.user)){ + this.notify(messagez); + } + } + notititle(message:Message):string{ + return message.author.username+" > "+this.guild.properties.name+" > "+this.name; + } + notify(message:Message,deep=0){ + Voice.noises(Voice.getNotificationSound()); + if(!("Notification" in window)){ - } - notititle(message:Message):string{ - return message.author.username+" > "+this.guild.properties.name+" > "+this.name; - } - notify(message:Message,deep=0){ - Voice.noises(Voice.getNotificationSound()); - if (!("Notification" in window)) { - - } else if (Notification.permission === "granted") { - let noticontent:string|undefined|null=message.content.textContent; - if(message.embeds[0]){ - noticontent||=message.embeds[0].json.title; - noticontent||=message.content.textContent; - } - noticontent||="Blank Message"; - let imgurl:null|string=null; - const images=message.getimages(); - if(images.length){ - const image = images[0]; - if(image.proxy_url){ - imgurl||=image.proxy_url; - } - imgurl||=image.url; - } - const notification = new Notification(this.notititle(message),{ - body:noticontent, - icon:message.author.getpfpsrc(), - image:imgurl, - }); - notification.addEventListener("click",_=>{ - window.focus(); - this.getHTML(); - }) - } else if (Notification.permission !== "denied") { - Notification.requestPermission().then(() => { - if(deep===3){return}; - this.notify(message,deep+1); - }); - } - } - async addRoleToPerms(role:Role){ - await fetch(this.info.api+"/channels/"+this.snowflake+"/permissions/"+role.snowflake,{ - method:"PUT", - headers:this.headers, - body:JSON.stringify({ - allow:"0", - deny:"0", - id:role.id, - type:0 - }) - }) - const perm=new Permissions("0","0"); - this.permission_overwrites.set(role.id,perm); - this.permission_overwritesar.push([role.snowflake,perm]); - } - async updateRolePermissions(id:string,perms:Permissions){ - const permission=this.permission_overwrites.get(id); - if(permission){ - permission.allow=perms.allow; - permission.deny=perms.deny; - await fetch(this.info.api+"/channels/"+this.snowflake+"/permissions/"+id,{ - method:"PUT", - headers:this.headers, - body:JSON.stringify({ - allow:permission.allow.toString(), - deny:permission.deny.toString(), - id:id, - type:0 - }) - }) - } - } + }else if(Notification.permission === "granted"){ + let noticontent:string|undefined|null=message.content.textContent; + if(message.embeds[0]){ + noticontent||=message.embeds[0].json.title; + noticontent||=message.content.textContent; + } + noticontent||="Blank Message"; + let imgurl:null|string=null; + const images=message.getimages(); + if(images.length){ + const image = images[0]; + if(image.proxy_url){ + imgurl||=image.proxy_url; + } + imgurl||=image.url; + } + const notification = new Notification(this.notititle(message),{ + body: noticontent, + icon: message.author.getpfpsrc(), + image: imgurl, + }); + notification.addEventListener("click",_=>{ + window.focus(); + this.getHTML(); + }); + }else if(Notification.permission !== "denied"){ + Notification.requestPermission().then(()=>{ + if(deep===3){ + return; + } + this.notify(message,deep+1); + }); + } + } + async addRoleToPerms(role:Role){ + await fetch(this.info.api+"/channels/"+this.snowflake+"/permissions/"+role.snowflake,{ + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: "0", + deny: "0", + id: role.id, + type: 0 + }) + }); + const perm=new Permissions("0","0"); + this.permission_overwrites.set(role.id,perm); + this.permission_overwritesar.push([role.snowflake,perm]); + } + async updateRolePermissions(id:string,perms:Permissions){ + const permission=this.permission_overwrites.get(id); + if(permission){ + permission.allow=perms.allow; + permission.deny=perms.deny; + await fetch(this.info.api+"/channels/"+this.snowflake+"/permissions/"+id,{ + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: permission.allow.toString(), + deny: permission.deny.toString(), + id, + type: 0 + }) + }); + } + } } Channel.setupcontextmenu(); -export {Channel}; +export{Channel}; diff --git a/webpage/contextmenu.ts b/webpage/contextmenu.ts index 2dca231..81ba311 100644 --- a/webpage/contextmenu.ts +++ b/webpage/contextmenu.ts @@ -1,88 +1,88 @@ class Contextmenu{ - static currentmenu; - name:string; - buttons:[string,(this:x,arg:y,e:MouseEvent)=>void,string|null,(this:x,arg:y)=>boolean,(this:x,arg:y)=>boolean,string][]; - div:HTMLDivElement; - static setup(){ - Contextmenu.currentmenu=""; - document.addEventListener('click', function(event) { - if(Contextmenu.currentmenu==""){ - return; - } - if (!Contextmenu.currentmenu.contains(event.target)) { - Contextmenu.currentmenu.remove(); - Contextmenu.currentmenu=""; - } - }); - } - constructor(name:string){ - this.name=name; - this.buttons=[] - } - addbutton(text:string,onclick:(this:x,arg:y,e:MouseEvent)=>void,img:null|string=null,shown:(this:x,arg:y)=>boolean=_=>true,enabled:(this:x,arg:y)=>boolean=_=>true){ - this.buttons.push([text,onclick,img,shown,enabled,"button"]); - return {}; - } - addsubmenu(text:string,onclick:(this:x,arg:y,e:MouseEvent)=>void,img=null,shown:(this:x,arg:y)=>boolean=_=>true,enabled:(this:x,arg:y)=>boolean=_=>true){ - this.buttons.push([text,onclick,img,shown,enabled,"submenu"]) - return {}; - } - makemenu(x:number,y:number,addinfo:any,other:y){ - const div=document.createElement("div"); - div.classList.add("contextmenu","flexttb"); + static currentmenu; + name:string; + buttons:[string,(this:x,arg:y,e:MouseEvent)=>void,string|null,(this:x,arg:y)=>boolean,(this:x,arg:y)=>boolean,string][]; + div:HTMLDivElement; + static setup(){ + Contextmenu.currentmenu=""; + document.addEventListener("click", event=>{ + if(Contextmenu.currentmenu==""){ + return; + } + if(!Contextmenu.currentmenu.contains(event.target)){ + Contextmenu.currentmenu.remove(); + Contextmenu.currentmenu=""; + } + }); + } + constructor(name:string){ + this.name=name; + this.buttons=[]; + } + addbutton(text:string,onclick:(this:x,arg:y,e:MouseEvent)=>void,img:null|string=null,shown:(this:x,arg:y)=>boolean=_=>true,enabled:(this:x,arg:y)=>boolean=_=>true){ + this.buttons.push([text,onclick,img,shown,enabled,"button"]); + return{}; + } + addsubmenu(text:string,onclick:(this:x,arg:y,e:MouseEvent)=>void,img=null,shown:(this:x,arg:y)=>boolean=_=>true,enabled:(this:x,arg:y)=>boolean=_=>true){ + this.buttons.push([text,onclick,img,shown,enabled,"submenu"]); + return{}; + } + makemenu(x:number,y:number,addinfo:any,other:y){ + const div=document.createElement("div"); + div.classList.add("contextmenu","flexttb"); - let visibleButtons=0; - for(const thing of this.buttons){ - if(!thing[3].bind(addinfo)(other))continue; - visibleButtons++; + let visibleButtons=0; + for(const thing of this.buttons){ + if(!thing[3].bind(addinfo)(other))continue; + visibleButtons++; - const intext=document.createElement("button") - intext.disabled=!thing[4].bind(addinfo)(other); - intext.classList.add("contextbutton") - intext.textContent=thing[0] - console.log(thing) - if(thing[5]==="button"||thing[5]==="submenu"){ - intext.onclick=thing[1].bind(addinfo,other); - } + const intext=document.createElement("button"); + intext.disabled=!thing[4].bind(addinfo)(other); + intext.classList.add("contextbutton"); + intext.textContent=thing[0]; + console.log(thing); + if(thing[5]==="button"||thing[5]==="submenu"){ + intext.onclick=thing[1].bind(addinfo,other); + } - div.appendChild(intext); - } - if (visibleButtons == 0) return; + div.appendChild(intext); + } + if(visibleButtons == 0)return; - if(Contextmenu.currentmenu!=""){ - Contextmenu.currentmenu.remove(); - } - div.style.top = y+'px'; - div.style.left = x+'px'; - document.body.appendChild(div); - Contextmenu.keepOnScreen(div); - console.log(div) - Contextmenu.currentmenu=div; - return this.div; - } - bindContextmenu(obj:HTMLElement,addinfo:x,other:y){ - const func=(event) => { - event.preventDefault(); - event.stopImmediatePropagation(); - this.makemenu(event.clientX,event.clientY,addinfo,other); - } - obj.addEventListener("contextmenu", func); - return func; - } - static keepOnScreen(obj:HTMLElement){ - const html = document.documentElement.getBoundingClientRect(); - const docheight=html.height - const docwidth=html.width - const box=obj.getBoundingClientRect(); - console.log(box,docheight,docwidth); - if(box.right>docwidth){ - console.log("test") - obj.style.left = docwidth-box.width+'px'; - } - if(box.bottom>docheight){ - obj.style.top = docheight-box.height+'px'; - } - } + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); + } + div.style.top = y+"px"; + div.style.left = x+"px"; + document.body.appendChild(div); + Contextmenu.keepOnScreen(div); + console.log(div); + Contextmenu.currentmenu=div; + return this.div; + } + bindContextmenu(obj:HTMLElement,addinfo:x,other:y){ + const func=event=>{ + event.preventDefault(); + event.stopImmediatePropagation(); + this.makemenu(event.clientX,event.clientY,addinfo,other); + }; + obj.addEventListener("contextmenu", func); + return func; + } + static keepOnScreen(obj:HTMLElement){ + const html = document.documentElement.getBoundingClientRect(); + const docheight=html.height; + const docwidth=html.width; + const box=obj.getBoundingClientRect(); + console.log(box,docheight,docwidth); + if(box.right>docwidth){ + console.log("test"); + obj.style.left = docwidth-box.width+"px"; + } + if(box.bottom>docheight){ + obj.style.top = docheight-box.height+"px"; + } + } } Contextmenu.setup(); -export {Contextmenu as Contextmenu} +export{Contextmenu}; diff --git a/webpage/dialog.ts b/webpage/dialog.ts index cbb8ee1..8ff24cb 100644 --- a/webpage/dialog.ts +++ b/webpage/dialog.ts @@ -1,251 +1,254 @@ class Dialog{ - layout; - onclose: Function; - onopen: Function; - html:HTMLDivElement; - background: HTMLDivElement; - constructor(layout,onclose=_=>{},onopen=_=>{}){ - this.layout=layout; - this.onclose=onclose; - this.onopen=onopen; - const div=document.createElement("div"); - div.appendChild(this.tohtml(layout)) - this.html=div; - this.html.classList.add("centeritem"); - if(!(layout[0]==="img")){ + layout; + onclose: Function; + onopen: Function; + html:HTMLDivElement; + background: HTMLDivElement; + constructor(layout,onclose=_=>{},onopen=_=>{}){ + this.layout=layout; + this.onclose=onclose; + this.onopen=onopen; + const div=document.createElement("div"); + div.appendChild(this.tohtml(layout)); + this.html=div; + this.html.classList.add("centeritem"); + if(!(layout[0]==="img")){ + this.html.classList.add("nonimagecenter"); + } + } + tohtml(array:any[]){ + switch(array[0]){ + case"img": + const img=document.createElement("img"); + img.src=array[1]; + if(array[2]!=undefined){ + if(array[2].length==2){ + img.width=array[2][0]; + img.height=array[2][1]; + }else if(array[2][0]=="fit"){ + img.classList.add("imgfit"); + } + } + return img; + case"hdiv": + const hdiv=document.createElement("table"); + const tr=document.createElement("tr"); + hdiv.appendChild(tr); - this.html.classList.add("nonimagecenter"); - } - } - tohtml(array:any[]){ - switch(array[0]){ - case "img": - const img=document.createElement("img"); - img.src=array[1]; - if(array[2]!=undefined){ - if(array[2].length==2){ - img.width=array[2][0]; - img.height=array[2][1]; - }else if(array[2][0]=="fit"){ - img.classList.add("imgfit") - } - } - return img; - case "hdiv": - const hdiv=document.createElement("table"); - const tr=document.createElement("tr"); - hdiv.appendChild(tr) + for(const thing of array){ + if(thing==="hdiv"){ + continue; + } + const td=document.createElement("td"); + td.appendChild(this.tohtml(thing)); + tr.appendChild(td); + } + return hdiv; + case"vdiv": + const vdiv=document.createElement("table"); + for(const thing of array){ + if(thing==="vdiv"){ + continue; + } + const tr=document.createElement("tr"); + tr.appendChild(this.tohtml(thing)); + vdiv.appendChild(tr); + } + return vdiv; + case"checkbox": + { + const div=document.createElement("div"); + const checkbox = document.createElement("input"); + div.appendChild(checkbox); + const label=document.createElement("span"); + checkbox.checked=array[2]; + label.textContent=array[1]; + div.appendChild(label); + checkbox.addEventListener("change",array[3]); + checkbox.type = "checkbox"; + return div; + } + case"button": + { + const div=document.createElement("div"); + const input = document.createElement("button"); - for(const thing of array){ - if(thing==="hdiv"){continue;} - const td=document.createElement("td"); - td.appendChild(this.tohtml(thing)); - tr.appendChild(td); - } - return hdiv; - case "vdiv": - const vdiv=document.createElement("table"); - for(const thing of array){ - if(thing==="vdiv"){continue;} - const tr=document.createElement("tr"); - tr.appendChild(this.tohtml(thing)); - vdiv.appendChild(tr); - } - return vdiv; - case "checkbox": - { - const div=document.createElement("div"); - const checkbox = document.createElement('input'); - div.appendChild(checkbox) - const label=document.createElement("span"); - checkbox.checked=array[2]; - label.textContent=array[1]; - div.appendChild(label); - checkbox.addEventListener("change",array[3]); - checkbox.type = "checkbox"; - return div; - } - case "button": - { - const div=document.createElement("div"); - const input = document.createElement('button'); + const label=document.createElement("span"); + input.textContent=array[2]; + label.textContent=array[1]; + div.appendChild(label); + div.appendChild(input); + input.addEventListener("click",array[3]); + return div; + } + case"mdbox": + { + const div=document.createElement("div"); + const input=document.createElement("textarea"); + input.value=array[2]; + const label=document.createElement("span"); + label.textContent=array[1]; + input.addEventListener("input",array[3]); + div.appendChild(label); + div.appendChild(document.createElement("br")); + div.appendChild(input); + return div; + } + case"textbox": + { + const div=document.createElement("div"); + const input=document.createElement("input"); + input.value=array[2]; + input.type="text"; + const label=document.createElement("span"); + label.textContent=array[1]; + console.log(array[3]); + input.addEventListener("input",array[3]); + div.appendChild(label); + div.appendChild(input); + return div; + } + case"fileupload": + { + const div=document.createElement("div"); + const input=document.createElement("input"); + input.type="file"; + const label=document.createElement("span"); + label.textContent=array[1]; + div.appendChild(label); + div.appendChild(input); + input.addEventListener("change",array[2]); + console.log(array); + return div; + } + case"text":{ + const span =document.createElement("span"); + span.textContent=array[1]; + return span; + } + case"title":{ + const span =document.createElement("span"); + span.classList.add("title"); + span.textContent=array[1]; + return span; + } + case"radio":{ + const div=document.createElement("div"); + const fieldset=document.createElement("fieldset"); + fieldset.addEventListener("change",()=>{ + let i=-1; + for(const thing of fieldset.children){ + i++; + if(i===0){ + continue; + } + const checkbox = thing.children[0].children[0] as HTMLInputElement; + if(checkbox.checked){ + array[3](checkbox.value); + } + } + }); + const legend=document.createElement("legend"); + legend.textContent=array[1]; + fieldset.appendChild(legend); + let i=0; + for(const thing of array[2]){ + const div=document.createElement("div"); + const input=document.createElement("input"); + input.classList.add("radio"); + input.type="radio"; + input.name=array[1]; + input.value=thing; + if(i===array[4]){ + input.checked=true; + } + const label=document.createElement("label"); - const label=document.createElement("span"); - input.textContent=array[2]; - label.textContent=array[1]; - div.appendChild(label); - div.appendChild(input) - input.addEventListener("click",array[3]); - return div; - } - case "mdbox": - { - const div=document.createElement("div"); - const input=document.createElement("textarea"); - input.value=array[2]; - const label=document.createElement("span"); - label.textContent=array[1]; - input.addEventListener("input",array[3]); - div.appendChild(label); - div.appendChild(document.createElement("br")); - div.appendChild(input); - return div; - } - case "textbox": - { - const div=document.createElement("div"); - const input=document.createElement("input"); - input.value=array[2]; - input.type="text"; - const label=document.createElement("span"); - label.textContent=array[1]; - console.log(array[3]) - input.addEventListener("input",array[3]); - div.appendChild(label); - div.appendChild(input); - return div; - } - case "fileupload": - { - const div=document.createElement("div"); - const input=document.createElement("input"); - input.type="file"; - const label=document.createElement("span"); - label.textContent=array[1]; - div.appendChild(label); - div.appendChild(input); - input.addEventListener("change",array[2]); - console.log(array) - return div; - } - case "text":{ - const span =document.createElement("span"); - span.textContent=array[1]; - return span; - } - case "title":{ - const span =document.createElement("span"); - span.classList.add("title") - span.textContent=array[1]; - return span; - } - case "radio":{ - const div=document.createElement("div"); - const fieldset=document.createElement("fieldset"); - fieldset.addEventListener("change",function(){ - let i=-1; - for(const thing of fieldset.children){ - i++; - if(i===0){ - continue; - } - const checkbox = thing.children[0].children[0] as HTMLInputElement; - if(checkbox.checked){ - array[3](checkbox.value); - } - } - }); - const legend=document.createElement("legend"); - legend.textContent=array[1]; - fieldset.appendChild(legend); - let i=0; - for(const thing of array[2]){ - const div=document.createElement("div"); - const input=document.createElement("input"); - input.classList.add("radio") - input.type="radio"; - input.name=array[1]; - input.value=thing; - if(i===array[4]){ - input.checked=true; - } - const label=document.createElement("label"); + label.appendChild(input); + const span=document.createElement("span"); + span.textContent=thing; + label.appendChild(span); + div.appendChild(label); + fieldset.appendChild(div); + i++; + } + div.appendChild(fieldset); + return div; + } + case"html": + return array[1]; - label.appendChild(input); - const span=document.createElement("span"); - span.textContent=thing; - label.appendChild(span); - div.appendChild(label); - fieldset.appendChild(div); - i++ - } - div.appendChild(fieldset); - return div; - } - case "html":{ - return array[1]; - } - case "select":{ - const div=document.createElement("div"); - const label=document.createElement("label"); - const select=document.createElement("select"); + case"select":{ + const div=document.createElement("div"); + const label=document.createElement("label"); + const select=document.createElement("select"); - label.textContent=array[1]; - div.append(label); - div.appendChild(select); - for(const thing of array[2]){ - const option = document.createElement("option"); - option.textContent=thing; - select.appendChild(option); - } - select.selectedIndex=array[4]; - select.addEventListener("change",array[3]); - return div; - } - case "tabs":{ - const table=document.createElement("table"); - const tabs=document.createElement("tr"); - tabs.classList.add("tabbed-head"); - table.appendChild(tabs); - const td=document.createElement("td"); - tabs.appendChild(td); - const content=document.createElement("tr"); - content.classList.add("tabbed-content"); - table.appendChild(content); + label.textContent=array[1]; + div.append(label); + div.appendChild(select); + for(const thing of array[2]){ + const option = document.createElement("option"); + option.textContent=thing; + select.appendChild(option); + } + select.selectedIndex=array[4]; + select.addEventListener("change",array[3]); + return div; + } + case"tabs":{ + const table=document.createElement("table"); + const tabs=document.createElement("tr"); + tabs.classList.add("tabbed-head"); + table.appendChild(tabs); + const td=document.createElement("td"); + tabs.appendChild(td); + const content=document.createElement("tr"); + content.classList.add("tabbed-content"); + table.appendChild(content); - let shown; - for(const thing of array[1]){ - - const button=document.createElement("button"); - button.textContent=thing[0]; - td.appendChild(button); + let shown; + for(const thing of array[1]){ + const button=document.createElement("button"); + button.textContent=thing[0]; + td.appendChild(button); - const tdcontent=document.createElement("td"); - tdcontent.colSpan=array[1].length; - tdcontent.appendChild(this.tohtml(thing[1])); - content.appendChild(tdcontent); - if(!shown){ - shown=tdcontent; - }else{ - tdcontent.hidden=true; - } - button.addEventListener("click",_=>{ - shown.hidden=true; - tdcontent.hidden=false; - shown=tdcontent; - }) - } - return table; - } - default: - console.error("can't find element:"+array[0]," full element:"+array) - return; - } - } - 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); - } + const tdcontent=document.createElement("td"); + tdcontent.colSpan=array[1].length; + tdcontent.appendChild(this.tohtml(thing[1])); + content.appendChild(tdcontent); + if(!shown){ + shown=tdcontent; + }else{ + tdcontent.hidden=true; + } + button.addEventListener("click",_=>{ + shown.hidden=true; + tdcontent.hidden=false; + shown=tdcontent; + }); + } + return table; + } + default: + console.error("can't find element:"+array[0]," full element:"+array); + } + } + show(){ + this.onopen(); + console.log("fullscreen"); + this.background=document.createElement("div"); + this.background.classList.add("background"); + document.body.appendChild(this.background); + document.body.appendChild(this.html); + this.background.onclick = _=>{ + this.hide(); + }; + } + hide(){ + document.body.removeChild(this.background); + document.body.removeChild(this.html); + } } -export {Dialog}; +export{Dialog}; diff --git a/webpage/direct.ts b/webpage/direct.ts index 92a18f6..685d3f1 100644 --- a/webpage/direct.ts +++ b/webpage/direct.ts @@ -1,61 +1,61 @@ -import {Guild} from "./guild.js"; -import { Channel } from "./channel.js"; -import { Message } from "./message.js"; -import { Localuser } from "./localuser.js"; -import {User} from "./user.js"; -import { Member } from "./member.js"; -import { SnowFlake } from "./snowflake.js"; -import { dirrectjson, memberjson } from "./jsontypes.js"; -import { Permissions } from "./permissions.js"; +import{Guild}from"./guild.js"; +import{ Channel }from"./channel.js"; +import{ Message }from"./message.js"; +import{ Localuser }from"./localuser.js"; +import{User}from"./user.js"; +import{ Member }from"./member.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ dirrectjson, memberjson }from"./jsontypes.js"; +import{ Permissions }from"./permissions.js"; class Direct extends Guild{ - constructor(json:dirrectjson[],owner:Localuser){ - super(-1,owner,null); - this.message_notifications=0; - this.owner=owner; - if(!this.localuser){ - console.error("Owner was not included, please fix") - } - this.headers=this.localuser.headers; - this.channels=[]; - this.channelids={}; - this.snowflake=new SnowFlake("@me",this); - this.properties={}; - this.roles=[]; - this.roleids=new Map(); - this.prevchannel=undefined; - this.properties.name="Direct Messages"; - for(const thing of json){ - const temp=new Group(thing,this); - this.channels.push(temp); - this.channelids[temp.id]=temp; - } - this.headchannels=this.channels; - } - createChannelpac(json){ - const thischannel=new Group(json,this); - this.channelids[json.id]=thischannel; - this.channels.push(thischannel); - this.calculateReorder(); - this.printServers(); - } - giveMember(_member:memberjson){ - console.error("not a real guild, can't give member object") - } - getRole(ID:string){ - return null; - } - hasRole(r:string){ - return false; - } - isAdmin(){ - return false; - } - unreaddms(){ - for(const thing of this.channels){ - (thing as Group).unreads(); - } - } + constructor(json:dirrectjson[],owner:Localuser){ + super(-1,owner,null); + this.message_notifications=0; + this.owner=owner; + if(!this.localuser){ + console.error("Owner was not included, please fix"); + } + this.headers=this.localuser.headers; + this.channels=[]; + this.channelids={}; + this.snowflake=new SnowFlake("@me",this); + this.properties={}; + this.roles=[]; + this.roleids=new Map(); + this.prevchannel=undefined; + this.properties.name="Direct Messages"; + for(const thing of json){ + const temp=new Group(thing,this); + this.channels.push(temp); + this.channelids[temp.id]=temp; + } + this.headchannels=this.channels; + } + createChannelpac(json){ + const thischannel=new Group(json,this); + this.channelids[json.id]=thischannel; + this.channels.push(thischannel); + this.calculateReorder(); + this.printServers(); + } + giveMember(_member:memberjson){ + console.error("not a real guild, can't give member object"); + } + getRole(ID:string){ + return null; + } + hasRole(r:string){ + return false; + } + isAdmin(){ + return false; + } + unreaddms(){ + for(const thing of this.channels){ + (thing as Group).unreads(); + } + } } const dmPermissions = new Permissions("0"); @@ -82,139 +82,139 @@ dmPermissions.setPermission("STREAM",1); dmPermissions.setPermission("USE_VAD",1); class Group extends Channel{ - user:User; - constructor(json:dirrectjson,owner:Direct){ - super(-1,owner); - this.owner=owner; - this.headers=this.guild.headers; - this.name=json.recipients[0]?.username; - if(json.recipients[0]){ - this.user=new User(json.recipients[0],this.localuser); - }else{ - this.user=this.localuser.user; - } - this.name??=this.localuser.user.username; - this.snowflake=new SnowFlake(json.id,this); - this.parent_id=null; - this.parent=null; - this.children=[]; - this.guild_id="@me"; - this.messageids=new Map(); - this.permission_overwrites=new Map(); - this.lastmessageid=json.last_message_id; - this.lastmessageid??=null; - this.mentions=0; - this.setUpInfiniteScroller(); - if(this.lastmessageid){ - this.position=Number((BigInt(this.lastmessageid)>>22n)+1420070400000n); - } - this.position=-Math.max(this.position,this.snowflake.getUnixTime()); - } - createguildHTML(){ - const div=document.createElement("div") - div.classList.add("channeleffects"); - const myhtml=document.createElement("span"); - myhtml.textContent=this.name; - div.appendChild(this.user.buildpfp()); - div.appendChild(myhtml); - div["myinfo"]=this; - div.onclick=_=>{ - this.getHTML(); - } - return div; - } - async getHTML(){ - const id=++Channel.genid; - if(this.guild!==this.localuser.lookingguild){ - this.guild.loadGuild(); - } - this.guild.prevchannel=this; - this.localuser.channelfocus=this; - const prom=this.infinite.delete(); - await this.putmessages(); - await prom; - if(id!==Channel.genid){ - return; - } - this.buildmessages(); - history.pushState(null, "","/channels/"+this.guild_id+"/"+this.id); - this.localuser.pageTitle("@"+this.name); - (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); - (document.getElementById("typebox") as HTMLDivElement).contentEditable=""+true; - } - messageCreate(messagep){ - const messagez=new Message(messagep.d,this); - if(this.lastmessageid){ - this.idToNext.set(this.lastmessageid,messagez.id); - } - this.idToPrev.set(messagez.id,this.lastmessageid); - this.lastmessageid=messagez.id; - this.messageids.set(messagez.snowflake,messagez); - if(messagez.author===this.localuser.user){ - this.lastreadmessageid=messagez.id; - if(this.myhtml){ - this.myhtml.classList.remove("cunread"); - } - }else{ - if(this.myhtml){ - this.myhtml.classList.add("cunread"); - } - } - this.unreads(); - this.infinite.addedBottom(); - if(messagez.author===this.localuser.user){ - return; - } - if(this.localuser.lookingguild?.prevchannel===this&&document.hasFocus()){ - return; - } - if(this.notification==="all"){ - this.notify(messagez); - }else if(this.notification==="mentions"&&messagez.mentionsuser(this.localuser.user)){ - this.notify(messagez); - } - } - notititle(message){ - return message.author.username; - } - unreads(){ - const sentdms=document.getElementById("sentdms") as HTMLDivElement;//Need to change sometime - let current:HTMLElement|null=null; - for(const thing of sentdms.children){ - if(thing["all"]===this){ - current=thing as HTMLElement; - } - } - if(this.hasunreads){ - if(current){current["noti"].textContent=this.mentions;return;} - const div=document.createElement("div"); - div.classList.add("servernoti"); - const noti=document.createElement("div"); - noti.classList.add("unread","notiunread","pinged"); - noti.textContent=""+this.mentions; - div["noti"]=noti; - div.append(noti) - const buildpfp=this.user.buildpfp(); - div["all"]=this; - buildpfp.classList.add("mentioned"); - div.append(buildpfp) - sentdms.append(div); - div.onclick=_=>{ - this.guild.loadGuild(); - this.getHTML(); - } - }else if(current){ + user:User; + constructor(json:dirrectjson,owner:Direct){ + super(-1,owner); + this.owner=owner; + this.headers=this.guild.headers; + this.name=json.recipients[0]?.username; + if(json.recipients[0]){ + this.user=new User(json.recipients[0],this.localuser); + }else{ + this.user=this.localuser.user; + } + this.name??=this.localuser.user.username; + this.snowflake=new SnowFlake(json.id,this); + this.parent_id=null; + this.parent=null; + this.children=[]; + this.guild_id="@me"; + this.messageids=new Map(); + this.permission_overwrites=new Map(); + this.lastmessageid=json.last_message_id; + this.mentions=0; + this.setUpInfiniteScroller(); + if(this.lastmessageid){ + this.position=Number((BigInt(this.lastmessageid)>>22n)+1420070400000n); + } + this.position=-Math.max(this.position,this.snowflake.getUnixTime()); + } + createguildHTML(){ + const div=document.createElement("div"); + div.classList.add("channeleffects"); + const myhtml=document.createElement("span"); + myhtml.textContent=this.name; + div.appendChild(this.user.buildpfp()); + div.appendChild(myhtml); + div["myinfo"]=this; + div.onclick=_=>{ + this.getHTML(); + }; + return div; + } + async getHTML(){ + const id=++Channel.genid; + if(this.guild!==this.localuser.lookingguild){ + this.guild.loadGuild(); + } + this.guild.prevchannel=this; + this.localuser.channelfocus=this; + const prom=this.infinite.delete(); + await this.putmessages(); + await prom; + if(id!==Channel.genid){ + return; + } + this.buildmessages(); + history.pushState(null, "","/channels/"+this.guild_id+"/"+this.id); + this.localuser.pageTitle("@"+this.name); + (document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden",""); + (document.getElementById("typebox") as HTMLDivElement).contentEditable=""+true; + } + messageCreate(messagep){ + const messagez=new Message(messagep.d,this); + if(this.lastmessageid){ + this.idToNext.set(this.lastmessageid,messagez.id); + this.idToPrev.set(messagez.id,this.lastmessageid); + } + this.lastmessageid=messagez.id; + this.messageids.set(messagez.snowflake,messagez); + if(messagez.author===this.localuser.user){ + this.lastreadmessageid=messagez.id; + if(this.myhtml){ + this.myhtml.classList.remove("cunread"); + } + }else{ + if(this.myhtml){ + this.myhtml.classList.add("cunread"); + } + } + this.unreads(); + this.infinite.addedBottom(); + if(messagez.author===this.localuser.user){ + return; + } + if(this.localuser.lookingguild?.prevchannel===this&&document.hasFocus()){ + return; + } + if(this.notification==="all"){ + this.notify(messagez); + }else if(this.notification==="mentions"&&messagez.mentionsuser(this.localuser.user)){ + this.notify(messagez); + } + } + notititle(message){ + return message.author.username; + } + unreads(){ + const sentdms=document.getElementById("sentdms") as HTMLDivElement;//Need to change sometime + let current:HTMLElement|null=null; + for(const thing of sentdms.children){ + if(thing["all"]===this){ + current=thing as HTMLElement; + } + } + if(this.hasunreads){ + if(current){ + current["noti"].textContent=this.mentions;return; + } + const div=document.createElement("div"); + div.classList.add("servernoti"); + const noti=document.createElement("div"); + noti.classList.add("unread","notiunread","pinged"); + noti.textContent=""+this.mentions; + div["noti"]=noti; + div.append(noti); + const buildpfp=this.user.buildpfp(); + div["all"]=this; + buildpfp.classList.add("mentioned"); + div.append(buildpfp); + sentdms.append(div); + div.onclick=_=>{ + this.guild.loadGuild(); + this.getHTML(); + }; + }else if(current){ + current.remove(); + }else{ - current.remove(); - }else{ - - } - } - isAdmin(): boolean { - return false; - } - hasPermission(name: string): boolean { - return dmPermissions.hasPermission(name); - } + } + } + isAdmin(): boolean{ + return false; + } + hasPermission(name: string): boolean{ + return dmPermissions.hasPermission(name); + } } -export {Direct, Group}; +export{Direct, Group}; diff --git a/webpage/embed.ts b/webpage/embed.ts index fc176e7..45f753d 100644 --- a/webpage/embed.ts +++ b/webpage/embed.ts @@ -1,230 +1,232 @@ -import {Dialog} from "./dialog.js"; -import {Message} from "./message.js"; -import {MarkDown} from "./markdown.js"; -import { embedjson } from "./jsontypes.js"; +import{Dialog}from"./dialog.js"; +import{Message}from"./message.js"; +import{MarkDown}from"./markdown.js"; +import{ embedjson }from"./jsontypes.js"; class Embed{ - type:string; - owner:Message; - json:embedjson; - constructor(json:embedjson, owner:Message){ - this.type=this.getType(json); - this.owner=owner; - this.json=json; - } - getType(json:embedjson){ - return json.type||"rich"; - } - generateHTML(){ - switch(this.type){ - case "rich": - return this.generateRich(); - case "image": - return this.generateImage(); - case "link": - return this.generateLink(); - case "article": - return this.generateArticle(); - default: - console.warn(`unsupported embed type ${this.type}, please add support dev :3`,this.json); - return document.createElement("div");//prevent errors by giving blank div - } - } - get message(){ - return this.owner; - } - get channel(){ - return this.message.channel; - } - get guild(){ - return this.channel.guild; - } - get localuser(){ - return this.guild.localuser; - } - generateRich(){ - const div=document.createElement("div"); - if(this.json.color){ - div.style.backgroundColor="#"+this.json.color.toString(16); - } - div.classList.add("embed-color"); + type:string; + owner:Message; + json:embedjson; + constructor(json:embedjson, owner:Message){ + this.type=this.getType(json); + this.owner=owner; + this.json=json; + } + getType(json:embedjson){ + return json.type||"rich"; + } + generateHTML(){ + switch(this.type){ + case"rich": + return this.generateRich(); + case"image": + return this.generateImage(); + case"link": + return this.generateLink(); + case"article": + return this.generateArticle(); + default: + console.warn(`unsupported embed type ${this.type}, please add support dev :3`,this.json); + return document.createElement("div");//prevent errors by giving blank div + } + } + get message(){ + return this.owner; + } + get channel(){ + return this.message.channel; + } + get guild(){ + return this.channel.guild; + } + get localuser(){ + return this.guild.localuser; + } + generateRich(){ + const div=document.createElement("div"); + if(this.json.color){ + div.style.backgroundColor="#"+this.json.color.toString(16); + } + div.classList.add("embed-color"); - const embed=document.createElement("div"); - embed.classList.add("embed"); - div.append(embed); + const embed=document.createElement("div"); + embed.classList.add("embed"); + div.append(embed); - if(this.json.author){ - const authorline=document.createElement("div"); - if(this.json.author.icon_url){ - const img=document.createElement("img"); - img.classList.add("embedimg"); - img.src=this.json.author.icon_url; - authorline.append(img); - } - const a=document.createElement("a"); - a.textContent=this.json.author.name as string; - if(this.json.author.url){ - a.href=this.json.author.url - } - a.classList.add("username") - authorline.append(a); - embed.append(authorline); - } - if(this.json.title){ - const title=document.createElement("a"); - title.append(new MarkDown(this.json.title,this.channel).makeHTML()); - if(this.json.url){ - title.href=this.json.url; - } - title.classList.add("embedtitle"); - embed.append(title); - } - if(this.json.description){ - const p=document.createElement("p"); - p.append(new MarkDown(this.json.description,this.channel).makeHTML()); - embed.append(p); - } + if(this.json.author){ + const authorline=document.createElement("div"); + if(this.json.author.icon_url){ + const img=document.createElement("img"); + img.classList.add("embedimg"); + img.src=this.json.author.icon_url; + authorline.append(img); + } + const a=document.createElement("a"); + a.textContent=this.json.author.name as string; + if(this.json.author.url){ + a.href=this.json.author.url; + } + a.classList.add("username"); + authorline.append(a); + embed.append(authorline); + } + if(this.json.title){ + const title=document.createElement("a"); + title.append(new MarkDown(this.json.title,this.channel).makeHTML()); + if(this.json.url){ + title.href=this.json.url; + } + title.classList.add("embedtitle"); + embed.append(title); + } + if(this.json.description){ + const p=document.createElement("p"); + p.append(new MarkDown(this.json.description,this.channel).makeHTML()); + embed.append(p); + } - embed.append(document.createElement("br")); - if(this.json.fields){ - for(const thing of this.json.fields){ - const div=document.createElement("div"); - const b=document.createElement("b"); - b.textContent=thing.name; - div.append(b); - const p=document.createElement("p") - p.append(new MarkDown(thing.value,this.channel).makeHTML()); - p.classList.add("embedp"); - div.append(p); + embed.append(document.createElement("br")); + if(this.json.fields){ + for(const thing of this.json.fields){ + const div=document.createElement("div"); + const b=document.createElement("b"); + b.textContent=thing.name; + div.append(b); + const p=document.createElement("p"); + p.append(new MarkDown(thing.value,this.channel).makeHTML()); + p.classList.add("embedp"); + div.append(p); - if(thing.inline){div.classList.add("inline");} - embed.append(div); - } - } - if(this.json.footer||this.json.timestamp){ - const footer=document.createElement("div"); - if(this.json?.footer?.icon_url){ - const img=document.createElement("img"); - img.src=this.json.footer.icon_url; - img.classList.add("embedicon"); - footer.append(img); - } - if(this.json?.footer?.text){ - const span=document.createElement("span"); - span.textContent=this.json.footer.text; - span.classList.add("spaceright"); - footer.append(span); - } - if(this.json?.footer&&this.json?.timestamp){ - const span=document.createElement("span"); - span.textContent="•"; - span.classList.add("spaceright"); - footer.append(span); - } - if(this.json?.timestamp){ - const span=document.createElement("span") - span.textContent=new Date(this.json.timestamp).toLocaleString();; - footer.append(span); - } - embed.append(footer); - } - return div; - } - generateImage(){ - const img=document.createElement("img"); - img.classList.add("messageimg") - img.onclick=function(){ - const full=new Dialog(["img",img.src,["fit"]]); - full.show(); - } - img.src=this.json.thumbnail.proxy_url; - if(this.json.thumbnail.width){ - let scale=1; - const max=96*3; - scale=Math.max(scale,this.json.thumbnail.width/max); - scale=Math.max(scale,this.json.thumbnail.height/max); - this.json.thumbnail.width/=scale; - this.json.thumbnail.height/=scale; - } - img.style.width=this.json.thumbnail.width+"px"; - img.style.height=this.json.thumbnail.height+"px"; - console.log(this.json,"Image fix"); - return img; - } - generateLink(){ - const table=document.createElement("table"); - table.classList.add("embed","linkembed"); - const trtop=document.createElement("tr"); - table.append(trtop); - if(this.json.url&&this.json.title){ - const td=document.createElement("td"); - const a=document.createElement("a"); - a.href=this.json.url; - a.textContent=this.json.title; - td.append(a); - trtop.append(td); - } - { - const td=document.createElement("td"); - const img=document.createElement("img"); - if(this.json.thumbnail){ - img.classList.add("embedimg"); - img.onclick=function(){ - const full=new Dialog(["img",img.src,["fit"]]); - full.show(); - } - img.src=this.json.thumbnail.proxy_url; - td.append(img); - } - trtop.append(td); - } - const bottomtr=document.createElement("tr"); - const td=document.createElement("td"); - if(this.json.description){ - const span=document.createElement("span"); - span.textContent=this.json.description; - td.append(span); - } - bottomtr.append(td); - table.append(bottomtr) - return table; - } - generateArticle(){ - const colordiv=document.createElement("div"); - colordiv.style.backgroundColor="#000000"; - colordiv.classList.add("embed-color"); + if(thing.inline){ + div.classList.add("inline"); + } + embed.append(div); + } + } + if(this.json.footer||this.json.timestamp){ + const footer=document.createElement("div"); + if(this.json?.footer?.icon_url){ + const img=document.createElement("img"); + img.src=this.json.footer.icon_url; + img.classList.add("embedicon"); + footer.append(img); + } + if(this.json?.footer?.text){ + const span=document.createElement("span"); + span.textContent=this.json.footer.text; + span.classList.add("spaceright"); + footer.append(span); + } + if(this.json?.footer&&this.json?.timestamp){ + const span=document.createElement("span"); + span.textContent="•"; + span.classList.add("spaceright"); + footer.append(span); + } + if(this.json?.timestamp){ + const span=document.createElement("span"); + span.textContent=new Date(this.json.timestamp).toLocaleString(); + footer.append(span); + } + embed.append(footer); + } + return div; + } + generateImage(){ + const img=document.createElement("img"); + img.classList.add("messageimg"); + img.onclick=function(){ + const full=new Dialog(["img",img.src,["fit"]]); + full.show(); + }; + img.src=this.json.thumbnail.proxy_url; + if(this.json.thumbnail.width){ + let scale=1; + const max=96*3; + scale=Math.max(scale,this.json.thumbnail.width/max); + scale=Math.max(scale,this.json.thumbnail.height/max); + this.json.thumbnail.width/=scale; + this.json.thumbnail.height/=scale; + } + img.style.width=this.json.thumbnail.width+"px"; + img.style.height=this.json.thumbnail.height+"px"; + console.log(this.json,"Image fix"); + return img; + } + generateLink(){ + const table=document.createElement("table"); + table.classList.add("embed","linkembed"); + const trtop=document.createElement("tr"); + table.append(trtop); + if(this.json.url&&this.json.title){ + const td=document.createElement("td"); + const a=document.createElement("a"); + a.href=this.json.url; + a.textContent=this.json.title; + td.append(a); + trtop.append(td); + } + { + const td=document.createElement("td"); + const img=document.createElement("img"); + if(this.json.thumbnail){ + img.classList.add("embedimg"); + img.onclick=function(){ + const full=new Dialog(["img",img.src,["fit"]]); + full.show(); + }; + img.src=this.json.thumbnail.proxy_url; + td.append(img); + } + trtop.append(td); + } + const bottomtr=document.createElement("tr"); + const td=document.createElement("td"); + if(this.json.description){ + const span=document.createElement("span"); + span.textContent=this.json.description; + td.append(span); + } + bottomtr.append(td); + table.append(bottomtr); + return table; + } + generateArticle(){ + const colordiv=document.createElement("div"); + colordiv.style.backgroundColor="#000000"; + colordiv.classList.add("embed-color"); - const div=document.createElement("div"); - div.classList.add("embed"); - if(this.json.provider){ - const provider=document.createElement("p"); - provider.classList.add("provider"); - provider.textContent=this.json.provider.name; - div.append(provider); - } - const a=document.createElement("a"); - if(this.json.url&&this.json.url){ - a.href=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"); - img.classList.add("bigembedimg"); - img.onclick=function(){ - 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 div=document.createElement("div"); + div.classList.add("embed"); + if(this.json.provider){ + const provider=document.createElement("p"); + provider.classList.add("provider"); + provider.textContent=this.json.provider.name; + div.append(provider); + } + const a=document.createElement("a"); + if(this.json.url&&this.json.url){ + a.href=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"); + img.classList.add("bigembedimg"); + img.onclick=function(){ + const full=new Dialog(["img",img.src,["fit"]]); + full.show(); + }; + img.src=this.json.thumbnail.proxy_url||this.json.thumbnail.url; + div.append(img); + } + colordiv.append(div); + return colordiv; + } } -export {Embed}; +export{Embed}; diff --git a/webpage/emoji.ts b/webpage/emoji.ts index bd0a859..dd14a46 100644 --- a/webpage/emoji.ts +++ b/webpage/emoji.ts @@ -1,226 +1,226 @@ -import { Contextmenu } from "./contextmenu.js"; -import { Guild } from "./guild.js"; -import { emojijson } from "./jsontypes.js"; -import { Localuser } from "./localuser.js"; +import{ Contextmenu }from"./contextmenu.js"; +import{ Guild }from"./guild.js"; +import{ emojijson }from"./jsontypes.js"; +import{ Localuser }from"./localuser.js"; class Emoji{ - static emojis:{ + static emojis:{ name:string, emojis:{ name:string, emoji:string, }[] }[]; - name:string; - id:string; - animated:boolean; - owner:Guild|Localuser; - get guild(){ - if(this.owner instanceof Guild){ - return this.owner; - } - } - get localuser(){ - if(this.owner instanceof Guild){ - return this.owner.localuser; - }else{ - return this.owner; - } - } - get info(){ - return this.owner.info; - } - constructor(json:{name:string,id:string,animated:boolean},owner:Guild|Localuser){ - this.name=json.name; - this.id=json.id; - this.animated=json.animated - this.owner=owner; - } - getHTML(bigemoji:boolean=false){ - const emojiElem=document.createElement("img"); - emojiElem.classList.add("md-emoji"); - emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); - emojiElem.crossOrigin="anonymous"; - emojiElem.src=this.info.cdn + "/emojis/" + this.id + "." + (this.animated ? "gif" : "png") + "?size=32"; + name:string; + id:string; + animated:boolean; + owner:Guild|Localuser; + get guild(){ + if(this.owner instanceof Guild){ + return this.owner; + } + } + get localuser(){ + if(this.owner instanceof Guild){ + return this.owner.localuser; + }else{ + return this.owner; + } + } + get info(){ + return this.owner.info; + } + constructor(json:{name:string,id:string,animated:boolean},owner:Guild|Localuser){ + this.name=json.name; + this.id=json.id; + this.animated=json.animated; + this.owner=owner; + } + getHTML(bigemoji:boolean=false){ + const emojiElem=document.createElement("img"); + emojiElem.classList.add("md-emoji"); + emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); + emojiElem.crossOrigin="anonymous"; + emojiElem.src=this.info.cdn + "/emojis/" + this.id + "." + (this.animated ? "gif" : "png") + "?size=32"; - emojiElem.alt=this.name; - emojiElem.loading="lazy"; - return emojiElem; - } - static decodeEmojiList(buffer:ArrayBuffer){ - const view = new DataView(buffer, 0); - let i=0; - function read16(){ - const int=view.getUint16(i); - i+=2; - return int; - } - function read8(){ - const int=view.getUint8(i); - i+=1; - return int; - } - function readString8(){ - return readStringNo(read8()); - } - function readString16(){ - return readStringNo(read16()); - } - function readStringNo(length:number){ - const array=new Uint8Array(length); + emojiElem.alt=this.name; + emojiElem.loading="lazy"; + return emojiElem; + } + static decodeEmojiList(buffer:ArrayBuffer){ + const view = new DataView(buffer, 0); + let i=0; + function read16(){ + const int=view.getUint16(i); + i+=2; + return int; + } + function read8(){ + const int=view.getUint8(i); + i+=1; + return int; + } + function readString8(){ + return readStringNo(read8()); + } + function readString16(){ + return readStringNo(read16()); + } + function readStringNo(length:number){ + const array=new Uint8Array(length); - for(let i=0;i127; - const emoji=readStringNo(len-(+skin_tone_support*128)); - emojis.push({ - name, - skin_tone_support, - emoji - }) - } - build.push({ - name, - emojis - }) - } - this.emojis=build; - console.log(build); - } - static grabEmoji(){ - fetch("/emoji.bin").then(e=>{ - return e.arrayBuffer() - }).then(e=>{ - Emoji.decodeEmojiList(e); - }) - } - static async emojiPicker(x:number,y:number, localuser:Localuser):Promise{ - let res:(r:Emoji|string)=>void; - const promise:Promise=new Promise((r)=>{res=r;}) - const menu=document.createElement("div"); - menu.classList.add("flextttb", "emojiPicker") - menu.style.top=y+"px"; - menu.style.left=x+"px"; + let emojinumber=read16(); + for(;emojinumber!==0;emojinumber--){ + //console.log(emojis); + const name=readString8(); + const len=read8(); + const skin_tone_support=len>127; + const emoji=readStringNo(len-(Number(skin_tone_support)*128)); + emojis.push({ + name, + skin_tone_support, + emoji + }); + } + build.push({ + name, + emojis + }); + } + this.emojis=build; + console.log(build); + } + static grabEmoji(){ + fetch("/emoji.bin").then(e=>{ + return e.arrayBuffer(); + }).then(e=>{ + Emoji.decodeEmojiList(e); + }); + } + static async emojiPicker(x:number,y:number, localuser:Localuser):Promise{ + let res:(r:Emoji|string)=>void; + const promise:Promise=new Promise(r=>{ + res=r; + }); + const menu=document.createElement("div"); + menu.classList.add("flextttb", "emojiPicker"); + menu.style.top=y+"px"; + menu.style.left=x+"px"; - const title=document.createElement("h2"); - title.textContent=Emoji.emojis[0].name; - title.classList.add("emojiTitle"); - menu.append(title); - const selection=document.createElement("div"); - selection.classList.add("flexltr","dontshrink","emojirow"); - const body=document.createElement("div"); - body.classList.add("emojiBody"); + const title=document.createElement("h2"); + title.textContent=Emoji.emojis[0].name; + title.classList.add("emojiTitle"); + menu.append(title); + const selection=document.createElement("div"); + selection.classList.add("flexltr","dontshrink","emojirow"); + const body=document.createElement("div"); + body.classList.add("emojiBody"); - let isFirst = true; - localuser.guilds.filter(guild => guild.id != "@me" && guild.emojis.length > 0).forEach(guild => { - const select = document.createElement("div") - select.classList.add("emojiSelect") + let isFirst = true; + localuser.guilds.filter(guild=>guild.id != "@me" && guild.emojis.length > 0).forEach(guild=>{ + const select = document.createElement("div"); + select.classList.add("emojiSelect"); - if (guild.properties.icon) { - const img = document.createElement("img") - img.classList.add("pfp", "servericon", "emoji-server") - img.crossOrigin = "anonymous" - img.src = localuser.info.cdn + "/icons/" + guild.properties.id + "/" + guild.properties.icon + ".png?size=48" - img.alt = "Server: " + guild.properties.name - select.appendChild(img) - } else { - const div = document.createElement("span") - div.textContent = guild.properties.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, "") - select.append(div) - } + if(guild.properties.icon){ + const img = document.createElement("img"); + img.classList.add("pfp", "servericon", "emoji-server"); + img.crossOrigin = "anonymous"; + img.src = localuser.info.cdn + "/icons/" + guild.properties.id + "/" + guild.properties.icon + ".png?size=48"; + img.alt = "Server: " + guild.properties.name; + select.appendChild(img); + }else{ + const div = document.createElement("span"); + div.textContent = guild.properties.name.replace(/'s /g, " ").replace(/\w+/g, word=>word[0]).replace(/\s/g, ""); + select.append(div); + } - selection.append(select) + selection.append(select); - const clickEvent = () => { - title.textContent = guild.properties.name - body.innerHTML = "" - for (const emojit of guild.emojis) { - const emojiElem = document.createElement("div") - emojiElem.classList.add("emojiSelect") + const clickEvent = ()=>{ + title.textContent = guild.properties.name; + body.innerHTML = ""; + for(const emojit of guild.emojis){ + const emojiElem = document.createElement("div"); + emojiElem.classList.add("emojiSelect"); - const emojiClass = new Emoji({ - id: emojit.id as string, - name: emojit.name, - animated: emojit.animated as boolean - },localuser) - emojiElem.append(emojiClass.getHTML()) - body.append(emojiElem) + const emojiClass = new Emoji({ + id: emojit.id as string, + name: emojit.name, + animated: emojit.animated as boolean + },localuser); + emojiElem.append(emojiClass.getHTML()); + body.append(emojiElem); - emojiElem.addEventListener("click", () => { - res(emojiClass) - Contextmenu.currentmenu.remove() - }) - } - } + emojiElem.addEventListener("click", ()=>{ + res(emojiClass); + Contextmenu.currentmenu.remove(); + }); + } + }; - select.addEventListener("click", clickEvent) - if (isFirst) { - clickEvent() - isFirst = false - } - }) + select.addEventListener("click", clickEvent); + if(isFirst){ + clickEvent(); + isFirst = false; + } + }); - setTimeout(()=>{ - if(Contextmenu.currentmenu!=""){ - Contextmenu.currentmenu.remove(); - } - document.body.append(menu); - Contextmenu.currentmenu=menu; - Contextmenu.keepOnScreen(menu); - },10) + setTimeout(()=>{ + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); + } + document.body.append(menu); + Contextmenu.currentmenu=menu; + Contextmenu.keepOnScreen(menu); + },10); - let i=0; - for(const thing of Emoji.emojis){ - const select=document.createElement("div"); - select.textContent=thing.emojis[0].emoji; - select.classList.add("emojiSelect"); - selection.append(select); - const clickEvent=()=>{ - title.textContent=thing.name; - body.innerHTML=""; - for(const emojit of thing.emojis){ - const emoji=document.createElement("div"); - emoji.classList.add("emojiSelect"); - emoji.textContent=emojit.emoji; - body.append(emoji); - emoji.onclick=_=>{ - res(emojit.emoji); - Contextmenu.currentmenu.remove(); - } - } - }; - select.onclick=clickEvent - if(i===0){ - clickEvent(); - } - i++; - } - menu.append(selection); - menu.append(body); - return promise; - } + let i=0; + for(const thing of Emoji.emojis){ + const select=document.createElement("div"); + select.textContent=thing.emojis[0].emoji; + select.classList.add("emojiSelect"); + selection.append(select); + const clickEvent=()=>{ + title.textContent=thing.name; + body.innerHTML=""; + for(const emojit of thing.emojis){ + const emoji=document.createElement("div"); + emoji.classList.add("emojiSelect"); + emoji.textContent=emojit.emoji; + body.append(emoji); + emoji.onclick=_=>{ + res(emojit.emoji); + Contextmenu.currentmenu.remove(); + }; + } + }; + select.onclick=clickEvent; + if(i===0){ + clickEvent(); + } + i++; + } + menu.append(selection); + menu.append(body); + return promise; + } } Emoji.grabEmoji(); -export {Emoji}; +export{Emoji}; diff --git a/webpage/file.ts b/webpage/file.ts index 6027b91..0d7edd7 100644 --- a/webpage/file.ts +++ b/webpage/file.ts @@ -1,145 +1,145 @@ -import { Message } from "./message.js"; -import { Dialog } from "./dialog.js"; -import { filejson } from "./jsontypes.js"; +import{ Message }from"./message.js"; +import{ Dialog }from"./dialog.js"; +import{ filejson }from"./jsontypes.js"; class File{ - owner:Message|null; - id:string; - filename:string; - content_type:string; - width:number|undefined; - height:number|undefined; - proxy_url:string|undefined; - url:string; - size:number; - constructor(fileJSON:filejson,owner:Message|null){ - this.owner=owner; - this.id=fileJSON.id; - this.filename=fileJSON.filename; - this.content_type=fileJSON.content_type; - this.width=fileJSON.width; - this.height=fileJSON.height; - this.url=fileJSON.url; - this.proxy_url=fileJSON.proxy_url; - this.content_type=fileJSON.content_type; - this.size=fileJSON.size; - } - getHTML(temp:boolean=false):HTMLElement{ - const src=this.proxy_url||this.url; - if(this.width&&this.height){ - let scale=1; - const max=96*3; - scale=Math.max(scale,this.width/max); - scale=Math.max(scale,this.height/max); - this.width/=scale; - this.height/=scale; - } - if(this.content_type.startsWith('image/')){ - const div=document.createElement("div") - const img=document.createElement("img"); - img.classList.add("messageimg"); - div.classList.add("messageimgdiv") - img.onclick=function(){ - const full=new Dialog(["img",img.src,["fit"]]); - full.show(); - } - img.src=src; - div.append(img) - if(this.width){ - div.style.width=this.width+"px"; - div.style.height=this.height+"px"; - } - console.log(img); - console.log(this.width,this.height) - return div; - }else if(this.content_type.startsWith('video/')){ - const video=document.createElement("video"); - const source=document.createElement("source"); - source.src=src; - video.append(source); - source.type=this.content_type; - video.controls=!temp; - if(this.width&&this.height){ - video.width=this.width; - video.height=this.height; - } - return video; - }else if(this.content_type.startsWith('audio/')){ - const audio=document.createElement("audio"); - const source=document.createElement("source"); - source.src=src; - audio.append(source); - source.type=this.content_type; - audio.controls=!temp; - return audio; - }else{ - return this.createunknown(); - } - } - upHTML(files:Blob[],file:globalThis.File):HTMLElement{ - const div=document.createElement("div"); - const contained=this.getHTML(true); - div.classList.add("containedFile"); - div.append(contained); - const controls=document.createElement("div"); - const garbage=document.createElement("button"); - garbage.textContent="🗑"; - garbage.onclick=_=>{ - div.remove(); - files.splice(files.indexOf(file),1); - } - controls.classList.add("controls"); - div.append(controls); - controls.append(garbage); - return div; - } - static initFromBlob(file:globalThis.File){ - return new File({ - filename:file.name, - size:file.size, - id:"null", - content_type:file.type, - width:undefined, - height:undefined, - url:URL.createObjectURL(file), - proxy_url:undefined - },null) - } - createunknown():HTMLElement{ - console.log("🗎") - const src=this.proxy_url||this.url; - const div=document.createElement("table"); - div.classList.add("unknownfile"); - const nametr=document.createElement("tr"); - div.append(nametr); - const fileicon=document.createElement("td"); - nametr.append(fileicon); - fileicon.append("🗎"); - fileicon.classList.add("fileicon"); - fileicon.rowSpan=2; - const nametd=document.createElement("td"); - if(src){ - const a=document.createElement("a"); - a.href=src; - a.textContent=this.filename; - nametd.append(a); - }else{ - nametd.textContent=this.filename; - } + owner:Message|null; + id:string; + filename:string; + content_type:string; + width:number|undefined; + height:number|undefined; + proxy_url:string|undefined; + url:string; + size:number; + constructor(fileJSON:filejson,owner:Message|null){ + this.owner=owner; + this.id=fileJSON.id; + this.filename=fileJSON.filename; + this.content_type=fileJSON.content_type; + this.width=fileJSON.width; + this.height=fileJSON.height; + this.url=fileJSON.url; + this.proxy_url=fileJSON.proxy_url; + this.content_type=fileJSON.content_type; + this.size=fileJSON.size; + } + getHTML(temp:boolean=false):HTMLElement{ + const src=this.proxy_url||this.url; + if(this.width&&this.height){ + let scale=1; + const max=96*3; + scale=Math.max(scale,this.width/max); + scale=Math.max(scale,this.height/max); + this.width/=scale; + this.height/=scale; + } + if(this.content_type.startsWith("image/")){ + const div=document.createElement("div"); + const img=document.createElement("img"); + img.classList.add("messageimg"); + div.classList.add("messageimgdiv"); + img.onclick=function(){ + const full=new Dialog(["img",img.src,["fit"]]); + full.show(); + }; + img.src=src; + div.append(img); + if(this.width){ + div.style.width=this.width+"px"; + div.style.height=this.height+"px"; + } + console.log(img); + console.log(this.width,this.height); + return div; + }else if(this.content_type.startsWith("video/")){ + const video=document.createElement("video"); + const source=document.createElement("source"); + source.src=src; + video.append(source); + source.type=this.content_type; + video.controls=!temp; + if(this.width&&this.height){ + video.width=this.width; + video.height=this.height; + } + return video; + }else if(this.content_type.startsWith("audio/")){ + const audio=document.createElement("audio"); + const source=document.createElement("source"); + source.src=src; + audio.append(source); + source.type=this.content_type; + audio.controls=!temp; + return audio; + }else{ + return this.createunknown(); + } + } + upHTML(files:Blob[],file:globalThis.File):HTMLElement{ + const div=document.createElement("div"); + const contained=this.getHTML(true); + div.classList.add("containedFile"); + div.append(contained); + const controls=document.createElement("div"); + const garbage=document.createElement("button"); + garbage.textContent="🗑"; + garbage.onclick=_=>{ + div.remove(); + files.splice(files.indexOf(file),1); + }; + controls.classList.add("controls"); + div.append(controls); + controls.append(garbage); + return div; + } + static initFromBlob(file:globalThis.File){ + return new File({ + filename: file.name, + size: file.size, + id: "null", + content_type: file.type, + width: undefined, + height: undefined, + url: URL.createObjectURL(file), + proxy_url: undefined + },null); + } + createunknown():HTMLElement{ + console.log("🗎"); + const src=this.proxy_url||this.url; + const div=document.createElement("table"); + div.classList.add("unknownfile"); + const nametr=document.createElement("tr"); + div.append(nametr); + const fileicon=document.createElement("td"); + nametr.append(fileicon); + fileicon.append("🗎"); + fileicon.classList.add("fileicon"); + fileicon.rowSpan=2; + const nametd=document.createElement("td"); + if(src){ + const a=document.createElement("a"); + a.href=src; + a.textContent=this.filename; + nametd.append(a); + }else{ + nametd.textContent=this.filename; + } - nametd.classList.add("filename"); - nametr.append(nametd); - const sizetr=document.createElement("tr"); - const size=document.createElement("td"); - sizetr.append(size); - size.textContent="Size:"+File.filesizehuman(this.size); - size.classList.add("filesize"); - div.appendChild(sizetr); - return div; - } - static filesizehuman(fsize:number){ - var i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); - return +((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes'][i]; - } + nametd.classList.add("filename"); + nametr.append(nametd); + const sizetr=document.createElement("tr"); + const size=document.createElement("td"); + sizetr.append(size); + size.textContent="Size:"+File.filesizehuman(this.size); + size.classList.add("filesize"); + div.appendChild(sizetr); + return div; + } + static filesizehuman(fsize:number){ + const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); + return Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i]; + } } -export{File} +export{File}; diff --git a/webpage/guild.ts b/webpage/guild.ts index a71933d..7a78a1a 100644 --- a/webpage/guild.ts +++ b/webpage/guild.ts @@ -1,71 +1,71 @@ -import { Channel } from "./channel.js"; -import { Localuser } from "./localuser.js"; -import {Contextmenu} from "./contextmenu.js"; -import {Role,RoleList} from "./role.js"; -import {Dialog} from "./dialog.js"; -import {Member} from "./member.js"; -import {Settings} from "./settings.js"; -import {Permissions} from "./permissions.js"; -import { SnowFlake } from "./snowflake.js"; -import { channeljson, guildjson, emojijson, memberjson } from "./jsontypes.js"; -import { User } from "./user.js"; -import { Message } from "./message.js"; +import{ Channel }from"./channel.js"; +import{ Localuser }from"./localuser.js"; +import{Contextmenu}from"./contextmenu.js"; +import{Role,RoleList}from"./role.js"; +import{Dialog}from"./dialog.js"; +import{Member}from"./member.js"; +import{Settings}from"./settings.js"; +import{Permissions}from"./permissions.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ channeljson, guildjson, emojijson, memberjson }from"./jsontypes.js"; +import{ User }from"./user.js"; +import{ Message }from"./message.js"; class Guild{ - owner:Localuser; - headers:Localuser["headers"]; - channels:Channel[]; - channelids:{[key:string]:Channel}; - snowflake:SnowFlake; - properties - roles:Role[]; - roleids:Map,Role>; - prevchannel:Channel|undefined; - message_notifications:number; - headchannels:Channel[]; - position:number; - parent_id:string; - member:Member; - html:HTMLElement; - emojis:emojijson[]; - get id(){ - return this.snowflake.id; - } - static contextmenu=new Contextmenu("guild menu"); - static setupcontextmenu(){ - Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){ - console.log(this) - navigator.clipboard.writeText(this.id); - }); + owner:Localuser; + headers:Localuser["headers"]; + channels:Channel[]; + channelids:{[key:string]:Channel}; + snowflake:SnowFlake; + properties; + roles:Role[]; + roleids:Map,Role>; + prevchannel:Channel|undefined; + message_notifications:number; + headchannels:Channel[]; + position:number; + parent_id:string; + member:Member; + html:HTMLElement; + emojis:emojijson[]; + get id(){ + return this.snowflake.id; + } + static contextmenu=new Contextmenu("guild menu"); + static setupcontextmenu(){ + Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){ + console.log(this); + navigator.clipboard.writeText(this.id); + }); - Guild.contextmenu.addbutton("Mark as read",function(this:Guild){ - console.log(this) - this.markAsRead(); - }); + Guild.contextmenu.addbutton("Mark as read",function(this:Guild){ + console.log(this); + this.markAsRead(); + }); - Guild.contextmenu.addbutton("Notifications",function(this:Guild){ - console.log(this) - this.setnotifcation(); - }); + Guild.contextmenu.addbutton("Notifications",function(this:Guild){ + console.log(this); + this.setnotifcation(); + }); - Guild.contextmenu.addbutton("Leave guild",function(this:Guild){ - this.confirmleave(); - },null,function(_){ - return this.properties.owner_id!==this.member.user.id - }); + Guild.contextmenu.addbutton("Leave guild",function(this:Guild){ + this.confirmleave(); + },null,function(_){ + return this.properties.owner_id!==this.member.user.id; + }); - Guild.contextmenu.addbutton("Delete guild",function(this:Guild){ - this.confirmDelete(); - },null,function(_){ - return this.properties.owner_id===this.member.user.id - }); + Guild.contextmenu.addbutton("Delete guild",function(this:Guild){ + this.confirmDelete(); + },null,function(_){ + return this.properties.owner_id===this.member.user.id; + }); - Guild.contextmenu.addbutton("Create invite",function(this:Guild){ - console.log(this); - },null,_=>true,_=>false); - Guild.contextmenu.addbutton("Settings[temp]",function(this:Guild){ - this.generateSettings(); - }); - /* -----things left for later----- + Guild.contextmenu.addbutton("Create invite",function(this:Guild){ + console.log(this); + },null,_=>true,_=>false); + Guild.contextmenu.addbutton("Settings[temp]",function(this:Guild){ + this.generateSettings(); + }); + /* -----things left for later----- guild.contextmenu.addbutton("Leave Guild",function(){ console.log(this) this.deleteChannel(); @@ -75,435 +75,438 @@ class Guild{ editchannelf(this); },null,_=>{return thisuser.isAdmin()}) */ - } - generateSettings(){ - const settings=new Settings("Settings for "+this.properties.name); + } + generateSettings(){ + const settings=new Settings("Settings for "+this.properties.name); - const s1=settings.addButton("roles"); - const permlist:[SnowFlake,Permissions][]=[]; - for(const thing of this.roles){ - permlist.push([thing.snowflake,thing.permissions]); - } - s1.options.push(new RoleList(permlist,this,this.updateRolePermissions.bind(this))); - settings.show(); - } - constructor(json:guildjson|-1,owner:Localuser,member:memberjson|User|null){ - if(json===-1||member===null){ - return; - } - if(json.stickers.length){ - console.log(json.stickers,":3") - } - this.emojis = json.emojis - this.owner=owner; - this.headers=this.owner.headers; - this.channels=[]; - this.channelids={}; - this.snowflake=new SnowFlake(json.id,this); - this.properties=json.properties; - this.roles=[]; - this.roleids=new Map(); - this.prevchannel=undefined; - this.message_notifications=0; - for(const roley of json.roles){ - const roleh=new Role(roley,this); - this.roles.push(roleh) - this.roleids.set(roleh.snowflake,roleh); - } - if(member instanceof User){ - Member.resolveMember(member,this).then(_=>{ - if(_){ - this.member=_ - }else{ - console.error("Member was unable to resolve"); - } - }); - }else{ - Member.new(member,this).then(_=>{ - if(_){ - this.member=_ - } - }); - } + const s1=settings.addButton("roles"); + const permlist:[SnowFlake,Permissions][]=[]; + for(const thing of this.roles){ + permlist.push([thing.snowflake,thing.permissions]); + } + s1.options.push(new RoleList(permlist,this,this.updateRolePermissions.bind(this))); + settings.show(); + } + constructor(json:guildjson|-1,owner:Localuser,member:memberjson|User|null){ + if(json===-1||member===null){ + return; + } + if(json.stickers.length){ + console.log(json.stickers,":3"); + } + this.emojis = json.emojis; + this.owner=owner; + this.headers=this.owner.headers; + this.channels=[]; + this.channelids={}; + this.snowflake=new SnowFlake(json.id,this); + this.properties=json.properties; + this.roles=[]; + this.roleids=new Map(); + this.prevchannel=undefined; + this.message_notifications=0; + for(const roley of json.roles){ + const roleh=new Role(roley,this); + this.roles.push(roleh); + this.roleids.set(roleh.snowflake,roleh); + } + if(member instanceof User){ + Member.resolveMember(member,this).then(_=>{ + if(_){ + this.member=_; + }else{ + console.error("Member was unable to resolve"); + } + }); + }else{ + Member.new(member,this).then(_=>{ + if(_){ + this.member=_; + } + }); + } - for(const thing of json.channels){ - const temp=new Channel(thing,this); - this.channels.push(temp); - this.channelids[temp.id]=temp; - } - this.headchannels=[]; - for(const thing of this.channels){ - const parent=thing.resolveparent(this); - if(!parent){ - this.headchannels.push(thing); - } - } + for(const thing of json.channels){ + const temp=new Channel(thing,this); + this.channels.push(temp); + this.channelids[temp.id]=temp; + } + this.headchannels=[]; + for(const thing of this.channels){ + const parent=thing.resolveparent(this); + if(!parent){ + this.headchannels.push(thing); + } + } + } + notisetting(settings){ + this.message_notifications=settings.message_notifications; + } + setnotifcation(){ + let noti=this.message_notifications; + const notiselect=new Dialog( + ["vdiv", + ["radio","select notifications type", + ["all","only mentions","none"], + function(e){ + noti=["all","only mentions","none"].indexOf(e); + }, + noti + ], + ["button","","submit",_=>{ + // + fetch(this.info.api+`/users/@me/guilds/${this.id}/settings/`,{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + message_notifications: noti + }) + }); + this.message_notifications=noti; + }] + ]); + notiselect.show(); + } + confirmleave(){ + const full= new Dialog([ + "vdiv", + ["title", + "Are you sure you want to leave?" + ], + ["hdiv", + ["button", + "", + "Yes, I'm sure", + _=>{ + this.leave().then(_=>{ + full.hide(); + }); + } + ], + ["button", + "", + "Nevermind", + _=>{ + full.hide(); + } + ] - } - notisetting(settings){ - this.message_notifications=settings.message_notifications; - } - setnotifcation(){ - let noti=this.message_notifications - const notiselect=new Dialog( - ["vdiv", - ["radio","select notifications type", - ["all","only mentions","none"], - function(e){ - noti=["all","only mentions","none"].indexOf(e); - }, - noti - ], - ["button","","submit",_=>{ - // - fetch(this.info.api+`/users/@me/guilds/${this.id}/settings/`,{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({ - "message_notifications": noti - }) - }) - this.message_notifications=noti; - }] - ]); - notiselect.show(); - } - confirmleave(){ - const full= new Dialog([ - "vdiv", - ["title", - "Are you sure you want to leave?" - ], - ["hdiv", - ["button", - "", - "Yes, I'm sure", - _=>{ - this.leave().then(_=>{ - full.hide(); - }); - } - ], - ["button", - "", - "Nevermind", - _=>{ - full.hide(); - } - ] + ] + ]); + full.show(); + } + async leave(){ + return fetch(this.info.api+"/users/@me/guilds/"+this.snowflake,{ + method: "DELETE", + headers: this.headers + }); + } + printServers(){ + let build=""; + for(const thing of this.headchannels){ + build+=(thing.name+":"+thing.position)+"\n"; + for(const thingy of thing.children){ + build+=(" "+thingy.name+":"+thingy.position)+"\n"; + } + } + console.log(build); + } + calculateReorder(){ + let position=-1; + const build:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}[]=[]; + for(const thing of this.headchannels){ + const thisthing:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}={id: thing.snowflake,position: undefined,parent_id: undefined}; + if(thing.position<=position){ + thing.position=(thisthing.position=position+1); + } + position=thing.position; + console.log(position); + if(thing.move_id&&thing.move_id!==thing.parent_id){ + thing.parent_id=thing.move_id; + thisthing.parent_id=thing.parent_id; + thing.move_id=null; + } + if(thisthing.position||thisthing.parent_id){ + build.push(thisthing); + } + if(thing.children.length>0){ + const things=thing.calculateReorder(); + for(const thing of things){ + build.push(thing); + } + } + } + console.log(build); + this.printServers(); + if(build.length===0){ + return; + } + const serverbug=false; + if(serverbug){ + for(const thing of build){ + console.log(build,thing); + fetch(this.info.api+"/guilds/"+this.snowflake+"/channels",{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify([thing]) + }); + } + }else{ + fetch(this.info.api+"/guilds/"+this.snowflake+"/channels",{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify(build) + }); + } + } + get localuser(){ + return this.owner; + } + get info(){ + return this.owner.info; + } + sortchannels(){ + this.headchannels.sort((a,b)=>{ + return a.position-b.position; + }); + } + generateGuildIcon(){ + const divy=document.createElement("div"); + divy.classList.add("servernoti"); - ] - ]); - full.show(); - } - async leave(){ - return fetch(this.info.api+"/users/@me/guilds/"+this.snowflake,{ - method:"DELETE", - headers:this.headers - }) - } - printServers(){ - let build="" - for(const thing of this.headchannels){ - build+=(thing.name+":"+thing.position)+"\n"; - for(const thingy of thing.children){ - build+=(" "+thingy.name+":"+thingy.position)+"\n"; - } - } - console.log(build); - } - calculateReorder(){ - let position=-1; - let build:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}[]=[]; - for(const thing of this.headchannels){ - const thisthing:{id:SnowFlake,position:number|undefined,parent_id:SnowFlake|undefined}={id:thing.snowflake,position:undefined,parent_id:undefined} - if(thing.position<=position){ - thing.position=(thisthing.position=position+1); - } - position=thing.position; - console.log(position); - if(thing.move_id&&thing.move_id!==thing.parent_id){ - thing.parent_id=thing.move_id; - thisthing.parent_id=thing.parent_id; - thing.move_id=null; - } - if(thisthing.position||thisthing.parent_id){ - build.push(thisthing); - } - if(thing.children.length>0){ - const things=thing.calculateReorder() - for(const thing of things){ - build.push(thing); - } - } - } - console.log(build) - this.printServers(); - if(build.length===0){return} - const serverbug=false; - if(serverbug){ - for(const thing of build){ - console.log(build,thing) - fetch(this.info.api+"/guilds/"+this.snowflake+"/channels",{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify([thing]) - }); - } - }else{ - fetch(this.info.api+"/guilds/"+this.snowflake+"/channels",{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify(build) - }); - } + const noti=document.createElement("div"); + noti.classList.add("unread"); + divy.append(noti); + this.localuser.guildhtml.set(this.id,divy); + if(this.properties.icon!=null){ + const img=document.createElement("img"); + img.classList.add("pfp","servericon"); + img.src=this.info.cdn+"/icons/"+this.properties.id+"/"+this.properties.icon+".png"; + divy.appendChild(img); + img.onclick=()=>{ + console.log(this.loadGuild); + this.loadGuild(); + this.loadChannel(); + }; + Guild.contextmenu.bindContextmenu(img,this,undefined); + }else{ + const div=document.createElement("div"); + const build=this.properties.name.replace(/'s /g, " ").replace(/\w+/g, word=>word[0]).replace(/\s/g, ""); + div.textContent=build; + div.classList.add("blankserver","servericon"); + divy.appendChild(div); + div.onclick=()=>{ + this.loadGuild(); + this.loadChannel(); + }; + Guild.contextmenu.bindContextmenu(div,this,undefined); + } + return divy; + } + confirmDelete(){ + let confirmname=""; + const full= new Dialog([ + "vdiv", + ["title", + "Are you sure you want to delete "+this.properties.name+"?" + ], + ["textbox", + "Name of server:", + "", + function(this:HTMLInputElement){ + confirmname=this.value; + } + ], + ["hdiv", + ["button", + "", + "Yes, I'm sure", + _=>{ + console.log(confirmname); + if(confirmname!==this.properties.name){ + return; + } + this.delete().then(_=>{ + full.hide(); + }); + } + ], + ["button", + "", + "Nevermind", + _=>{ + full.hide(); + } + ] - } - get localuser(){ - return this.owner; - } - get info(){ - return this.owner.info; - } - sortchannels(){ - this.headchannels.sort((a,b)=>{return a.position-b.position;}); - } - generateGuildIcon(){ - const divy=document.createElement("div"); - divy.classList.add("servernoti"); + ] + ]); + full.show(); + } + async delete(){ + return fetch(this.info.api+"/guilds/"+this.snowflake+"/delete",{ + method: "POST", + headers: this.headers, + }); + } + unreads(html?:HTMLElement|undefined){ + if(html){ + this.html=html; + }else{ + html=this.html; + } + let read=true; + for(const thing of this.channels){ + if(thing.hasunreads){ + console.log(thing); + read=false; + break; + } + } + if(!html){ + return; + } + if(read){ + html.children[0].classList.remove("notiunread"); + }else{ + html.children[0].classList.add("notiunread"); + } + } + getHTML(){ + //this.printServers(); + this.sortchannels(); + this.printServers(); + const build=document.createElement("div"); - const noti=document.createElement("div"); - noti.classList.add("unread"); - divy.append(noti); - this.localuser.guildhtml.set(this.id,divy); - if(this.properties.icon!=null){ - const img=document.createElement("img"); - img.classList.add("pfp","servericon"); - img.src=this.info.cdn+"/icons/"+this.properties.id+"/"+this.properties.icon+".png"; - divy.appendChild(img) - img.onclick=()=>{ - console.log(this.loadGuild) - this.loadGuild(); - this.loadChannel(); - } - Guild.contextmenu.bindContextmenu(img,this,undefined); - }else{ - const div=document.createElement("div"); - let build=this.properties.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, ""); - div.textContent=build; - div.classList.add("blankserver","servericon") - divy.appendChild(div) - div.onclick=()=>{ - this.loadGuild(); - this.loadChannel(); - } - Guild.contextmenu.bindContextmenu(div,this,undefined) - } - return divy; - } - confirmDelete(){ - let confirmname=""; - const full= new Dialog([ - "vdiv", - ["title", - "Are you sure you want to delete "+this.properties.name+"?" - ], - ["textbox", - "Name of server:", - "", - function(this:HTMLInputElement){ - confirmname=this.value; - } - ] - , - ["hdiv", - ["button", - "", - "Yes, I'm sure", - _=>{ - console.log(confirmname) - if(confirmname!==this.properties.name){ - return; - } - this.delete().then(_=>{ - full.hide(); - }); - } - ], - ["button", - "", - "Nevermind", - _=>{ - full.hide(); - } - ] + for(const thing of this.headchannels){ + build.appendChild(thing.createguildHTML(this.isAdmin())); + } + return build; + } + isAdmin(){ + return this.member.isAdmin(); + } + async markAsRead(){ + const build:{read_states:{channel_id:SnowFlake,message_id:string|null|undefined,read_state_type:number}[]}={read_states: []}; + for(const thing of this.channels){ + if(thing.hasunreads){ + build.read_states.push({channel_id: thing.snowflake,message_id: thing.lastmessageid,read_state_type: 0}); + thing.lastreadmessageid=thing.lastmessageid; + if(!thing.myhtml)continue; + thing.myhtml.classList.remove("cunread"); + } + } + this.unreads(); + fetch(this.info.api+"/read-states/ack-bulk",{ + method: "POST", + headers: this.headers, + body: JSON.stringify(build) + }); + } + hasRole(r:Role|string){ + console.log("this should run"); + if(r instanceof Role){ + r=r.id; + } + return this.member.hasRole(r); + } + loadChannel(ID?:string|undefined){ + if(ID&&this.channelids[ID]){ + this.channelids[ID].getHTML(); + return; + } + if(this.prevchannel){ + console.log(this.prevchannel); + this.prevchannel.getHTML(); + return; + } + for(const thing of this.channels){ + if(thing.children.length===0){ + thing.getHTML(); + return; + } + } + } + loadGuild(){ + this.localuser.loadGuild(this.id); + } + updateChannel(json:channeljson){ + SnowFlake.getSnowFlakeFromID(json.id,Channel).getObject().updateChannel(json); + this.headchannels=[]; + for(const thing of this.channels){ + thing.children=[]; + } + for(const thing of this.channels){ + if(thing.resolveparent(this)){ + this.headchannels.push(thing); + } + } + this.printServers(); + } + createChannelpac(json:channeljson){ + const thischannel=new Channel(json,this); + this.channelids[json.id]=thischannel; + this.channels.push(thischannel); + thischannel.resolveparent(this); + if(!thischannel.parent){ + this.headchannels.push(thischannel); + } + this.calculateReorder(); + this.printServers(); + } + createchannels(func=this.createChannel){ + let name=""; + let category=0; + const channelselect=new Dialog( + ["vdiv", + ["radio","select channel type", + ["voice","text","announcement"], + function(e){ + console.log(e); + category={text: 0,voice: 2,announcement: 5,category: 4}[e]; + }, + 1 + ], + ["textbox","Name of channel","",function(this:HTMLInputElement){ + console.log(this); + name=this.value; + }], + ["button","","submit",function(){ + console.log(name,category); + func(name,category); + channelselect.hide(); + }] + ]); + channelselect.show(); + } + createcategory(){ + let name=""; + const category=4; + const channelselect=new Dialog( + ["vdiv", + ["textbox","Name of category","",function(this:HTMLInputElement){ + console.log(this); + name=this.value; + }], + ["button","","submit",()=>{ + console.log(name,category); + this.createChannel(name,category); + channelselect.hide(); + }] + ]); + channelselect.show(); + } + delChannel(json:channeljson){ + const channel=this.channelids[json.id]; + delete this.channelids[json.id]; - ] - ]); - full.show(); - } - async delete(){ - return fetch(this.info.api+"/guilds/"+this.snowflake+"/delete",{ - method:"POST", - headers:this.headers, - }) - } - unreads(html:HTMLElement|undefined=undefined){ - if(html){ - this.html=html; - }else{ - html=this.html; - } - let read=true; - for(const thing of this.channels){ - if(thing.hasunreads){ - console.log(thing) - read=false; - break; - } - } - if(!html){return;} - if(read){ - html.children[0].classList.remove("notiunread"); - }else{ - html.children[0].classList.add("notiunread"); - } - } - getHTML(){ - //this.printServers(); - this.sortchannels(); - this.printServers(); - const build=document.createElement("div"); + this.channels.splice(this.channels.indexOf(channel),1); + const indexy=this.headchannels.indexOf(channel); + if(indexy!==-1){ + this.headchannels.splice(indexy,1); + } - for(const thing of this.headchannels){ - build.appendChild(thing.createguildHTML(this.isAdmin())); - } - return build; - } - isAdmin(){ - return this.member.isAdmin() - } - async markAsRead(){ - const build:{read_states:{channel_id:SnowFlake,message_id:string|null,read_state_type:number}[]}={read_states:[]}; - for(const thing of this.channels){ - if(thing.hasunreads){ - build.read_states.push({channel_id:thing.snowflake,message_id:thing.lastmessageid,read_state_type:0}); - thing.lastreadmessageid=thing.lastmessageid; - if(!thing.myhtml) continue; - thing.myhtml.classList.remove("cunread"); - } - } - this.unreads(); - fetch(this.info.api+"/read-states/ack-bulk",{ - method:"POST", - headers:this.headers, - body:JSON.stringify(build) - }) - } - hasRole(r:Role|string){ - console.log("this should run"); - if(r instanceof Role){ - r=r.id; - } - return this.member.hasRole(r); - } - loadChannel(ID:string|undefined=undefined){ - if(ID&&this.channelids[ID]){ - this.channelids[ID].getHTML(); - return; - } - if(this.prevchannel){ - console.log(this.prevchannel) - this.prevchannel.getHTML(); - return; - } - for(const thing of this.channels){ - if(thing.children.length===0){ - thing.getHTML(); - return - } - } - } - loadGuild(){ - this.localuser.loadGuild(this.id); - } - updateChannel(json:channeljson){ - SnowFlake.getSnowFlakeFromID(json.id,Channel).getObject().updateChannel(json); - this.headchannels=[]; - for(const thing of this.channels){ - thing.children=[]; - } - for(const thing of this.channels){ - if(thing.resolveparent(this)){ - this.headchannels.push(thing); - } - } - this.printServers(); - } - createChannelpac(json:channeljson){ - const thischannel=new Channel(json,this); - this.channelids[json.id]=thischannel; - this.channels.push(thischannel); - thischannel.resolveparent(this); - if(!thischannel.parent){ - this.headchannels.push(thischannel); - } - this.calculateReorder(); - this.printServers(); - } - createchannels(func=this.createChannel){ - let name=""; - let category=0; - const channelselect=new Dialog( - ["vdiv", - ["radio","select channel type", - ["voice","text","announcement"], - function(e){ - console.log(e) - category={"text":0,"voice":2,"announcement":5,"category":4}[e] - }, - 1 - ], - ["textbox","Name of channel","",function(this:HTMLInputElement){ - console.log(this) - name=this.value - }], - ["button","","submit",function(){ - console.log(name,category) - func(name,category); - channelselect.hide(); - }.bind(this)] - ]); - channelselect.show(); - } - createcategory(){ - let name=""; - let category=4; - const channelselect=new Dialog( - ["vdiv", - ["textbox","Name of category","",function(this:HTMLInputElement){ - console.log(this); - name=this.value; - }], - ["button","","submit",()=>{ - console.log(name,category) - this.createChannel(name,category); - channelselect.hide(); - }] - ]); - channelselect.show(); - } - delChannel(json:channeljson){ - const channel=this.channelids[json.id]; - delete this.channelids[json.id]; - - this.channels.splice(this.channels.indexOf(channel),1); - const indexy=this.headchannels.indexOf(channel); - if(indexy!==-1){ - this.headchannels.splice(indexy,1); - } - - /* + /* const build=[]; for(const thing of this.channels){ console.log(thing.id); @@ -518,50 +521,50 @@ class Guild{ } this.channels=build; */ - this.printServers(); - } - createChannel(name:string,type:number){ - fetch(this.info.api+"/guilds/"+this.snowflake+"/channels",{ - method:"POST", - headers:this.headers, - body:JSON.stringify({name: name, type: type}) - }) - } - async createRole(name:string){ - const fetched=await fetch(this.info.api+"/guilds/"+this.snowflake+"roles",{ - method:"POST", - headers:this.headers, - body:JSON.stringify({ - name:name, - color:0, - permissions:"0" - }) - }) - const json=await fetched.json(); - const role=new Role(json,this); - this.roleids.set(role.snowflake,role); - this.roles.push(role); - return role; - } - async updateRolePermissions(id:string,perms:Permissions){ - const role=this.roleids[id]; - role.permissions.allow=perms.allow; - role.permissions.deny=perms.deny; + this.printServers(); + } + createChannel(name:string,type:number){ + fetch(this.info.api+"/guilds/"+this.snowflake+"/channels",{ + method: "POST", + headers: this.headers, + body: JSON.stringify({name, type}) + }); + } + async createRole(name:string){ + const fetched=await fetch(this.info.api+"/guilds/"+this.snowflake+"roles",{ + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + color: 0, + permissions: "0" + }) + }); + const json=await fetched.json(); + const role=new Role(json,this); + this.roleids.set(role.snowflake,role); + this.roles.push(role); + return role; + } + async updateRolePermissions(id:string,perms:Permissions){ + const role=this.roleids[id]; + role.permissions.allow=perms.allow; + role.permissions.deny=perms.deny; - await fetch(this.info.api+"/guilds/"+this.snowflake+"/roles/"+this.snowflake,{ - method:"PATCH", - headers:this.headers, - body:JSON.stringify({ - color:role.color, - hoist:role.hoist, - icon:role.icon, - mentionable:role.mentionable, - name:role.name, - permissions:role.permissions.allow.toString(), - unicode_emoji:role.unicode_emoji, - }) - }) - } + await fetch(this.info.api+"/guilds/"+this.snowflake+"/roles/"+this.snowflake,{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + color: role.color, + hoist: role.hoist, + icon: role.icon, + mentionable: role.mentionable, + name: role.name, + permissions: role.permissions.allow.toString(), + unicode_emoji: role.unicode_emoji, + }) + }); + } } Guild.setupcontextmenu(); -export { Guild }; +export{ Guild }; diff --git a/webpage/home.html b/webpage/home.html index 968b747..c54b337 100644 --- a/webpage/home.html +++ b/webpage/home.html @@ -1,48 +1,48 @@ - - - - Jank Client - - - - - - - + + + + Jank Client + + + + + + + - -
- -

Jank Client

-

Spacebar Guild

-

Github

-
-
+ +
+ +

Jank Client

+

Spacebar Guild

+

Github

+
+
-

Welcome to Jank Client

-
-

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

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

Spacebar compatible Instances:

-
-
-
-
-

Contribute to Jank Client

-

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


+

Welcome to Jank Client

+
+

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

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

Spacebar compatible Instances:

+
+
+
+
+

Contribute to Jank Client

+

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


Github

-
-
- - +
+
+ + diff --git a/webpage/home.ts b/webpage/home.ts index 2dfbffd..5d299a6 100644 --- a/webpage/home.ts +++ b/webpage/home.ts @@ -1,64 +1,64 @@ -import {mobile} from "./login.js"; +import{mobile}from"./login.js"; console.log(mobile); const serverbox=document.getElementById("instancebox") as HTMLDivElement; fetch("/instances.json").then(_=>_.json()).then((json:{name:string,description?:string,descriptionLong?:string,image?:string,url?:string,display?:boolean,online?:boolean, uptime:{alltime:number,daytime:number,weektime:number}, urls:{wellknown:string,api:string,cdn:string,gateway:string,login?:string}}[])=>{ - console.warn(json); - for(const instance of json){ - if(instance.display===false){ - continue; - } - const div=document.createElement("div"); - div.classList.add("flexltr","instance"); - if(instance.image){ - const img=document.createElement("img"); - img.src=instance.image; - div.append(img); - } - const statbox=document.createElement("div"); - statbox.classList.add("flexttb"); + console.warn(json); + for(const instance of json){ + if(instance.display===false){ + continue; + } + const div=document.createElement("div"); + div.classList.add("flexltr","instance"); + if(instance.image){ + const img=document.createElement("img"); + img.src=instance.image; + div.append(img); + } + const statbox=document.createElement("div"); + statbox.classList.add("flexttb"); - { - const textbox=document.createElement("div"); - textbox.classList.add("flexttb","instatancetextbox"); - const title=document.createElement("h2"); - title.innerText=instance.name; - if(instance.online!==undefined){ - const status=document.createElement("span"); - status.innerText=instance.online?"Online":"Offline"; - status.classList.add("instanceStatus"); - title.append(status); - } - textbox.append(title); - if(instance.description||instance.descriptionLong){ - const p=document.createElement("p"); - if(instance.descriptionLong){ - p.innerText=instance.descriptionLong; - }else{ - p.innerText=instance.description; - } - textbox.append(p); - } - statbox.append(textbox) - } - if(instance.uptime){ - const stats=document.createElement("div"); - stats.classList.add("flexltr"); - const span=document.createElement("span"); - span.innerText=`Uptime: All time: ${Math.floor(instance.uptime.alltime*100)}% This week: ${Math.floor(instance.uptime.weektime*100)}% Today: ${Math.floor(instance.uptime.daytime*100)}%` - stats.append(span); - statbox.append(stats); - } - div.append(statbox); - div.onclick=_=>{ - if(instance.online){ - window.location.href="/register.html?instance="+encodeURI(instance.name); - }else{ - alert("Instance is offline, can't connect"); - } - } - serverbox.append(div); - } - }) + { + const textbox=document.createElement("div"); + textbox.classList.add("flexttb","instatancetextbox"); + const title=document.createElement("h2"); + title.innerText=instance.name; + if(instance.online!==undefined){ + const status=document.createElement("span"); + status.innerText=instance.online?"Online":"Offline"; + status.classList.add("instanceStatus"); + title.append(status); + } + textbox.append(title); + if(instance.description||instance.descriptionLong){ + const p=document.createElement("p"); + if(instance.descriptionLong){ + p.innerText=instance.descriptionLong; + }else if(instance.description){ + p.innerText=instance.description; + } + textbox.append(p); + } + statbox.append(textbox); + } + if(instance.uptime){ + const stats=document.createElement("div"); + stats.classList.add("flexltr"); + const span=document.createElement("span"); + span.innerText=`Uptime: All time: ${Math.floor(instance.uptime.alltime*100)}% This week: ${Math.floor(instance.uptime.weektime*100)}% Today: ${Math.floor(instance.uptime.daytime*100)}%`; + stats.append(span); + statbox.append(stats); + } + div.append(statbox); + div.onclick=_=>{ + if(instance.online){ + window.location.href="/register.html?instance="+encodeURI(instance.name); + }else{ + alert("Instance is offline, can't connect"); + } + }; + serverbox.append(div); + } +}); diff --git a/webpage/index.html b/webpage/index.html index 989a773..8c9df93 100644 --- a/webpage/index.html +++ b/webpage/index.html @@ -1,80 +1,80 @@ - - - - Jank Client - - - - - - + + + + Jank Client + + + + + + - - + + - - + + -
-
- -

Jank Client is loading

-

This shouldn't take long

-

Switch Accounts

-
-
-
-
-
-
-
-
-

Server Name

-
-
-
-
- +
+
+ +

Jank Client is loading

+

This shouldn't take long

+

Switch Accounts

+
+
+
+
+
+
+
+
+

Server Name

+
+
+
+
+ -
-

USERNAME

-

STATUS

-
-
+
+

USERNAME

+

STATUS

+
+
-
- -
-
-
-
-
- - Channel name - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- +
+ +
+
+
+
+
+ + Channel name + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ diff --git a/webpage/index.ts b/webpage/index.ts index 87ea021..1afdfc7 100644 --- a/webpage/index.ts +++ b/webpage/index.ts @@ -1,183 +1,187 @@ -import { Localuser } from "./localuser.js"; -import {Contextmenu} from "./contextmenu.js"; -import {mobile, getBulkUsers,setTheme, Specialuser} from "./login.js"; -import { MarkDown } from "./markdown.js"; -import { Message } from "./message.js"; -import { File } from "./file.js"; -(async()=>{ - async function waitforload(){ - let res; - new Promise(r=>{res=r}); - document.addEventListener("DOMContentLoaded", function(){ - res(); - }); - await res; - } - await waitforload(); +import{ Localuser }from"./localuser.js"; +import{Contextmenu}from"./contextmenu.js"; +import{mobile, getBulkUsers,setTheme, Specialuser}from"./login.js"; +import{ MarkDown }from"./markdown.js"; +import{ Message }from"./message.js"; +import{ File }from"./file.js"; +(async ()=>{ + async function waitforload(){ + let res; + new Promise(r=>{ + res=r; + }); + document.addEventListener("DOMContentLoaded", ()=>{ + res(); + }); + await res; + } + await waitforload(); - const users=getBulkUsers(); - if(!users.currentuser){ - window.location.href = '/login.html'; - } + const users=getBulkUsers(); + if(!users.currentuser){ + window.location.href = "/login.html"; + } - function showAccountSwitcher(){ - const table=document.createElement("div"); - for(const thing of Object.values(users.users)){ - const specialuser=thing as Specialuser; - console.log(specialuser.pfpsrc) + function showAccountSwitcher(){ + const table=document.createElement("div"); + for(const thing of Object.values(users.users)){ + const specialuser=thing as Specialuser; + console.log(specialuser.pfpsrc); - const userinfo=document.createElement("div"); - userinfo.classList.add("flexltr","switchtable"); - const pfp=document.createElement("img"); - userinfo.append(pfp); + const userinfo=document.createElement("div"); + userinfo.classList.add("flexltr","switchtable"); + const pfp=document.createElement("img"); + userinfo.append(pfp); - const user=document.createElement("div"); - userinfo.append(user); - user.append(specialuser.username); - user.append(document.createElement("br")); - const span=document.createElement("span"); - span.textContent=specialuser.serverurls.wellknown.replace("https://","").replace("http://",""); - user.append(span); - user.classList.add("userinfo") - span.classList.add("serverURL") + const user=document.createElement("div"); + userinfo.append(user); + user.append(specialuser.username); + user.append(document.createElement("br")); + const span=document.createElement("span"); + span.textContent=specialuser.serverurls.wellknown.replace("https://","").replace("http://",""); + user.append(span); + user.classList.add("userinfo"); + span.classList.add("serverURL"); - pfp.src=specialuser.pfpsrc; - pfp.classList.add("pfp"); - table.append(userinfo); - userinfo.addEventListener("click",_=>{ - thisuser.unload(); - thisuser.swapped=true; - const loading=document.getElementById("loading") as HTMLDivElement; - loading.classList.remove("doneloading"); - loading.classList.add("loading"); - thisuser=new Localuser(specialuser); - users["currentuser"]=specialuser.uid; - localStorage.setItem("userinfos",JSON.stringify(users)); - thisuser.initwebsocket().then(_=>{ - thisuser.loaduser(); - thisuser.init(); - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading") - - }); - userinfo.remove(); - }) - } - { - const td=document.createElement("div"); - td.classList.add("switchtable") - td.append("Switch accounts ⇌"); - td.addEventListener("click",_=>{ - window.location.href="/login.html"; - }) - table.append(td); - } - table.classList.add("accountSwitcher"); - if(Contextmenu.currentmenu!=""){ - Contextmenu.currentmenu.remove(); - } - Contextmenu.currentmenu=table; - console.log(table); - document.body.append(table); - } - { - const userinfo=document.getElementById("userinfo") as HTMLDivElement; - userinfo.addEventListener("click",_=>{ - _.stopImmediatePropagation(); - showAccountSwitcher(); - }) - const switchaccounts=document.getElementById("switchaccounts") as HTMLDivElement; - switchaccounts.addEventListener("click",_=>{ - _.stopImmediatePropagation(); - showAccountSwitcher(); - }) - console.log("this ran") - } - let thisuser:Localuser; - try{ - console.log(users.users,users.currentuser) - thisuser=new Localuser(users.users[users.currentuser]); - thisuser.initwebsocket().then(_=>{ - thisuser.loaduser(); - thisuser.init(); - const loading=document.getElementById("loading") as HTMLDivElement; - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading") - }); - }catch(e){ - console.error(e); - (document.getElementById("load-desc") as HTMLSpanElement).textContent="Account unable to start"; - thisuser=new Localuser(-1); - } + pfp.src=specialuser.pfpsrc; + pfp.classList.add("pfp"); + table.append(userinfo); + userinfo.addEventListener("click",_=>{ + thisuser.unload(); + thisuser.swapped=true; + const loading=document.getElementById("loading") as HTMLDivElement; + loading.classList.remove("doneloading"); + loading.classList.add("loading"); + thisuser=new Localuser(specialuser); + users.currentuser=specialuser.uid; + localStorage.setItem("userinfos",JSON.stringify(users)); + thisuser.initwebsocket().then(_=>{ + thisuser.loaduser(); + thisuser.init(); + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + userinfo.remove(); + }); + } + { + const td=document.createElement("div"); + td.classList.add("switchtable"); + td.append("Switch accounts ⇌"); + td.addEventListener("click",_=>{ + window.location.href="/login.html"; + }); + table.append(td); + } + table.classList.add("accountSwitcher"); + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); + } + Contextmenu.currentmenu=table; + console.log(table); + document.body.append(table); + } + { + const userinfo=document.getElementById("userinfo") as HTMLDivElement; + userinfo.addEventListener("click",_=>{ + _.stopImmediatePropagation(); + showAccountSwitcher(); + }); + const switchaccounts=document.getElementById("switchaccounts") as HTMLDivElement; + switchaccounts.addEventListener("click",_=>{ + _.stopImmediatePropagation(); + showAccountSwitcher(); + }); + console.log("this ran"); + } + let thisuser:Localuser; + try{ + console.log(users.users,users.currentuser); + thisuser=new Localuser(users.users[users.currentuser]); + thisuser.initwebsocket().then(_=>{ + thisuser.loaduser(); + thisuser.init(); + const loading=document.getElementById("loading") as HTMLDivElement; + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + }catch(e){ + console.error(e); + (document.getElementById("load-desc") as HTMLSpanElement).textContent="Account unable to start"; + thisuser=new Localuser(-1); + } - { - const menu=new Contextmenu("create rightclick");//Really should go into the localuser class, but that's a later thing - menu.addbutton("Create channel",function(){ - if(thisuser.lookingguild){ - thisuser.lookingguild.createchannels(); - } - },null,_=>{return thisuser.isAdmin()}) + { + const menu=new Contextmenu("create rightclick");//Really should go into the localuser class, but that's a later thing + menu.addbutton("Create channel",()=>{ + if(thisuser.lookingguild){ + thisuser.lookingguild.createchannels(); + } + },null,_=>{ + return thisuser.isAdmin(); + }); - menu.addbutton("Create category",function(){ - if(thisuser.lookingguild){ - thisuser.lookingguild.createcategory(); - } - },null,_=>{return thisuser.isAdmin()}) - menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement,0,0) - } + menu.addbutton("Create category",()=>{ + if(thisuser.lookingguild){ + thisuser.lookingguild.createcategory(); + } + },null,_=>{ + return thisuser.isAdmin(); + }); + menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement,0,0); + } - const pasteimage=document.getElementById("pasteimage") as HTMLDivElement; - let replyingto:Message|null=null; - async function enter(event){ - const channel=thisuser.channelfocus - if(!channel||!thisuser.channelfocus) return; - channel.typingstart(); - if(event.key === "Enter"&&!event.shiftKey){ - event.preventDefault(); - if(channel.editing){ - channel.editing.edit(markdown.rawString); - channel.editing=null; - }else{ - replyingto= thisuser.channelfocus.replyingto; - let replying=replyingto; - if(replyingto?.div){ - replyingto.div.classList.remove("replying"); - } - thisuser.channelfocus.replyingto=null; - channel.sendMessage(markdown.rawString,{ - attachments:images, - embeds:[], - replyingto:replying - }) - thisuser.channelfocus.makereplybox(); - } - while(images.length!=0){ - images.pop(); - pasteimage.removeChild(imageshtml.pop() as HTMLElement); - } - typebox.innerHTML=""; - return; - } - } + const pasteimage=document.getElementById("pasteimage") as HTMLDivElement; + let replyingto:Message|null=null; + async function enter(event){ + const channel=thisuser.channelfocus; + if(!channel||!thisuser.channelfocus)return; + channel.typingstart(); + if(event.key === "Enter"&&!event.shiftKey){ + event.preventDefault(); + if(channel.editing){ + channel.editing.edit(markdown.rawString); + channel.editing=null; + }else{ + replyingto= thisuser.channelfocus.replyingto; + const replying=replyingto; + if(replyingto?.div){ + replyingto.div.classList.remove("replying"); + } + thisuser.channelfocus.replyingto=null; + channel.sendMessage(markdown.rawString,{ + attachments: images, + embeds: [], + replyingto: replying + }); + thisuser.channelfocus.makereplybox(); + } + while(images.length!=0){ + images.pop(); + pasteimage.removeChild(imageshtml.pop() as HTMLElement); + } + typebox.innerHTML=""; + } + } - const typebox=document.getElementById("typebox") as HTMLDivElement; - const markdown=new MarkDown("",thisuser); - markdown.giveBox(typebox); - typebox["markdown"]=markdown; - typebox.addEventListener("keyup",enter); - typebox.addEventListener("keydown",event=>{ - if(event.key === "Enter"&&!event.shiftKey) event.preventDefault(); - }); - console.log(typebox) - typebox.onclick=console.log; + const typebox=document.getElementById("typebox") as HTMLDivElement; + const markdown=new MarkDown("",thisuser); + markdown.giveBox(typebox); + typebox["markdown"]=markdown; + typebox.addEventListener("keyup",enter); + typebox.addEventListener("keydown",event=>{ + if(event.key === "Enter"&&!event.shiftKey) event.preventDefault(); + }); + console.log(typebox); + typebox.onclick=console.log; - /* + /* function getguildinfo(){ const path=window.location.pathname.split("/"); const channel=path[3]; @@ -185,41 +189,41 @@ import { File } from "./file.js"; } */ - const images:Blob[]=[]; - const imageshtml:HTMLElement[]=[]; + const images:Blob[]=[]; + const imageshtml:HTMLElement[]=[]; - document.addEventListener('paste', async (e) => { - if(!e.clipboardData) return; - Array.from(e.clipboardData.files).forEach(async (f) => { - const file=File.initFromBlob(f); - e.preventDefault(); - const html=file.upHTML(images,f); - pasteimage.appendChild(html); - images.push(f) - imageshtml.push(html); - }); - }); + document.addEventListener("paste", async e=>{ + if(!e.clipboardData)return; + Array.from(e.clipboardData.files).forEach(async f=>{ + const file=File.initFromBlob(f); + e.preventDefault(); + const html=file.upHTML(images,f); + pasteimage.appendChild(html); + images.push(f); + imageshtml.push(html); + }); + }); - setTheme(); + setTheme(); - function userSettings(){ - thisuser.showusersettings(); - } - (document.getElementById("settings") as HTMLImageElement).onclick=userSettings; + function userSettings(){ + thisuser.showusersettings(); + } + (document.getElementById("settings") as HTMLImageElement).onclick=userSettings; - if(mobile){ - (document.getElementById("channelw") as HTMLDivElement).onclick=()=>{ - ((document.getElementById("channels") as HTMLDivElement).parentNode as HTMLElement).classList.add("collapse"); - (document.getElementById("servertd") as HTMLDivElement).classList.add("collapse"); - (document.getElementById("servers") as HTMLDivElement).classList.add("collapse"); - } - (document.getElementById("mobileback") as HTMLDivElement).textContent="#"; - (document.getElementById("mobileback") as HTMLDivElement).onclick=()=>{ - ((document.getElementById("channels") as HTMLDivElement).parentNode as HTMLElement).classList.remove("collapse"); - (document.getElementById("servertd") as HTMLDivElement).classList.remove("collapse"); - (document.getElementById("servers") as HTMLDivElement).classList.remove("collapse"); - } - } -})() + if(mobile){ + (document.getElementById("channelw") as HTMLDivElement).onclick=()=>{ + ((document.getElementById("channels") as HTMLDivElement).parentNode as HTMLElement).classList.add("collapse"); + (document.getElementById("servertd") as HTMLDivElement).classList.add("collapse"); + (document.getElementById("servers") as HTMLDivElement).classList.add("collapse"); + }; + (document.getElementById("mobileback") as HTMLDivElement).textContent="#"; + (document.getElementById("mobileback") as HTMLDivElement).onclick=()=>{ + ((document.getElementById("channels") as HTMLDivElement).parentNode as HTMLElement).classList.remove("collapse"); + (document.getElementById("servertd") as HTMLDivElement).classList.remove("collapse"); + (document.getElementById("servers") as HTMLDivElement).classList.remove("collapse"); + }; + } +})(); diff --git a/webpage/infiniteScroller.ts b/webpage/infiniteScroller.ts index 6d7e4d4..60b2449 100644 --- a/webpage/infiniteScroller.ts +++ b/webpage/infiniteScroller.ts @@ -1,305 +1,294 @@ class InfiniteScroller{ - readonly getIDFromOffset:(ID:string,offset:number)=>Promise; - readonly getHTMLFromID:(ID:string)=>Promise; - readonly destroyFromID:(ID:string)=>Promise; - readonly reachesBottom:()=>void; - private readonly minDist=2000; - private readonly fillDist=3000; - private readonly maxDist=6000; - HTMLElements:[HTMLElement,string][]=[]; - div:HTMLDivElement|null; - scroll:HTMLDivElement|null; - constructor(getIDFromOffset:InfiniteScroller["getIDFromOffset"],getHTMLFromID:InfiniteScroller["getHTMLFromID"],destroyFromID:InfiniteScroller["destroyFromID"],reachesBottom:InfiniteScroller["reachesBottom"]=()=>{}){ - this.getIDFromOffset=getIDFromOffset; - this.getHTMLFromID=getHTMLFromID; - this.destroyFromID=destroyFromID; - this.reachesBottom=reachesBottom; - } - timeout:NodeJS.Timeout|null; - async getDiv(initialId:string,bottom=true):Promise{ - const div=document.createElement("div"); - div.classList.add("messagecontainer"); - //div.classList.add("flexttb") - const scroll=document.createElement("div"); - scroll.classList.add("flexttb","scroller") - div.appendChild(scroll); - this.div=div; - //this.interval=setInterval(this.updatestuff.bind(this,true),100); + readonly getIDFromOffset:(ID:string,offset:number)=>Promise; + readonly getHTMLFromID:(ID:string)=>Promise; + readonly destroyFromID:(ID:string)=>Promise; + readonly reachesBottom:()=>void; + private readonly minDist=2000; + private readonly fillDist=3000; + private readonly maxDist=6000; + HTMLElements:[HTMLElement,string][]=[]; + div:HTMLDivElement|null; + scroll:HTMLDivElement|null; + constructor(getIDFromOffset:InfiniteScroller["getIDFromOffset"],getHTMLFromID:InfiniteScroller["getHTMLFromID"],destroyFromID:InfiniteScroller["destroyFromID"],reachesBottom:InfiniteScroller["reachesBottom"]=()=>{}){ + this.getIDFromOffset=getIDFromOffset; + this.getHTMLFromID=getHTMLFromID; + this.destroyFromID=destroyFromID; + this.reachesBottom=reachesBottom; + } + timeout:NodeJS.Timeout|null; + async getDiv(initialId:string,bottom=true):Promise{ + const div=document.createElement("div"); + div.classList.add("messagecontainer"); + //div.classList.add("flexttb") + const scroll=document.createElement("div"); + scroll.classList.add("flexttb","scroller"); + div.appendChild(scroll); + this.div=div; + //this.interval=setInterval(this.updatestuff.bind(this,true),100); - this.scroll=scroll; - this.div.addEventListener("scroll",_=>{ - if(this.scroll) this.scrollTop=this.scroll.scrollTop; - this.watchForChange() - }); - this.scroll.addEventListener("scroll",_=>{ - if(null===this.timeout){ - this.timeout=setTimeout(this.updatestuff.bind(this),300); - } + this.scroll=scroll; + this.div.addEventListener("scroll",_=>{ + if(this.scroll)this.scrollTop=this.scroll.scrollTop; + this.watchForChange(); + }); + this.scroll.addEventListener("scroll",_=>{ + if(this.timeout===null){ + this.timeout=setTimeout(this.updatestuff.bind(this),300); + } - this.watchForChange() - }); - { - let oldheight=0; - new ResizeObserver(_=>{ - this.updatestuff(); - const change=oldheight-div.offsetHeight; - if(change>0&&this.scroll){ - this.scroll.scrollTop+=change; - } - oldheight=div.offsetHeight; - this.watchForChange(); - }).observe(div); - } - new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); + this.watchForChange(); + }); + { + let oldheight=0; + new ResizeObserver(_=>{ + this.updatestuff(); + const change=oldheight-div.offsetHeight; + if(change>0&&this.scroll){ + this.scroll.scrollTop+=change; + } + oldheight=div.offsetHeight; + this.watchForChange(); + }).observe(div); + } + new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); - await this.firstElement(initialId) - this.updatestuff(); - await this.watchForChange().then(_=>{ - this.updatestuff(); - }) - return div; - } - scrollBottom:number; - scrollTop:number; - needsupdate=true; - averageheight:number=60; - async updatestuff(){ - this.timeout=null; - if(!this.scroll) return; - this.scrollBottom = this.scroll.scrollHeight - this.scroll.scrollTop - this.scroll.clientHeight; - this.averageheight=this.scroll.scrollHeight/this.HTMLElements.length; - if(this.averageheight<10){ - this.averageheight=60; - } - this.scrollTop=this.scroll.scrollTop; - if(!this.scrollBottom){ - if(!await this.watchForChange()){ - this.reachesBottom(); - } - } - if(!this.scrollTop){ - await this.watchForChange() - } - this.needsupdate=false; - //this.watchForChange(); - } - async firstElement(id:string){ - if(!this.scroll) return; - const html=await this.getHTMLFromID(id); - this.scroll.appendChild(html); - this.HTMLElements.push([html,id]); - } - currrunning:boolean=false; - async addedBottom(){ - this.updatestuff(); - const func=this.snapBottom(); - await this.watchForChange(); - func(); - } - snapBottom(){ - const scrollBottom=this.scrollBottom; - return ()=>{ - if(this.scroll&&scrollBottom<30){ - this.scroll.scrollTop=this.scroll.scrollHeight+20; - } - } - } - private async watchForTop(already=false,fragement=new DocumentFragment()):Promise{ - if(!this.scroll) return false; - try{ - let again=false; - if(this.scrollTop<(already?this.fillDist:this.minDist)){ - let nextid:string|undefined; - const firstelm=this.HTMLElements.at(0); - if(firstelm){ - const previd=firstelm[1]; - nextid=await this.getIDFromOffset(previd,1); - } + await this.firstElement(initialId); + this.updatestuff(); + await this.watchForChange().then(_=>{ + this.updatestuff(); + }); + return div; + } + scrollBottom:number; + scrollTop:number; + needsupdate=true; + averageheight:number=60; + async updatestuff(){ + this.timeout=null; + if(!this.scroll)return; + this.scrollBottom = this.scroll.scrollHeight - this.scroll.scrollTop - this.scroll.clientHeight; + this.averageheight=this.scroll.scrollHeight/this.HTMLElements.length; + if(this.averageheight<10){ + this.averageheight=60; + } + this.scrollTop=this.scroll.scrollTop; + if(!this.scrollBottom && !await this.watchForChange()){ + this.reachesBottom(); + } + if(!this.scrollTop){ + await this.watchForChange(); + } + this.needsupdate=false; + //this.watchForChange(); + } + async firstElement(id:string){ + if(!this.scroll)return; + const html=await this.getHTMLFromID(id); + this.scroll.appendChild(html); + this.HTMLElements.push([html,id]); + } + currrunning:boolean=false; + async addedBottom(){ + this.updatestuff(); + const func=this.snapBottom(); + await this.watchForChange(); + func(); + } + snapBottom(){ + const scrollBottom=this.scrollBottom; + return()=>{ + if(this.scroll&&scrollBottom<30){ + this.scroll.scrollTop=this.scroll.scrollHeight+20; + } + }; + } + private async watchForTop(already=false,fragement=new DocumentFragment()):Promise{ + if(!this.scroll)return false; + try{ + let again=false; + if(this.scrollTop<(already?this.fillDist:this.minDist)){ + let nextid:string|undefined; + const firstelm=this.HTMLElements.at(0); + if(firstelm){ + const previd=firstelm[1]; + nextid=await this.getIDFromOffset(previd,1); + } - if(!nextid){ + if(!nextid){ - }else{ - const html=await this.getHTMLFromID(nextid); - if(!html){ - this.destroyFromID(nextid); - return false; - } - again=true; - fragement.prepend(html); - this.HTMLElements.unshift([html,nextid]); - this.scrollTop+=this.averageheight; - }; - } - if(this.scrollTop>this.maxDist){ + }else{ + const html=await this.getHTMLFromID(nextid); + if(!html){ + this.destroyFromID(nextid); + return false; + } + again=true; + fragement.prepend(html); + this.HTMLElements.unshift([html,nextid]); + this.scrollTop+=this.averageheight; + } + } + if(this.scrollTop>this.maxDist){ + const html=this.HTMLElements.shift(); + if(html){ + again=true; + await this.destroyFromID(html[1]); + this.scrollTop-=this.averageheight; + } + } + if(again){ + await this.watchForTop(true,fragement); + } + return again; + }finally{ + if(!already){ + if(this.scroll.scrollTop===0){ + this.scrollTop=1; + this.scroll.scrollTop=10; + } + this.scroll.prepend(fragement,fragement); + } + } + } + async watchForBottom(already=false,fragement=new DocumentFragment()):Promise{ + if(!this.scroll)return false; + try{ + let again=false; + const scrollBottom = this.scrollBottom; + if(scrollBottom<(already?this.fillDist:this.minDist)){ + let nextid:string|undefined; + const lastelm=this.HTMLElements.at(-1); + if(lastelm){ + const previd=lastelm[1]; + nextid=await this.getIDFromOffset(previd,-1); + } + if(!nextid){ + }else{ + again=true; + const html=await this.getHTMLFromID(nextid); + fragement.appendChild(html); + this.HTMLElements.push([html,nextid]); + this.scrollBottom+=this.averageheight; + } + } + if(scrollBottom>this.maxDist){ + const html=this.HTMLElements.pop(); + if(html){ + await this.destroyFromID(html[1]); + this.scrollBottom-=this.averageheight; + again=true; + } + } + if(again){ + await this.watchForBottom(true,fragement); + } + return again; + }finally{ + if(!already){ + this.scroll.append(fragement); + if(this.scrollBottom<30){ + this.scroll.scrollTop=this.scroll.scrollHeight; + } + } + } + } + watchtime:boolean=false; + changePromise:Promise|undefined; + async watchForChange():Promise{ + if(this.currrunning){ + this.watchtime=true; + if(this.changePromise){ + return await this.changePromise; + }else{ + return false; + } + }else{ + this.watchtime=false; + this.currrunning=true; + } + this.changePromise=new Promise(async res=>{ + try{ + try{ + if(!this.div){ + res(false);return false; + } + const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[]; + const changed=(out[0].value||out[1].value); + if(this.timeout===null&&changed){ + this.timeout=setTimeout(this.updatestuff.bind(this),300); + } + if(!this.currrunning){ + console.error("something really bad happened"); + } - - const html=this.HTMLElements.shift(); - if(html){ - again=true; - await this.destroyFromID(html[1]); - this.scrollTop-=this.averageheight; - } - } - if(again){ - await this.watchForTop(true,fragement); - } - return again; - }finally{ - if(!already){ - if(this.scroll.scrollTop===0){ - this.scrollTop=1; - this.scroll.scrollTop=10; - } - this.scroll.prepend(fragement,fragement); - } - } - } - async watchForBottom(already=false,fragement=new DocumentFragment()):Promise{ - if(!this.scroll) return false; - try{ - let again=false; - const scrollBottom = this.scrollBottom; - if(scrollBottom<(already?this.fillDist:this.minDist)){ - - let nextid:string|undefined; - const lastelm=this.HTMLElements.at(-1); - if(lastelm){ - const previd=lastelm[1]; - nextid=await this.getIDFromOffset(previd,-1); - } - if(!nextid){ - }else{ - again=true; - const html=await this.getHTMLFromID(nextid); - fragement.appendChild(html); - this.HTMLElements.push([html,nextid]); - this.scrollBottom+=this.averageheight; - }; - } - if(scrollBottom>this.maxDist){ - - - const html=this.HTMLElements.pop(); - if(html){ - await this.destroyFromID(html[1]); - this.scrollBottom-=this.averageheight; - again=true; - } - } - if(again){ - await this.watchForBottom(true,fragement); - } - return again; - }finally{ - if(!already){ - this.scroll.append(fragement); - if(this.scrollBottom<30){ - this.scroll.scrollTop=this.scroll.scrollHeight; - } - } - } - } - watchtime:boolean=false; - changePromise:Promise|undefined; - async watchForChange():Promise{ - if(this.currrunning){ - this.watchtime=true; - if(this.changePromise){ - return await this.changePromise; - }else{ - return false; - } - }else{ - this.watchtime=false; - this.currrunning=true; - - } - this.changePromise=new Promise(async res=>{ - try{ - - try{ - if(!this.div){res(false);return false} - const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[]; - const changed=(out[0].value||out[1].value); - if(null===this.timeout&&changed){ - this.timeout=setTimeout(this.updatestuff.bind(this),300); - } - if(!this.currrunning){console.error("something really bad happened")} - - res(!!changed); - return !!changed; - - }catch(e){ - console.error(e); - } - res(false); - return false; - }catch(e){ - throw e; - }finally{ - - - setTimeout(_=>{ - this.changePromise=undefined; - this.currrunning=false; - if(this.watchtime){ - this.watchForChange(); - } - },300) - - } - }) - return await this.changePromise; - } - async focus(id:string,flash=true){ - - let element:HTMLElement|undefined; - for(const thing of this.HTMLElements){ - if(thing[1]===id){ - element=thing[0]; - } - } - console.log(id,element,this.HTMLElements.length,":3"); - if(element){ - - if(flash){ - element.scrollIntoView({ - behavior:"smooth", - block:"center" - }); - await new Promise(resolve => setTimeout(resolve, 1000)); - element.classList.remove("jumped"); - await new Promise(resolve => setTimeout(resolve, 100)); - element.classList.add("jumped"); - }else{ - element.scrollIntoView(); - } - }else{ - for(const thing of this.HTMLElements){ - await this.destroyFromID(thing[1]); - } - this.HTMLElements=[]; - await this.firstElement(id); - this.updatestuff(); - await this.watchForChange(); - await new Promise(resolve => setTimeout(resolve, 100)); - await this.focus(id,true); - } - } - async delete():Promise{ - for(const thing of this.HTMLElements){ - await this.destroyFromID(thing[1]); - } - this.HTMLElements=[]; - if(this.timeout){ - clearTimeout(this.timeout); - } - if(this.div){ - this.div.remove(); - } - this.scroll=null; - this.div=null; - } + res(Boolean(changed)); + return Boolean(changed); + }catch(e){ + console.error(e); + } + res(false); + return false; + }catch(e){ + throw e; + }finally{ + setTimeout(_=>{ + this.changePromise=undefined; + this.currrunning=false; + if(this.watchtime){ + this.watchForChange(); + } + },300); + } + }); + return await this.changePromise; + } + async focus(id:string,flash=true){ + let element:HTMLElement|undefined; + for(const thing of this.HTMLElements){ + if(thing[1]===id){ + element=thing[0]; + } + } + console.log(id,element,this.HTMLElements.length,":3"); + if(element){ + if(flash){ + element.scrollIntoView({ + behavior: "smooth", + block: "center" + }); + await new Promise(resolve=>setTimeout(resolve, 1000)); + element.classList.remove("jumped"); + await new Promise(resolve=>setTimeout(resolve, 100)); + element.classList.add("jumped"); + }else{ + element.scrollIntoView(); + } + }else{ + for(const thing of this.HTMLElements){ + await this.destroyFromID(thing[1]); + } + this.HTMLElements=[]; + await this.firstElement(id); + this.updatestuff(); + await this.watchForChange(); + await new Promise(resolve=>setTimeout(resolve, 100)); + await this.focus(id,true); + } + } + async delete():Promise{ + for(const thing of this.HTMLElements){ + await this.destroyFromID(thing[1]); + } + this.HTMLElements=[]; + if(this.timeout){ + clearTimeout(this.timeout); + } + if(this.div){ + this.div.remove(); + } + this.scroll=null; + this.div=null; + } } -export {InfiniteScroller}; +export{InfiniteScroller}; diff --git a/webpage/invite.html b/webpage/invite.html index 39f2bc0..314a22e 100644 --- a/webpage/invite.html +++ b/webpage/invite.html @@ -1,22 +1,22 @@ - - - - Jank Client - - - - - - - -
-
-
-

Server Name

-

Someone invited you to Server Name

- -
-
- + + + + Jank Client + + + + + + + +
+
+
+

Server Name

+

Someone invited you to Server Name

+ +
+
+ diff --git a/webpage/invite.ts b/webpage/invite.ts index b8cf730..2f95193 100644 --- a/webpage/invite.ts +++ b/webpage/invite.ts @@ -1,120 +1,118 @@ -import {getBulkUsers, Specialuser, getapiurls} from "./login.js"; -(async()=>{ - const users=getBulkUsers(); - const well=new URLSearchParams(window.location.search).get("instance"); - const joinable:Specialuser[]=[]; - for(const thing in users.users){ - const user:Specialuser = users.users[thing] - if(user.serverurls.wellknown.includes(well)){ - joinable.push(user); - } - console.log(users.users[thing]); - } - let urls:{api:string,cdn:string}; - if(!joinable.length){ - const out=await getapiurls(well); - if(out){ - urls=out; - for(const thing in users.users){ - const user:Specialuser = users.users[thing] - if(user.serverurls.api.includes(out.api)){ - joinable.push(user); - } - console.log(users.users[thing]); - } - }else{ - throw Error("someone needs to handle the case where the servers don't exist") - } - - }else{ - urls=joinable[0].serverurls; - } - if(!joinable.length){ - document.getElementById("AcceptInvite").textContent="Create an account to accept the invite" - } - const code=window.location.pathname.split("/")[2]; - let guildinfo; - fetch(`${urls.api}/invites/${code}`,{ - method:"GET" - }).then(_=>_.json()).then(json=>{ - const guildjson=json.guild; - guildinfo=guildjson; - document.getElementById("invitename").textContent=guildjson.name; - document.getElementById("invitedescription").textContent= +import{getBulkUsers, Specialuser, getapiurls}from"./login.js"; +(async ()=>{ + const users=getBulkUsers(); + const well=new URLSearchParams(window.location.search).get("instance"); + const joinable:Specialuser[]=[]; + for(const thing in users.users){ + const user:Specialuser = users.users[thing]; + if(user.serverurls.wellknown.includes(well)){ + joinable.push(user); + } + console.log(users.users[thing]); + } + let urls:{api:string,cdn:string}; + if(!joinable.length&&well){ + const out=await getapiurls(well); + if(out){ + urls=out; + for(const thing in users.users){ + const user:Specialuser = users.users[thing]; + if(user.serverurls.api.includes(out.api)){ + joinable.push(user); + } + console.log(users.users[thing]); + } + }else{ + throw new Error("someone needs to handle the case where the servers don't exist"); + } + }else{ + urls=joinable[0].serverurls; + } + if(!joinable.length){ + document.getElementById("AcceptInvite").textContent="Create an account to accept the invite"; + } + const code=window.location.pathname.split("/")[2]; + let guildinfo; + fetch(`${urls.api}/invites/${code}`,{ + method: "GET" + }).then(_=>_.json()).then(json=>{ + const guildjson=json.guild; + guildinfo=guildjson; + document.getElementById("invitename").textContent=guildjson.name; + document.getElementById("invitedescription").textContent= `${json.inviter.username} invited you to join ${guildjson.name}`; - if(guildjson.icon){ - const img=document.createElement("img"); - img.src=`${urls.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; - img.classList.add("inviteGuild"); - document.getElementById("inviteimg").append(img); - }else{ - const txt=guildjson.name.replace(/'s /g, " ").replace(/\w+/g, word => word[0]).replace(/\s/g, ""); - const div=document.createElement("div"); - div.textContent=txt; - div.classList.add("inviteGuild"); - document.getElementById("inviteimg").append(div); - } + if(guildjson.icon){ + const img=document.createElement("img"); + img.src=`${urls.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; + img.classList.add("inviteGuild"); + document.getElementById("inviteimg").append(img); + }else{ + const txt=guildjson.name.replace(/'s /g, " ").replace(/\w+/g, word=>word[0]).replace(/\s/g, ""); + const div=document.createElement("div"); + div.textContent=txt; + div.classList.add("inviteGuild"); + document.getElementById("inviteimg").append(div); + } + }); + function showAccounts(){ + const table=document.createElement("dialog"); + for(const thing of Object.values(joinable)){ + const specialuser=thing as Specialuser; + console.log(specialuser.pfpsrc); - }) - function showAccounts(){ - const table=document.createElement("dialog"); - for(const thing of Object.values(joinable)){ - const specialuser=thing as Specialuser; - console.log(specialuser.pfpsrc) + const userinfo=document.createElement("div"); + userinfo.classList.add("flexltr","switchtable"); + const pfp=document.createElement("img"); + userinfo.append(pfp); - const userinfo=document.createElement("div"); - userinfo.classList.add("flexltr","switchtable"); - const pfp=document.createElement("img"); - userinfo.append(pfp); + const user=document.createElement("div"); + userinfo.append(user); + user.append(specialuser.username); + user.append(document.createElement("br")); + const span=document.createElement("span"); + span.textContent=specialuser.serverurls.wellknown.replace("https://","").replace("http://",""); + user.append(span); + user.classList.add("userinfo"); + span.classList.add("serverURL"); - const user=document.createElement("div"); - userinfo.append(user); - user.append(specialuser.username); - user.append(document.createElement("br")); - const span=document.createElement("span"); - span.textContent=specialuser.serverurls.wellknown.replace("https://","").replace("http://",""); - user.append(span); - user.classList.add("userinfo") - span.classList.add("serverURL") - - pfp.src=specialuser.pfpsrc; - pfp.classList.add("pfp"); - table.append(userinfo); - userinfo.addEventListener("click",_=>{ - console.log(thing); - fetch(`${urls.api}/invites/${code}`,{ - method:"POST", - headers:{ - Authorization:thing.token - } - }).then(_=>{ - users["currentuser"]=specialuser.uid; - localStorage.setItem("userinfos",JSON.stringify(users)); - window.location.href="/channels/"+guildinfo.id; - }) - }) - } - { - const td=document.createElement("div"); - td.classList.add("switchtable") - td.append("Login or create an account ⇌"); - td.addEventListener("click",_=>{ - const l=new URLSearchParams("?") - l.set("goback",window.location.href); - l.set("instance",well); - window.location.href="/login?"+l.toString(); - }) - if(!joinable.length){ - const l=new URLSearchParams("?") - l.set("goback",window.location.href); - l.set("instance",well); - window.location.href="/login?"+l.toString(); - } - table.append(td); - } - table.classList.add("accountSwitcher"); - console.log(table); - document.body.append(table); - } - document.getElementById("AcceptInvite").addEventListener("click",showAccounts); + pfp.src=specialuser.pfpsrc; + pfp.classList.add("pfp"); + table.append(userinfo); + userinfo.addEventListener("click",_=>{ + console.log(thing); + fetch(`${urls.api}/invites/${code}`,{ + method: "POST", + headers: { + Authorization: thing.token + } + }).then(_=>{ + users.currentuser=specialuser.uid; + localStorage.setItem("userinfos",JSON.stringify(users)); + window.location.href="/channels/"+guildinfo.id; + }); + }); + } + { + const td=document.createElement("div"); + td.classList.add("switchtable"); + td.append("Login or create an account ⇌"); + td.addEventListener("click",_=>{ + const l=new URLSearchParams("?"); + l.set("goback",window.location.href); + l.set("instance",well); + window.location.href="/login?"+l.toString(); + }); + if(!joinable.length){ + const l=new URLSearchParams("?"); + l.set("goback",window.location.href); + l.set("instance",well); + window.location.href="/login?"+l.toString(); + } + table.append(td); + } + table.classList.add("accountSwitcher"); + console.log(table); + document.body.append(table); + } + document.getElementById("AcceptInvite").addEventListener("click",showAccounts); })(); diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index 17a89bd..8cbd235 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -396,4 +396,4 @@ type memberChunk={ chunk_count: number, not_found: string[] } -export {readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson,emojijson,presencejson,wsjson,messageCreateJson,memberChunk}; +export{readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson,emojijson,presencejson,wsjson,messageCreateJson,memberChunk}; diff --git a/webpage/login.html b/webpage/login.html index 63094ac..d63bfed 100644 --- a/webpage/login.html +++ b/webpage/login.html @@ -1,36 +1,36 @@ - - - - Jank Client - - - - - - - -
-

Login


-
-
-

-

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

Login


+ +
+

+

-
-

+
+

-
-



-

+
+



+

-
+
-
- - -Don't have an account? -
- - +
+ + + Don't have an account? +
+ + diff --git a/webpage/login.ts b/webpage/login.ts index 1d4772e..4ea614c 100644 --- a/webpage/login.ts +++ b/webpage/login.ts @@ -1,366 +1,362 @@ -import { Dialog } from "./dialog.js"; +import{ Dialog }from"./dialog.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); function setTheme(){ - let name=localStorage.getItem("theme"); - if(!name){ - localStorage.setItem("theme","Dark"); - name="Dark"; - } - document.body.className=name+"-theme"; + let name=localStorage.getItem("theme"); + if(!name){ + localStorage.setItem("theme","Dark"); + name="Dark"; + } + document.body.className=name+"-theme"; } setTheme(); function getBulkUsers(){ - const json=getBulkInfo() - for(const thing in json.users){ - json.users[thing]=new Specialuser(json.users[thing]); - } - return json; + const json=getBulkInfo(); + for(const thing in json.users){ + json.users[thing]=new Specialuser(json.users[thing]); + } + return json; } function trimswitcher(){ - const json=getBulkInfo() - const map=new Map(); - for(const thing in json.users){ - const user=json.users[thing]; - let wellknown=user.serverurls.wellknown; - if(wellknown[wellknown.length-1]!=="/"){ - wellknown+="/"; - } - wellknown+=user.username; - if(map.has(wellknown)){ - const otheruser=map.get(wellknown); - if(otheruser[1].serverurls.wellknown[otheruser[1].serverurls.wellknown.length-1]==="/"){ - delete json.users[otheruser[0]]; - map.set(wellknown,[thing,user]); - }else{ - delete json.users[thing]; - } - }else{ - map.set(wellknown,[thing,user]); - } - } - for(const thing in json.users){ - if(thing[thing.length-1]==="/"){ - const user=json.users[thing]; - delete json.users[thing]; - json.users[thing.slice(0, -1)]=user; - } - } - localStorage.setItem("userinfos",JSON.stringify(json)); - console.log(json); + const json=getBulkInfo(); + const map=new Map(); + for(const thing in json.users){ + const user=json.users[thing]; + let wellknown=user.serverurls.wellknown; + if(wellknown.at(-1)!=="/"){ + wellknown+="/"; + } + wellknown+=user.username; + if(map.has(wellknown)){ + const otheruser=map.get(wellknown); + if(otheruser[1].serverurls.wellknown.at(-1)==="/"){ + delete json.users[otheruser[0]]; + map.set(wellknown,[thing,user]); + }else{ + delete json.users[thing]; + } + }else{ + map.set(wellknown,[thing,user]); + } + } + for(const thing in json.users){ + if(thing.at(-1)==="/"){ + const user=json.users[thing]; + delete json.users[thing]; + json.users[thing.slice(0, -1)]=user; + } + } + localStorage.setItem("userinfos",JSON.stringify(json)); + console.log(json); } function getBulkInfo(){ - return JSON.parse(localStorage.getItem("userinfos")); + return JSON.parse(localStorage.getItem("userinfos")); } function setDefaults(){ - let userinfos=getBulkInfo(); - if(!userinfos){ - localStorage.setItem("userinfos",JSON.stringify({ - currentuser:null, - users:{}, - preferences: + let userinfos=getBulkInfo(); + if(!userinfos){ + localStorage.setItem("userinfos",JSON.stringify({ + currentuser: null, + users: {}, + preferences: { - theme:"Dark", - notifications:false, - notisound:"three", + theme: "Dark", + notifications: false, + notisound: "three", }, - })); - userinfos=getBulkInfo(); - } - if(userinfos.users===undefined){ - userinfos.users={}; - } - if(userinfos.accent_color===undefined){ - userinfos.accent_color="#242443"; - } - document.documentElement.style.setProperty('--accent-color', userinfos.accent_color); - if(userinfos.preferences===undefined){ - userinfos.preferences={ - theme:"Dark", - notifications:false, - notisound:"three", - } - } - if(userinfos.preferences&&(userinfos.preferences.notisound===undefined)){ - userinfos.preferences.notisound="three"; - } - localStorage.setItem("userinfos",JSON.stringify(userinfos)) + })); + userinfos=getBulkInfo(); + } + if(userinfos.users===undefined){ + userinfos.users={}; + } + if(userinfos.accent_color===undefined){ + userinfos.accent_color="#242443"; + } + document.documentElement.style.setProperty("--accent-color", userinfos.accent_color); + if(userinfos.preferences===undefined){ + userinfos.preferences={ + theme: "Dark", + notifications: false, + notisound: "three", + }; + } + if(userinfos.preferences&&(userinfos.preferences.notisound===undefined)){ + userinfos.preferences.notisound="three"; + } + localStorage.setItem("userinfos",JSON.stringify(userinfos)); } setDefaults(); class Specialuser{ - serverurls:{api:string,cdn:string,gateway:string,wellknown:string,login:string}; - email:string; - token:string; - loggedin; - json; - constructor(json){ - if(json instanceof Specialuser){ - console.error("specialuser can't construct from another specialuser"); - } - this.serverurls=json.serverurls; - let apistring=new URL(json.serverurls.api).toString(); - apistring=apistring.replace(/\/(v\d+\/?)?$/, "")+"/v9"; - this.serverurls.api=apistring; - this.serverurls.cdn=new URL(json.serverurls.cdn).toString().replace(/\/$/,""); - this.serverurls.gateway=new URL(json.serverurls.gateway).toString().replace(/\/$/,"");; - this.serverurls.wellknown=new URL(json.serverurls.wellknown).toString().replace(/\/$/,"");; - this.serverurls.login=new URL(json.serverurls.login).toString().replace(/\/$/,"");; - this.email=json.email; - this.token=json.token; - this.loggedin=json.loggedin; - this.json=json; - if(!this.serverurls||!this.email||!this.token){ - console.error("There are fundamentally missing pieces of info missing from this user"); - } - } - set pfpsrc(e){ - console.log("this ran fr") - 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; - } - 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)); - } + serverurls:{api:string,cdn:string,gateway:string,wellknown:string,login:string}; + email:string; + token:string; + loggedin; + json; + constructor(json){ + if(json instanceof Specialuser){ + console.error("specialuser can't construct from another specialuser"); + } + this.serverurls=json.serverurls; + let apistring=new URL(json.serverurls.api).toString(); + apistring=apistring.replace(/\/(v\d+\/?)?$/, "")+"/v9"; + this.serverurls.api=apistring; + this.serverurls.cdn=new URL(json.serverurls.cdn).toString().replace(/\/$/,""); + this.serverurls.gateway=new URL(json.serverurls.gateway).toString().replace(/\/$/,""); + this.serverurls.wellknown=new URL(json.serverurls.wellknown).toString().replace(/\/$/,""); + this.serverurls.login=new URL(json.serverurls.login).toString().replace(/\/$/,""); + this.email=json.email; + this.token=json.token; + this.loggedin=json.loggedin; + this.json=json; + if(!this.serverurls||!this.email||!this.token){ + console.error("There are fundamentally missing pieces of info missing from this user"); + } + } + set pfpsrc(e){ + console.log("this ran fr"); + 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; + } + 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){ - user=new Specialuser(user); - const info=getBulkInfo(); - info.users[user.uid]=user; - info.currentuser=user.uid; - localStorage.setItem("userinfos",JSON.stringify(info)); - return user; + 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; let instanceinfo; const stringURLMap=new Map(); const stringURLsMap=new Map(); async function getapiurls(str:string):Promise<{api:string,cdn:string,gateway:string,wellknown:string,login:string}|false>{ - if(!URL.canParse(str)){ - const val=stringURLMap.get(str); - if(val){ - str=val; - }else{ - const val=stringURLsMap.get(str) - if(val){ - const responce=await fetch(val.api+val.api.endsWith("/")?"":"/"+"ping"); - if(responce.ok){ - if(val.login){ - return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; - }else{ - val.login=val.api; - return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; - } - - } - } - } - } - if(str[str.length-1]!=="/"){ - str+="/" - } - let api:string; - try{ - const info=await fetch(`${str}/.well-known/spacebar`).then((x) => x.json()); - api=info.api; - }catch{ - return false - } - const url = new URL(api); - try{ - - const info=await fetch(`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`).then((x) => x.json()); - return { - api: info.apiEndpoint, - gateway: info.gateway, - cdn: info.cdn, - wellknown: str, - login:url.toString() - }; - }catch{ - const val=stringURLsMap.get(str) - if(val){ - const responce=await fetch(val.api+val.api.endsWith("/")?"":"/"+"ping"); - if(responce.ok){ - if(val.login){ - return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; - }else{ - val.login=val.api; - return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; - } - - } - } - return false; - } - + if(!URL.canParse(str)){ + const val=stringURLMap.get(str); + if(val){ + str=val; + }else{ + const val=stringURLsMap.get(str); + if(val){ + const responce=await fetch(val.api+val.api.endsWith("/")?"":"/"+"ping"); + if(responce.ok){ + if(val.login){ + return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; + }else{ + val.login=val.api; + return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; + } + } + } + } + } + if(str.at(-1)!=="/"){ + str+="/"; + } + let api:string; + try{ + const info=await fetch(`${str}/.well-known/spacebar`).then(x=>x.json()); + api=info.api; + }catch{ + return false; + } + const url = new URL(api); + try{ + const info=await fetch(`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`).then(x=>x.json()); + return{ + api: info.apiEndpoint, + gateway: info.gateway, + cdn: info.cdn, + wellknown: str, + login: url.toString() + }; + }catch{ + const val=stringURLsMap.get(str); + if(val){ + const responce=await fetch(val.api+val.api.endsWith("/")?"":"/"+"ping"); + if(responce.ok){ + if(val.login){ + return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; + }else{ + val.login=val.api; + return val as {wellknown:string,api:string,cdn:string,gateway:string,login:string}; + } + } + } + return false; + } } async function checkInstance(e:string){ - const verify=document.getElementById("verify"); - try{ - verify.textContent="Checking Instance"; - const instanceinfo=await getapiurls((instancein as HTMLInputElement).value) as {wellknown:string,api:string,cdn:string,gateway:string,login:string, value:string}; - if(instanceinfo){ - instanceinfo.value=(instancein as HTMLInputElement).value; - localStorage.setItem("instanceinfo",JSON.stringify(instanceinfo)); - verify.textContent="Instance is all good" - if(checkInstance["alt"]){checkInstance["alt"]();} - setTimeout(_=>{ - console.log(verify.textContent) - verify.textContent=""; - },3000); - }else{ - verify.textContent="Invalid Instance, try again" - } - }catch(e){ - console.log("catch") - verify.textContent="Invalid Instance, try again" - } + const verify=document.getElementById("verify"); + try{ + verify.textContent="Checking Instance"; + const instanceinfo=await getapiurls((instancein as HTMLInputElement).value) as {wellknown:string,api:string,cdn:string,gateway:string,login:string, value:string}; + if(instanceinfo){ + instanceinfo.value=(instancein as HTMLInputElement).value; + localStorage.setItem("instanceinfo",JSON.stringify(instanceinfo)); + verify.textContent="Instance is all good"; + if(checkInstance.alt){ + checkInstance.alt(); + } + setTimeout(_=>{ + console.log(verify.textContent); + verify.textContent=""; + },3000); + }else{ + verify.textContent="Invalid Instance, try again"; + } + }catch{ + console.log("catch"); + verify.textContent="Invalid Instance, try again"; + } } if(instancein){ - console.log(instancein) - instancein.addEventListener("keydown",e=>{ - const verify=document.getElementById("verify"); - verify.textContent="Waiting to check Instance" - clearTimeout(timeout); - timeout=setTimeout(checkInstance,1000); - }); - if(localStorage.getItem("instanceinfo")){ - const json=JSON.parse(localStorage.getItem("instanceinfo")); - if(json.value){ - (instancein as HTMLInputElement).value=json.value - }else{ - (instancein as HTMLInputElement).value=json.wellknown - } - }else{ - checkInstance("https://spacebar.chat/"); - } - + console.log(instancein); + instancein.addEventListener("keydown",e=>{ + const verify=document.getElementById("verify"); + verify.textContent="Waiting to check Instance"; + clearTimeout(timeout); + timeout=setTimeout(checkInstance,1000); + }); + if(localStorage.getItem("instanceinfo")){ + const json=JSON.parse(localStorage.getItem("instanceinfo")); + if(json.value){ + (instancein as HTMLInputElement).value=json.value; + }else{ + (instancein as HTMLInputElement).value=json.wellknown; + } + }else{ + checkInstance("https://spacebar.chat/"); + } } async function login(username:string, password:string, captcha:string){ - if(captcha===""){ - captcha=undefined; - } - const options={ - method: "POST", - body:JSON.stringify({ - "login": username, - "password": password, - "undelete":false, - "captcha_key":captcha - }), - headers: { - "Content-type": "application/json; charset=UTF-8", - }} - try{ - const info=JSON.parse(localStorage.getItem("instanceinfo")); - const api=info.login+(info.login.startsWith("/")?"/":""); - return await fetch(api+'/auth/login',options).then(response=>response.json()) - .then((response) => { - console.log(response,response.message) - if("Invalid Form Body"===response.message){ - return response.errors.login._errors[0].message; - console.log("test") - } - //this.serverurls||!this.email||!this.token - console.log(response); + if(captcha===""){ + captcha=undefined; + } + const options={ + method: "POST", + body: JSON.stringify({ + login: username, + password, + undelete: false, + captcha_key: captcha + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }}; + try{ + const info=JSON.parse(localStorage.getItem("instanceinfo")); + const api=info.login+(info.login.startsWith("/")?"/":""); + return await fetch(api+"/auth/login",options).then(response=>response.json()) + .then(response=>{ + console.log(response,response.message); + if(response.message==="Invalid Form Body"){ + return response.errors.login._errors[0].message; + console.log("test"); + } + //this.serverurls||!this.email||!this.token + console.log(response); - if(response.captcha_sitekey){ + if(response.captcha_sitekey){ + const capt=document.getElementById("h-captcha"); + if(!capt.children.length){ + const capty=document.createElement("div"); + capty.classList.add("h-captcha"); - const capt=document.getElementById("h-captcha"); - if(!capt.children.length){ - const capty=document.createElement("div"); - capty.classList.add("h-captcha"); - - capty.setAttribute("data-sitekey", response.captcha_sitekey); - const script=document.createElement("script"); - script.src="https://js.hcaptcha.com/1/api.js"; - capt.append(script); - capt.append(capty); - }else{ - eval("hcaptcha.reset()"); - } - return; - }else{ - console.log(response); - if(response.ticket){ - let onetimecode=""; - new Dialog(["vdiv",["title","2FA code:"],["textbox","","",function(this:HTMLInputElement){onetimecode=this.value}],["button","","Submit",function(){ - fetch(api+"/auth/mfa/totp",{ - method:"POST", - headers:{ - "Content-Type": "application/json" - }, - body:JSON.stringify({ - code:onetimecode, - ticket:response.ticket, - }) - }).then(r=>r.json()).then(response=>{ - if(response.message){ - alert(response.message) - }else{ - console.warn(response); - if(!response.token) return; - adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token}).username=username; - const redir=new URLSearchParams(window.location.search).get("goback"); - if(redir){ - window.location.href = redir; - }else{ - window.location.href = '/channels/@me'; - } - } - }) - }]]).show(); - }else{ - console.warn(response); - if(!response.token) return; - adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token}).username=username; - const redir=new URLSearchParams(window.location.search).get("goback"); - if(redir){ - window.location.href = redir; - }else{ - window.location.href = '/channels/@me'; - } - return ""; - } - } - }) - }catch(error){ - console.error('Error:', error); - }; + capty.setAttribute("data-sitekey", response.captcha_sitekey); + const script=document.createElement("script"); + script.src="https://js.hcaptcha.com/1/api.js"; + capt.append(script); + capt.append(capty); + }else{ + eval("hcaptcha.reset()"); + } + }else{ + console.log(response); + if(response.ticket){ + let onetimecode=""; + new Dialog(["vdiv",["title","2FA code:"],["textbox","","",function(this:HTMLInputElement){ + onetimecode=this.value; + }],["button","","Submit",function(){ + fetch(api+"/auth/mfa/totp",{ + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + code: onetimecode, + ticket: response.ticket, + }) + }).then(r=>r.json()).then(response=>{ + if(response.message){ + alert(response.message); + }else{ + console.warn(response); + if(!response.token)return; + adduser({serverurls: JSON.parse(localStorage.getItem("instanceinfo")),email: username,token: response.token}).username=username; + const redir=new URLSearchParams(window.location.search).get("goback"); + if(redir){ + window.location.href = redir; + }else{ + window.location.href = "/channels/@me"; + } + } + }); + }]]).show(); + }else{ + console.warn(response); + if(!response.token)return; + adduser({serverurls: JSON.parse(localStorage.getItem("instanceinfo")),email: username,token: response.token}).username=username; + const redir=new URLSearchParams(window.location.search).get("goback"); + if(redir){ + window.location.href = redir; + }else{ + window.location.href = "/channels/@me"; + } + return""; + } + } + }); + }catch(error){ + console.error("Error:", error); + } } async function check(e){ - - e.preventDefault(); - let h=await login(e.srcElement[1].value,e.srcElement[2].value,e.srcElement[3].value); - document.getElementById("wrong").textContent=h; - console.log(h); + e.preventDefault(); + const h=await login(e.srcElement[1].value,e.srcElement[2].value,e.srcElement[3].value); + document.getElementById("wrong").textContent=h; + console.log(h); } if(document.getElementById("form")){ -document.getElementById("form").addEventListener("submit", check); + document.getElementById("form").addEventListener("submit", check); } //this currently does not work, and need to be implemented better at some time. /* @@ -390,50 +386,50 @@ if ("serviceWorker" in navigator){ */ const switchurl=document.getElementById("switch") as HTMLAreaElement; if(switchurl){ - switchurl.href+=window.location.search; - const instance=new URLSearchParams(window.location.search).get("instance"); - console.log(instance); - if(instance){ - instancein.value=instance; - checkInstance(""); - } + switchurl.href+=window.location.search; + const instance=new URLSearchParams(window.location.search).get("instance"); + console.log(instance); + if(instance){ + instancein.value=instance; + checkInstance(""); + } } -export {checkInstance}; +export{checkInstance}; trimswitcher(); -export {mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser,getapiurls,adduser} +export{mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser,getapiurls,adduser}; const datalist=document.getElementById("instances"); console.warn(datalist); if(datalist){ - fetch("/instances.json").then(_=>_.json()).then((json:{name:string,online:boolean,description?:string,src?:string,url?:string,display?:boolean,urls:{wellknown:string,api:string,cdn:string,gateway:string,login?:string}}[])=>{ - console.warn(json); - if(instancein&&instancein.value===""){ - instancein.value=json[0].name; - } - for(const instance of json){ - if(instance.display===false){ - continue; - } - const option=document.createElement("option"); - option.disabled=!instance.online; - option.value=instance.name; - if(instance.url){ - stringURLMap.set(option.value,instance.url); - if(instance.urls){ - stringURLsMap.set(instance.url,instance.urls); - } - }else if(instance.urls){ - stringURLsMap.set(option.value,instance.urls); - }else{ - option.disabled=true; - } - if(instance.description){ - option.label=instance.description; - }else{ - option.label=instance.name; - } - datalist.append(option); - } - checkInstance(""); - }) + fetch("/instances.json").then(_=>_.json()).then((json:{name:string,online:boolean,description?:string,src?:string,url?:string,display?:boolean,urls:{wellknown:string,api:string,cdn:string,gateway:string,login?:string}}[])=>{ + console.warn(json); + if(instancein&&instancein.value===""){ + instancein.value=json[0].name; + } + for(const instance of json){ + if(instance.display===false){ + continue; + } + const option=document.createElement("option"); + option.disabled=!instance.online; + option.value=instance.name; + if(instance.url){ + stringURLMap.set(option.value,instance.url); + if(instance.urls){ + stringURLsMap.set(instance.url,instance.urls); + } + }else if(instance.urls){ + stringURLsMap.set(option.value,instance.urls); + }else{ + option.disabled=true; + } + if(instance.description){ + option.label=instance.description; + }else{ + option.label=instance.name; + } + datalist.append(option); + } + checkInstance(""); + }); } diff --git a/webpage/markdown.ts b/webpage/markdown.ts index 211435d..ac2e387 100644 --- a/webpage/markdown.ts +++ b/webpage/markdown.ts @@ -1,573 +1,598 @@ -import { Channel } from "./channel.js"; -import { Emoji } from "./emoji.js"; -import { Localuser } from "./localuser.js"; +import{ Channel }from"./channel.js"; +import{ Emoji }from"./emoji.js"; +import{ Localuser }from"./localuser.js"; class MarkDown{ - txt : string[]; - keep:boolean; - stdsize:boolean; - owner:Localuser|Channel; - info:Localuser["info"]; - constructor(text : string|string[],owner:MarkDown["owner"],{keep=false,stdsize=false} = {}){ - if((typeof text)===(typeof "")){ - this.txt=(text as string).split(""); - }else{ - this.txt=(text as string[]); - } - if(this.txt===undefined){ - this.txt=[]; - } - this.info=owner.info; - this.keep=keep; - this.owner=owner; - this.stdsize=stdsize; - } - get rawString(){ - return this.txt.join(""); - } - get textContent(){ - return this.makeHTML().textContent; - } - makeHTML({keep=this.keep,stdsize=this.stdsize}={}){ - return this.markdown(this.txt,{keep:keep,stdsize:stdsize}); - } - markdown(text : string|string[],{keep=false,stdsize=false} = {}){ - let txt : string[]; - if((typeof text)===(typeof "")){ - txt=(text as string).split(""); - }else{ - txt=(text as string[]); - } - if(txt===undefined){ - txt=[]; - } - const span=document.createElement("span"); - let current=document.createElement("span"); - function appendcurrent(){ - if(current.innerHTML!==""){ - span.append(current); - current=document.createElement("span"); - } + txt : string[]; + keep:boolean; + stdsize:boolean; + owner:Localuser|Channel; + info:Localuser["info"]; + constructor(text : string|string[],owner:MarkDown["owner"],{keep=false,stdsize=false} = {}){ + if((typeof text)===(typeof "")){ + this.txt=(text as string).split(""); + }else{ + this.txt=(text as string[]); + } + if(this.txt===undefined){ + this.txt=[]; + } + this.info=owner.info; + this.keep=keep; + this.owner=owner; + this.stdsize=stdsize; + } + get rawString(){ + return this.txt.join(""); + } + get textContent(){ + return this.makeHTML().textContent; + } + makeHTML({keep=this.keep,stdsize=this.stdsize}={}){ + return this.markdown(this.txt,{keep,stdsize}); + } + markdown(text : string|string[],{keep=false,stdsize=false} = {}){ + let txt : string[]; + if((typeof text)===(typeof "")){ + txt=(text as string).split(""); + }else{ + txt=(text as string[]); + } + if(txt===undefined){ + txt=[]; + } + const span=document.createElement("span"); + let current=document.createElement("span"); + function appendcurrent(){ + if(current.innerHTML!==""){ + span.append(current); + current=document.createElement("span"); + } + } + for(let i=0;i"&&txt[i+2]===" "){ + element=document.createElement("div"); + const line=document.createElement("div"); + line.classList.add("quoteline"); + element.append(line); + element.classList.add("quote"); + keepys="> "; + i+=3; + } + if(keepys){ + appendcurrent(); + if(!first&&!stdsize){ + span.appendChild(document.createElement("br")); + } + const build:string[]=[]; + for(;txt[i]!=="\n"&&txt[i]!==undefined;i++){ + build.push(txt[i]); + } + try{ + if(stdsize){ + element=document.createElement("span"); + }else continue; + if(keep){ + element.append(keepys); + } + element.appendChild(this.markdown(build,{keep,stdsize})); + span.append(element); + }finally{ + i-=1; + console.log(txt[i]); + continue; + } + } + if(first){ + i++; + } + } + if(txt[i]==="\n"){ + if(!stdsize){ + appendcurrent(); + span.append(document.createElement("br")); + } + continue; + } + if(txt[i]==="`"){ + let count=1; + if(txt[i+1]==="`"){ + count++; + if(txt[i+2]==="`"){ + count++; + } + } + let build=""; + if(keep){ + build+="`".repeat(count); + } + let find=0; + let j=i+count; + let init=true; + for(;txt[j]!==undefined&&(txt[j]!=="\n"||count===3)&&find!==count;j++){ + if(txt[j]==="`"){ + find++; + }else{ + if(find!==0){ + build+="`".repeat(find); + find=0; + } + if(init&&count===3){ + if(txt[j]===" "||txt[j]==="\n"){ + init=false; + } + if(keep){ + build+=txt[j]; + } + continue; + } + build+=txt[j]; + } + } + if(stdsize){ + console.log(build); + build=build.replaceAll("\n",""); + console.log(build,JSON.stringify(build)); + } + if(find===count){ + appendcurrent(); + i=j; + if(keep){ + build+="`".repeat(find); + } + if(count!==3&&!stdsize){ + const samp=document.createElement("samp"); + samp.textContent=build; + span.appendChild(samp); + }else{ + const pre=document.createElement("pre"); + if(build.at(-1)==="\n"){ + build=build.substring(0,build.length-1); + } + if(txt[i]==="\n"){ + i++; + } + pre.textContent=build; + span.appendChild(pre); + } + i--; + continue; + } + } - if(txt[i+1]==="#"){ - console.log("test"); - if(txt[i+2]==="#"){ - if(txt[i+3]==="#"&&txt[i+4]===" "){ - element=document.createElement("h3"); - keepys="### "; - i+=5; - }else if(txt[i+3]===" "){ - element=document.createElement("h2"); - element.classList.add("h2md"); - keepys="## "; - i+=4; - } - }else if(txt[i+2]===" "){ - element=document.createElement("h1"); - keepys="# "; - i+=3; - } - }else if(txt[i+1]===">"&&txt[i+2]===" "){ - element=document.createElement("div"); - const line=document.createElement("div"); - line.classList.add("quoteline"); - element.append(line); - element.classList.add("quote"); - keepys="> "; - i+=3; - } - if(keepys){ - appendcurrent(); - if(!first&&!stdsize){ - span.appendChild(document.createElement("br")); - } - const build:string[]=[]; - for(;txt[i]!=="\n"&&txt[i]!==undefined;i++){ - build.push(txt[i]); - } - try{ - if(stdsize){ - element=document.createElement("span"); - }else continue; - if(keep){ - element.append(keepys); - } - element.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - span.append(element); - }finally{ - i-=1; - console.log(txt[i]); - continue; - } - } - if(first){ - i++; - } - } - if(txt[i]==="\n"){ + if(txt[i]==="*"){ + let count=1; + if(txt[i+1]==="*"){ + count++; + if(txt[i+2]==="*"){ + count++; + } + } + let build:string[]=[]; + let find=0; + let j=i+count; + for(;txt[j]!==undefined&&find!==count;j++){ + if(txt[j]==="*"){ + find++; + }else{ + build.push(txt[j]); + if(find!==0){ + build=build.concat(new Array(find).fill("*")); + find=0; + } + } + } + if(find===count&&(count!=1||txt[i+1]!==" ")){ + appendcurrent(); + i=j; - if(!stdsize){ - appendcurrent(); - span.append(document.createElement("br")); - } - continue; - } - if(txt[i]==="`"){ - let count=1; - if(txt[i+1]==="`"){ - count++; - if(txt[i+2]==="`"){ - count++; - } - } - let build=""; - if(keep){ - build+="`".repeat(count); - } - let find=0; - let j=i+count; - let init=true; - for(;txt[j]!==undefined&&(txt[j]!=="\n"||count===3)&&find!==count;j++){ - if(txt[j]==="`"){ - find++; - }else{ - if(find!==0){ - build+="`".repeat(find); - find=0; - } - if(init&&count===3){ - if(txt[j]===" "||txt[j]==="\n"){ - init=false; - } - if(keep){ - build+=txt[j]; - } - continue; - } - build+=txt[j]; - } - } - if(stdsize){ - console.log(build); - build=build.replaceAll("\n",""); - console.log(build,JSON.stringify(build)); - } - if(find===count){ - appendcurrent(); - i=j; - if(keep){ - build+="`".repeat(find); - } - if(count!==3&&!stdsize){ - const samp=document.createElement("samp"); - samp.textContent=build; - span.appendChild(samp); - }else{ - const pre=document.createElement("pre"); - if(build[build.length-1]==="\n"){ - build=build.substring(0,build.length-1); - } - if(txt[i]==="\n"){ - i++ - } - pre.textContent=build; - span.appendChild(pre); - } - i--; - continue; - } - } + const stars="*".repeat(count); + if(count===1){ + const i=document.createElement("i"); + if(keep){ + i.append(stars); + } + i.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + i.append(stars); + } + span.appendChild(i); + }else if(count===2){ + const b=document.createElement("b"); + if(keep){ + b.append(stars); + } + b.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + b.append(stars); + } + span.appendChild(b); + }else{ + const b=document.createElement("b"); + const i=document.createElement("i"); + if(keep){ + b.append(stars); + } + b.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + b.append(stars); + } + i.appendChild(b); + span.appendChild(i); + } + i--; + continue; + } + } - if(txt[i]==="*"){ - let count=1; - if(txt[i+1]==="*"){ - count++; - if(txt[i+2]==="*"){ - count++; - } - } - let build:string[]=[]; - let find=0; - let j=i+count; - for(;txt[j]!==undefined&&find!==count;j++){ + if(txt[i]==="_"){ + let count=1; + if(txt[i+1]==="_"){ + count++; + if(txt[i+2]==="_"){ + count++; + } + } + let build:string[]=[]; + let find=0; + let j=i+count; + for(;txt[j]!==undefined&&find!==count;j++){ + if(txt[j]==="_"){ + find++; + }else{ + build.push(txt[j]); + if(find!==0){ + build=build.concat(new Array(find).fill("_")); + find=0; + } + } + } + if(find===count&&(count!=1||(txt[j+1]===" "||txt[j+1]==="\n"||txt[j+1]===undefined))){ + appendcurrent(); + i=j; + const underscores="_".repeat(count); + if(count===1){ + const i=document.createElement("i"); + if(keep){ + i.append(underscores); + } + i.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + i.append(underscores); + } + span.appendChild(i); + }else if(count===2){ + const u=document.createElement("u"); + if(keep){ + u.append(underscores); + } + u.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + u.append(underscores); + } + span.appendChild(u); + }else{ + const u=document.createElement("u"); + const i=document.createElement("i"); + if(keep){ + i.append(underscores); + } + i.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + i.append(underscores); + } + u.appendChild(i); + span.appendChild(u); + } + i--; + continue; + } + } - if(txt[j]==="*"){ - find++; - }else{ - build.push(txt[j]); - if(find!==0){ - build=build.concat(new Array(find).fill("*")); - find=0; - } - } - } - if(find===count&&(count!=1||txt[i+1]!==" ")){ - appendcurrent(); - i=j; + if(txt[i]==="~"&&txt[i+1]==="~"){ + const count=2; + let build:string[]=[]; + let find=0; + let j=i+2; + for(;txt[j]!==undefined&&find!==count;j++){ + if(txt[j]==="~"){ + find++; + }else{ + build.push(txt[j]); + if(find!==0){ + build=build.concat(new Array(find).fill("~")); + find=0; + } + } + } + if(find===count){ + appendcurrent(); + i=j-1; + const tildes="~~"; + if(count===2){ + const s=document.createElement("s"); + if(keep){ + s.append(tildes); + } + s.appendChild(this.markdown(build,{keep,stdsize})); + if(keep){ + s.append(tildes); + } + span.appendChild(s); + } + continue; + } + } + if(txt[i]==="|"&&txt[i+1]==="|"){ + const count=2; + let build:string[]=[]; + let find=0; + let j=i+2; + for(;txt[j]!==undefined&&find!==count;j++){ + if(txt[j]==="|"){ + find++; + }else{ + build.push(txt[j]); + if(find!==0){ + build=build.concat(new Array(find).fill("~")); + find=0; + } + } + } + if(find===count){ + appendcurrent(); + i=j-1; + const pipes="||"; + if(count===2){ + const j=document.createElement("j"); + if(keep){ + j.append(pipes); + } + j.appendChild(this.markdown(build,{keep,stdsize})); + j.classList.add("spoiler"); + j.onclick=MarkDown.unspoil; + if(keep){ + j.append(pipes); + } + span.appendChild(j); + } + continue; + } + } - const stars="*".repeat(count); - if(count===1){ - const i=document.createElement("i"); - if(keep){i.append(stars)} - i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){i.append(stars)} - span.appendChild(i); - }else if(count===2){ - const b=document.createElement("b"); - if(keep){b.append(stars)} - b.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){b.append(stars)} - span.appendChild(b); - }else{ - const b=document.createElement("b"); - const i=document.createElement("i"); - if(keep){b.append(stars)} - b.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){b.append(stars)} - i.appendChild(b); - span.appendChild(i); - } - i-- - continue; - } - } + if(txt[i]==="<" && txt[i + 1]==="t" && txt[i + 2]===":"){ + let found=false; + const build=["<","t",":"]; + let j = i+3; + for(; txt[j] !== void 0; j++){ + build.push(txt[j]); - if(txt[i]==="_"){ - let count=1; - if(txt[i+1]==="_"){ - count++; - if(txt[i+2]==="_"){ - count++; - } - } - let build:string[]=[]; - let find=0; - let j=i+count; - for(;txt[j]!==undefined&&find!==count;j++){ + if(txt[j]===">"){ + found=true; + break; + } + } - if(txt[j]==="_"){ - find++; - }else{ - build.push(txt[j]); - if(find!==0){ - build=build.concat(new Array(find).fill("_")); - find=0; - } - } - } - if(find===count&&(count!=1||(txt[j+1]===" "||txt[j+1]==="\n"||txt[j+1]===undefined))){ - appendcurrent(); - i=j; - const underscores="_".repeat(count); - if(count===1){ - const i=document.createElement("i"); - if(keep){i.append(underscores)} - i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){i.append(underscores)} - span.appendChild(i); - }else if(count===2){ - const u=document.createElement("u"); - if(keep){u.append(underscores)} - u.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){u.append(underscores)} - span.appendChild(u); - }else{ - const u=document.createElement("u"); - const i=document.createElement("i"); - if(keep){i.append(underscores)} - i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){i.append(underscores)} - u.appendChild(i) - span.appendChild(u); - } - i--; - continue; - } - } - - if(txt[i]==="~"&&txt[i+1]==="~"){ - let count=2; - let build:string[]=[]; - let find=0; - let j=i+2; - for(;txt[j]!==undefined&&find!==count;j++){ - if(txt[j]==="~"){ - find++; - }else{ - build.push(txt[j]); - if(find!==0){ - build=build.concat(new Array(find).fill("~")); - find=0; - } - } - } - if(find===count){ - appendcurrent(); - i=j-1; - const tildes="~~"; - if(count===2){ - const s=document.createElement("s"); - if(keep){s.append(tildes)} - s.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - if(keep){s.append(tildes)} - span.appendChild(s); - } - continue; - } - } - if(txt[i]==="|"&&txt[i+1]==="|"){ - let count=2; - let build:string[]=[]; - let find=0; - let j=i+2; - for(;txt[j]!==undefined&&find!==count;j++){ - if(txt[j]==="|"){ - find++; - }else{ - build.push(txt[j]); - if(find!==0){ - build=build.concat(new Array(find).fill("~")); - find=0; - } - } - } - if(find===count){ - appendcurrent(); - i=j-1; - const pipes="||"; - if(count===2){ - const j=document.createElement("j"); - if(keep){j.append(pipes)} - j.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); - j.classList.add("spoiler"); - j.onclick=MarkDown.unspoil; - if(keep){j.append(pipes)} - span.appendChild(j); - } - continue; - } - } - - if (txt[i]==="<" && txt[i + 1]==="t" && txt[i + 2]===":") { - let found=false; - const build=["<","t",":"]; - let j = i+3; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); - - if (txt[j]===">") { - found=true; - break; - } - } - - if (found) { - appendcurrent(); - i=j; - const parts=build.join("").match(/^$/) as RegExpMatchArray; - const dateInput=new Date(Number.parseInt(parts[1]) * 1000); - let time=""; - if (Number.isNaN(dateInput.getTime())) time=build.join(""); - else { - if (parts[3]==="d") time=dateInput.toLocaleString(void 0, {day: "2-digit", month: "2-digit", year: "numeric"}); - else if (parts[3]==="D") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric"}); - else if (!parts[3] || parts[3]==="f") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric"}) + " " + + if(found){ + appendcurrent(); + i=j; + const parts=build.join("").match(/^$/) as RegExpMatchArray; + const dateInput=new Date(Number.parseInt(parts[1]) * 1000); + let time=""; + if(Number.isNaN(dateInput.getTime())) time=build.join(""); + else{ + if(parts[3]==="d") time=dateInput.toLocaleString(void 0, {day: "2-digit", month: "2-digit", year: "numeric"}); + else if(parts[3]==="D") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric"}); + else if(!parts[3] || parts[3]==="f") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric"}) + " " + dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); - else if (parts[3]==="F") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric", weekday: "long"}) + " " + + else if(parts[3]==="F") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric", weekday: "long"}) + " " + dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); - else if (parts[3]==="t") time=dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); - else if (parts[3]==="T") time=dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit", second: "2-digit"}); - else if (parts[3]==="R") time=Math.round((Date.now() - (Number.parseInt(parts[1]) * 1000))/1000/60) + " minutes ago"; - } + else if(parts[3]==="t") time=dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); + else if(parts[3]==="T") time=dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit", second: "2-digit"}); + else if(parts[3]==="R") time=Math.round((Date.now() - (Number.parseInt(parts[1]) * 1000))/1000/60) + " minutes ago"; + } - const timeElem=document.createElement("span"); - timeElem.classList.add("markdown-timestamp"); - timeElem.textContent=time; - span.appendChild(timeElem); - continue; - } - } + const timeElem=document.createElement("span"); + timeElem.classList.add("markdown-timestamp"); + timeElem.textContent=time; + span.appendChild(timeElem); + continue; + } + } - if (txt[i] === "<" && (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":"))) { - let found=false; - const build = txt[i + 1] === "a" ? ["<","a",":"] : ["<",":"]; - let j = i+build.length; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); + if(txt[i] === "<" && (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":"))){ + let found=false; + const build = txt[i + 1] === "a" ? ["<","a",":"] : ["<",":"]; + let j = i+build.length; + for(; txt[j] !== void 0; j++){ + build.push(txt[j]); - if (txt[j]===">") { - found=true; - break; - } - } + if(txt[j]===">"){ + found=true; + break; + } + } - if (found) { - const buildjoin=build.join(""); - const parts=buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); - if (parts && parts[2]) { - appendcurrent(); - i=j; - const isEmojiOnly = txt.join("").trim()===buildjoin.trim(); - const owner=(this.owner instanceof Channel)?this.owner.guild:this.owner - const emoji=new Emoji({name:buildjoin,id:parts[2],animated:!!parts[1]},owner); - span.appendChild(emoji.getHTML(isEmojiOnly)); + if(found){ + const buildjoin=build.join(""); + const parts=buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); + if(parts && parts[2]){ + appendcurrent(); + i=j; + const isEmojiOnly = txt.join("").trim()===buildjoin.trim(); + const owner=(this.owner instanceof Channel)?this.owner.guild:this.owner; + const emoji=new Emoji({name: buildjoin,id: parts[2],animated: Boolean(parts[1])},owner); + span.appendChild(emoji.getHTML(isEmojiOnly)); - continue; - } - } - } + continue; + } + } + } - if (txt[i] == "[" && !keep) { - let partsFound=0; - let j=i+1; - const build=["["]; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); + if(txt[i] == "[" && !keep){ + let partsFound=0; + let j=i+1; + const build=["["]; + for(; txt[j] !== void 0; j++){ + build.push(txt[j]); - if (partsFound === 0 && txt[j] === "]") { - if (txt[j + 1] === "(" && + if(partsFound === 0 && txt[j] === "]"){ + if(txt[j + 1] === "(" && txt[j + 2] === "h" && txt[j + 3] === "t" && txt[j + 4] === "t" && txt[j + 5] === "p" && (txt[j + 6] === "s" || txt[j + 6] === ":") - ) { - partsFound++; - } else { - break; - }; - } else if (partsFound === 1 && txt[j] === ")") { - partsFound++; - break; - } - } + ){ + partsFound++; + }else{ + break; + } + }else if(partsFound === 1 && txt[j] === ")"){ + partsFound++; + break; + } + } - if (partsFound === 2) { - appendcurrent(); - i=j; + if(partsFound === 2){ + appendcurrent(); + i=j; - const parts=build.join("").match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/); - if (parts) { - const linkElem=document.createElement("a"); - linkElem.href=parts[2]; - linkElem.textContent=parts[1]; - linkElem.target="_blank"; - linkElem.rel="noopener noreferrer"; - linkElem.title=(parts[3] ? parts[3].substring(2, parts[3].length - 1)+"\n\n" : "") + parts[2]; - span.appendChild(linkElem); + const parts=build.join("").match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/); + if(parts){ + const linkElem=document.createElement("a"); + linkElem.href=parts[2]; + linkElem.textContent=parts[1]; + linkElem.target="_blank"; + linkElem.rel="noopener noreferrer"; + linkElem.title=(parts[3] ? parts[3].substring(2, parts[3].length - 1)+"\n\n" : "") + parts[2]; + span.appendChild(linkElem); - continue; - } - } - } + continue; + } + } + } - current.textContent+=txt[i]; - } - appendcurrent(); - return span; - } - static unspoil(e:any) : void{ - e.target.classList.remove("spoiler"); - e.target.classList.add("unspoiled"); - } - giveBox(box:HTMLDivElement){ - box.onkeydown=_=>{ - //console.log(_); - }; - let prevcontent=""; - box.onkeyup=_=>{ - const content=MarkDown.gatherBoxText(box); - if(content!==prevcontent){ - prevcontent=content; - this.txt=content.split(""); - this.boxupdate(box); - } - }; - box.onpaste=_=>{ - if(!_.clipboardData) return; - console.log(_.clipboardData.types) - const data=_.clipboardData.getData("text"); + current.textContent+=txt[i]; + } + appendcurrent(); + return span; + } + static unspoil(e:any) : void{ + e.target.classList.remove("spoiler"); + e.target.classList.add("unspoiled"); + } + giveBox(box:HTMLDivElement){ + box.onkeydown=_=>{ + //console.log(_); + }; + let prevcontent=""; + box.onkeyup=_=>{ + const content=MarkDown.gatherBoxText(box); + if(content!==prevcontent){ + prevcontent=content; + this.txt=content.split(""); + this.boxupdate(box); + } + }; + box.onpaste=_=>{ + if(!_.clipboardData)return; + console.log(_.clipboardData.types); + const data=_.clipboardData.getData("text"); - document.execCommand('insertHTML', false, data); - _.preventDefault(); - if(!box.onkeyup) return; - box.onkeyup(new KeyboardEvent("_")) - } - } - boxupdate(box:HTMLElement){ - const restore = saveCaretPosition(box); - box.innerHTML=""; - box.append(this.makeHTML({keep:true})) - if(restore){ - restore(); - } - } - static gatherBoxText(element:HTMLElement){ - if(element.tagName.toLowerCase()==="img"){ - return (element as HTMLImageElement).alt; - } - if(element.tagName.toLowerCase()==="br"){ - return "\n"; - } + document.execCommand("insertHTML", false, data); + _.preventDefault(); + if(!box.onkeyup)return; + box.onkeyup(new KeyboardEvent("_")); + }; + } + boxupdate(box:HTMLElement){ + const restore = saveCaretPosition(box); + box.innerHTML=""; + box.append(this.makeHTML({keep: true})); + if(restore){ + restore(); + } + } + static gatherBoxText(element:HTMLElement){ + if(element.tagName.toLowerCase()==="img"){ + return(element as HTMLImageElement).alt; + } + if(element.tagName.toLowerCase()==="br"){ + return"\n"; + } - let build=""; - for(const thing of element.childNodes){ - - if(thing instanceof Text){ - - const text=thing.textContent; - build+=text; - continue; - } - const text=this.gatherBoxText(thing as HTMLElement); - if(text){ - build+=text; - } - } - return build; - } + let build=""; + for(const thing of element.childNodes){ + if(thing instanceof Text){ + const text=thing.textContent; + build+=text; + continue; + } + const text=this.gatherBoxText(thing as HTMLElement); + if(text){ + build+=text; + } + } + return build; + } } //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div function saveCaretPosition(context){ - var selection = window.getSelection(); - if(!selection) return; - var range = selection.getRangeAt(0); - range.setStart( context, 0 ); - var len = range.toString().length; + const selection = window.getSelection(); + if(!selection)return; + const range = selection.getRangeAt(0); + range.setStart(context, 0); + const len = range.toString().length; - return function restore(){ - if(!selection) return; - var pos = getTextNodeAtPosition(context, len); - selection.removeAllRanges(); - var range = new Range(); - range.setStart(pos.node ,pos.position); - selection.addRange(range); - - } + return function restore(){ + if(!selection)return; + const pos = getTextNodeAtPosition(context, len); + selection.removeAllRanges(); + const range = new Range(); + range.setStart(pos.node ,pos.position); + selection.addRange(range); + }; } function getTextNodeAtPosition(root, index){ - const NODE_TYPE = NodeFilter.SHOW_TEXT; - var treeWalker = document.createTreeWalker(root, NODE_TYPE, function next(elem) { - if(!elem.textContent) return 0; - if(index > elem.textContent.length){ - index -= elem.textContent.length; - return NodeFilter.FILTER_REJECT - } - return NodeFilter.FILTER_ACCEPT; - }); - var c = treeWalker.nextNode(); - return { - node: c? c: root, - position: index - }; + const NODE_TYPE = NodeFilter.SHOW_TEXT; + const treeWalker = document.createTreeWalker(root, NODE_TYPE, elem=>{ + if(!elem.textContent)return 0; + if(index > elem.textContent.length){ + index -= elem.textContent.length; + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_ACCEPT; + }); + const c = treeWalker.nextNode(); + return{ + node: c? c: root, + position: index + }; } -export {MarkDown}; +export{MarkDown}; diff --git a/webpage/member.ts b/webpage/member.ts index 20340fb..137057b 100644 --- a/webpage/member.ts +++ b/webpage/member.ts @@ -1,218 +1,219 @@ -import {User} from "./user.js"; -import {Role} from "./role.js"; -import {Guild} from "./guild.js"; -import { SnowFlake } from "./snowflake.js"; -import { memberjson, presencejson, userjson } from "./jsontypes.js"; -import { Dialog } from "./dialog.js"; +import{User}from"./user.js"; +import{Role}from"./role.js"; +import{Guild}from"./guild.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ memberjson, presencejson, userjson }from"./jsontypes.js"; +import{ Dialog }from"./dialog.js"; class Member{ - static already={}; - owner:Guild; - user:User; - roles:Role[]=[]; - id:string; - nick:string; - private constructor(memberjson:memberjson,owner:Guild){ - this.owner=owner; - if(this.localuser.userMap.has(memberjson.id)){ - this.user=this.localuser.userMap.get(memberjson.id) as User; - }else if(memberjson.user){ - this.user=new User(memberjson.user,owner.localuser); - }else{ - throw new Error("Missing user object of this member"); - } + static already={}; + owner:Guild; + user:User; + roles:Role[]=[]; + id:string; + nick:string; + private constructor(memberjson:memberjson,owner:Guild){ + this.owner=owner; + if(this.localuser.userMap.has(memberjson.id)){ + this.user=this.localuser.userMap.get(memberjson.id) as User; + }else if(memberjson.user){ + this.user=new User(memberjson.user,owner.localuser); + }else{ + throw new Error("Missing user object of this member"); + } - for(const thing of Object.keys(memberjson)){ - if(thing==="guild"){continue} - if(thing==="owner"){continue} - if(thing==="roles"){ - for(const strrole of memberjson["roles"]){ - - const role=SnowFlake.getSnowFlakeFromID(strrole,Role).getObject(); - this.roles.push(role); - } - continue; - } - this[thing]=memberjson[thing]; - } - if(this.localuser.userMap.has(this?.id)){ - this.user=this.localuser.userMap.get(this?.id) as User; - return; - } - - - } - get guild(){ - return this.owner; - } - get localuser(){ - return this.guild.localuser; - } - get info(){ - return this.owner.info; - } - static async new(memberjson:memberjson,owner:Guild):Promise{ - let user:User; - if(owner.localuser.userMap.has(memberjson.id)){ - user=owner.localuser.userMap.get(memberjson.id); - }else if(memberjson.user){ - user=new User(memberjson.user,owner.localuser); - }else{ - throw new Error("missing user object of this member"); - } - if(user.members.has(owner)){ - let memb=user.members.get(owner) - if(memb===undefined){ - memb=new Member(memberjson,owner); - user.members.set(owner,memb); - return memb - }else if(memb instanceof Promise){ - return await memb;//I should do something else, though for now this is "good enough" - }else{ - return memb; - } - }else{ - const memb=new Member(memberjson,owner); - user.members.set(owner,memb); - return memb; - } - } - static async resolveMember(user:User,guild:Guild):Promise{ - const maybe=user.members.get(guild); - if(!user.members.has(guild)){ - const membpromise=guild.localuser.resolvemember(user.id,guild.id); - const promise=new Promise( async res=>{ - const membjson=await membpromise; - if(membjson===undefined){ - res(undefined); - return undefined; - }else{ - const member=new Member(membjson,guild); - const map=guild.localuser.presences; - member.getPresence(map.get(member.id)); - map.delete(member.id); - res(member); - return member; - } - }) - user.members.set(guild,promise); - } - if(maybe instanceof Promise){ - return await maybe; - }else{ - return maybe - } - } - public getPresence(presence:presencejson|undefined){ - this.user.getPresence(presence); - } - /** + for(const thing of Object.keys(memberjson)){ + if(thing==="guild"){ + continue; + } + if(thing==="owner"){ + continue; + } + if(thing==="roles"){ + for(const strrole of memberjson.roles){ + const role=SnowFlake.getSnowFlakeFromID(strrole,Role).getObject(); + this.roles.push(role); + } + continue; + } + this[thing]=memberjson[thing]; + } + if(this.localuser.userMap.has(this?.id)){ + this.user=this.localuser.userMap.get(this?.id) as User; + } + } + get guild(){ + return this.owner; + } + get localuser(){ + return this.guild.localuser; + } + get info(){ + return this.owner.info; + } + static async new(memberjson:memberjson,owner:Guild):Promise{ + let user:User; + if(owner.localuser.userMap.has(memberjson.id)){ + user=owner.localuser.userMap.get(memberjson.id) as User; + }else if(memberjson.user){ + user=new User(memberjson.user,owner.localuser); + }else{ + throw new Error("missing user object of this member"); + } + if(user.members.has(owner)){ + let memb=user.members.get(owner); + if(memb===undefined){ + memb=new Member(memberjson,owner); + user.members.set(owner,memb); + return memb; + }else if(memb instanceof Promise){ + return await memb;//I should do something else, though for now this is "good enough" + }else{ + return memb; + } + }else{ + const memb=new Member(memberjson,owner); + user.members.set(owner,memb); + return memb; + } + } + static async resolveMember(user:User,guild:Guild):Promise{ + const maybe=user.members.get(guild); + if(!user.members.has(guild)){ + const membpromise=guild.localuser.resolvemember(user.id,guild.id); + const promise=new Promise(async res=>{ + const membjson=await membpromise; + if(membjson===undefined){ + res(undefined); + }else{ + const member=new Member(membjson,guild); + const map=guild.localuser.presences; + member.getPresence(map.get(member.id)); + map.delete(member.id); + res(member); + return member; + } + }); + user.members.set(guild,promise); + } + if(maybe instanceof Promise){ + return await maybe; + }else{ + return maybe; + } + } + public getPresence(presence:presencejson|undefined){ + this.user.getPresence(presence); + } + /** * @todo */ - highInfo(){ - fetch(this.info.api+"/users/"+this.id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+this.guild.id,{headers:this.guild.headers}) - } - hasRole(ID:string){ - console.log(this.roles,ID); - for(const thing of this.roles){ - if(thing.id===ID){ - return true; - } - } - return false; - } - getColor(){ - for(const thing of this.roles){ - const color=thing.getColor(); - if(color){ - return color; - } - } - return ""; - } - isAdmin(){ - for(const role of this.roles){ - if(role.permissions.getPermission("ADMINISTRATOR")){ - return true; - } - } - return this.guild.properties.owner_id===this.user.id; - } - bind(html:HTMLElement){ - if(html.tagName==="SPAN"){ - if(!this) {return}; - /* + highInfo(){ + fetch(this.info.api+"/users/"+this.id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+this.guild.id,{headers: this.guild.headers}); + } + hasRole(ID:string){ + console.log(this.roles,ID); + for(const thing of this.roles){ + if(thing.id===ID){ + return true; + } + } + return false; + } + getColor(){ + for(const thing of this.roles){ + const color=thing.getColor(); + if(color){ + return color; + } + } + return""; + } + isAdmin(){ + for(const role of this.roles){ + if(role.permissions.getPermission("ADMINISTRATOR")){ + return true; + } + } + return this.guild.properties.owner_id===this.user.id; + } + bind(html:HTMLElement){ + if(html.tagName==="SPAN"){ + if(!this){ + return; + } + /* if(this.error){ } */ - html.style.color=this.getColor(); - } + html.style.color=this.getColor(); + } - //this.profileclick(html); - } - profileclick(html:HTMLElement){ - //to be implemented - } - get name(){ - return this.nick||this.user.username; - } - kick(){ - let reason="" - const menu=new Dialog(["vdiv", - ["title","Kick "+this.name+" from "+this.guild.properties.name], - ["textbox","Reason:","",function(e:Event){ - reason=(e.target as HTMLInputElement).value; - }], - ["button","","submit",()=>{ - this.kickAPI(reason); - menu.hide(); - }] - ]); - menu.show(); - } - kickAPI(reason:string){ - const headers=structuredClone(this.guild.headers); - headers["x-audit-log-reason"]=reason - fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`,{ - method:"DELETE", - headers, + //this.profileclick(html); + } + profileclick(html:HTMLElement){ + //to be implemented + } + get name(){ + return this.nick||this.user.username; + } + kick(){ + let reason=""; + const menu=new Dialog(["vdiv", + ["title","Kick "+this.name+" from "+this.guild.properties.name], + ["textbox","Reason:","",function(e:Event){ + reason=(e.target as HTMLInputElement).value; + }], + ["button","","submit",()=>{ + this.kickAPI(reason); + menu.hide(); + }] + ]); + menu.show(); + } + kickAPI(reason:string){ + const headers=structuredClone(this.guild.headers); + headers["x-audit-log-reason"]=reason; + fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`,{ + method: "DELETE", + headers, - }) - } - ban(){ - let reason="" - const menu=new Dialog(["vdiv", - ["title","Ban "+this.name+" from "+this.guild.properties.name], - ["textbox","Reason:","",function(e:Event){ - reason=(e.target as HTMLInputElement).value; - }], - ["button","","submit",()=>{ - this.banAPI(reason); - menu.hide(); - }] - ]); - menu.show(); - } - banAPI(reason:string){ - const headers=structuredClone(this.guild.headers); - headers["x-audit-log-reason"]=reason - fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`,{ - method:"PUT", - headers + }); + } + ban(){ + let reason=""; + const menu=new Dialog(["vdiv", + ["title","Ban "+this.name+" from "+this.guild.properties.name], + ["textbox","Reason:","",function(e:Event){ + reason=(e.target as HTMLInputElement).value; + }], + ["button","","submit",()=>{ + this.banAPI(reason); + menu.hide(); + }] + ]); + menu.show(); + } + banAPI(reason:string){ + const headers=structuredClone(this.guild.headers); + headers["x-audit-log-reason"]=reason; + fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`,{ + method: "PUT", + headers - }) - } - hasPermission(name:string):boolean{ - if(this.isAdmin()){ - return true; - } - for(const thing of this.roles){ - if(thing.permissions.getPermission(name)){ - return true; - } - } - return false; - } + }); + } + hasPermission(name:string):boolean{ + if(this.isAdmin()){ + return true; + } + for(const thing of this.roles){ + if(thing.permissions.getPermission(name)){ + return true; + } + } + return false; + } } -export {Member}; +export{Member}; diff --git a/webpage/message.ts b/webpage/message.ts index 1d1846a..97e29e1 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -1,33 +1,33 @@ -import {Contextmenu} from "./contextmenu.js"; -import {User} from "./user.js"; -import {Member} from "./member.js"; -import {MarkDown} from "./markdown.js"; -import {Embed} from "./embed.js"; -import { Channel } from "./channel.js"; -import {Localuser} from "./localuser.js"; -import { Role } from "./role.js"; -import {File} from "./file.js"; -import { SnowFlake } from "./snowflake.js"; -import { memberjson, messagejson } from "./jsontypes.js"; -import {Emoji} from "./emoji.js"; +import{Contextmenu}from"./contextmenu.js"; +import{User}from"./user.js"; +import{Member}from"./member.js"; +import{MarkDown}from"./markdown.js"; +import{Embed}from"./embed.js"; +import{ Channel }from"./channel.js"; +import{Localuser}from"./localuser.js"; +import{ Role }from"./role.js"; +import{File}from"./file.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ memberjson, messagejson }from"./jsontypes.js"; +import{Emoji}from"./emoji.js"; class Message{ - static contextmenu=new Contextmenu("message menu"); - owner:Channel; - headers:Localuser["headers"]; - embeds:Embed[]; - author:User; - mentions:User[]; - mention_roles:Role[]; - attachments:File[];//probably should be its own class tbh, should be Attachments[] - snowflake:SnowFlake; - message_reference; - type:number; - timestamp:number; - content:MarkDown; - static del:Promise; - static resolve:Function; - /* + static contextmenu=new Contextmenu("message menu"); + owner:Channel; + headers:Localuser["headers"]; + embeds:Embed[]; + author:User; + mentions:User[]; + mention_roles:Role[]; + attachments:File[];//probably should be its own class tbh, should be Attachments[] + snowflake:SnowFlake; + message_reference; + type:number; + timestamp:number; + content:MarkDown; + static del:Promise; + static resolve:Function; + /* weakdiv:WeakRef; set div(e:HTMLDivElement){ if(!e){ @@ -40,563 +40,562 @@ class Message{ return this.weakdiv?.deref(); } //*/ - div:HTMLDivElement|undefined; - member:Member|undefined; - reactions:messagejson["reactions"]; - get id(){ - return this.snowflake.id; - } - static setup(){ - this.del=new Promise(_=>{this.resolve=_}); - Message.setupcmenu(); - } - static setupcmenu(){ - Message.contextmenu.addbutton("Copy raw text",function(this:Message){ - navigator.clipboard.writeText(this.content.rawString); - }); - Message.contextmenu.addbutton("Reply",function(this:Message){ - this.channel.setReplying(this); - }); - Message.contextmenu.addbutton("Copy message id",function(this:Message){ - navigator.clipboard.writeText(this.id); - }); - Message.contextmenu.addsubmenu("Add reaction",function(this:Message,arg,e:MouseEvent){ - Emoji.emojiPicker(e.x,e.y,this.localuser).then(_=>{ - this.reactionToggle(_); - }); - }); - Message.contextmenu.addbutton("Edit",function(this:Message){ - this.channel.editing=this; - const markdown=(document.getElementById("typebox") as HTMLDivElement)["markdown"] as MarkDown; - markdown.txt=this.content.rawString.split(''); - markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); - },null,function(){ - return this.author.id===this.localuser.user.id - }); - Message.contextmenu.addbutton("Delete message",function(this:Message){ - this.delete(); - },null,function(){ - return this.canDelete() + div:HTMLDivElement|undefined; + member:Member|undefined; + reactions:messagejson["reactions"]; + get id(){ + return this.snowflake.id; + } + static setup(){ + this.del=new Promise(_=>{ + this.resolve=_; + }); + Message.setupcmenu(); + } + static setupcmenu(){ + Message.contextmenu.addbutton("Copy raw text",function(this:Message){ + navigator.clipboard.writeText(this.content.rawString); + }); + Message.contextmenu.addbutton("Reply",function(this:Message){ + this.channel.setReplying(this); + }); + Message.contextmenu.addbutton("Copy message id",function(this:Message){ + navigator.clipboard.writeText(this.id); + }); + Message.contextmenu.addsubmenu("Add reaction",function(this:Message,arg,e:MouseEvent){ + Emoji.emojiPicker(e.x,e.y,this.localuser).then(_=>{ + this.reactionToggle(_); + }); + }); + Message.contextmenu.addbutton("Edit",function(this:Message){ + this.channel.editing=this; + const markdown=(document.getElementById("typebox") as HTMLDivElement)["markdown"] as MarkDown; + markdown.txt=this.content.rawString.split(""); + markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); + },null,function(){ + return this.author.id===this.localuser.user.id; + }); + Message.contextmenu.addbutton("Delete message",function(this:Message){ + this.delete(); + },null,function(){ + return this.canDelete(); + }); + } + constructor(messagejson:messagejson,owner:Channel){ + this.owner=owner; + this.headers=this.owner.headers; + this.giveData(messagejson); + this.owner.messages.set(this.id,this); + } + reactionToggle(emoji:string|Emoji){ + let remove = false; + for(const thing of this.reactions){ + if(thing.emoji.name === emoji){ + remove = thing.me; + break; + } + } + let reactiontxt:string; + if(emoji instanceof Emoji){ + reactiontxt=`${emoji.name}:${emoji.id}`; + }else{ + reactiontxt=encodeURIComponent(emoji); + } + fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${reactiontxt}/@me`, { + method: remove ? "DELETE" : "PUT", + headers: this.headers + }); + } + giveData(messagejson:messagejson){ + const func=this.channel.infinite.snapBottom(); + for(const thing of Object.keys(messagejson)){ + if(thing==="attachments"){ + this.attachments=[]; + for(const thing of messagejson.attachments){ + this.attachments.push(new File(thing,this)); + } + continue; + }else if(thing==="content"){ + this.content=new MarkDown(messagejson[thing],this.channel); + continue; + }else if(thing ==="id"){ + this.snowflake=new SnowFlake(messagejson.id,this); + continue; + }else if(thing==="member"){ + Member.new(messagejson.member as memberjson,this.guild).then(_=>{ + this.member=_ as Member; + }); + continue; + }else if(thing ==="embeds"){ + this.embeds=[]; + for(const thing in messagejson.embeds){ + console.log(thing,messagejson.embeds); + this.embeds[thing]=new Embed(messagejson.embeds[thing],this); + } + continue; + } + this[thing]=messagejson[thing]; + } + if(messagejson.reactions?.length){ + console.log(messagejson.reactions,":3"); + } - }) - } - constructor(messagejson:messagejson,owner:Channel){ - this.owner=owner; - this.headers=this.owner.headers; - this.giveData(messagejson); - this.owner.messages.set(this.id,this); - } - reactionToggle(emoji:string|Emoji){ + this.author=new User(messagejson.author,this.localuser); + for(const thing in messagejson.mentions){ + this.mentions[thing]=new User(messagejson.mentions[thing],this.localuser); + } + if(!this.member&&this.guild.id!=="@me"){ + this.author.resolvemember(this.guild).then(_=>{ + this.member=_; + }); + } + if(this.mentions.length||this.mention_roles.length){//currently mention_roles isn't implemented on the spacebar servers + console.log(this.mentions,this.mention_roles); + } + if(this.mentionsuser(this.localuser.user)){ + console.log(this); + } + if(this.div){ + this.generateMessage(); + } + func(); + } + canDelete(){ + return this.channel.hasPermission("MANAGE_MESSAGES")||this.author.snowflake===this.localuser.user.snowflake; + } + get channel(){ + return this.owner; + } + get guild(){ + return this.owner.guild; + } + get localuser(){ + return this.owner.localuser; + } + get info(){ + return this.owner.info; + } + messageevents(obj:HTMLDivElement){ + const func=Message.contextmenu.bindContextmenu(obj,this,undefined); + this.div=obj; + obj.classList.add("messagediv"); + } + deleteDiv(){ + if(!this.div)return; + try{ + this.div.remove(); + this.div=undefined; + }catch(e){ + console.error(e); + } + } + mentionsuser(userd:User|Member){ + if(userd instanceof User){ + return this.mentions.includes(userd); + }else if(userd instanceof Member){ + return this.mentions.includes(userd.user); + } + } + getimages(){ + const build:File[]=[]; + for(const thing of this.attachments){ + if(thing.content_type.startsWith("image/")){ + build.push(thing); + } + } + return build; + } + async edit(content){ + return await fetch(this.info.api+"/channels/"+this.channel.snowflake+"/messages/"+this.id,{ + method: "PATCH", + headers: this.headers, + body: JSON.stringify({content}) + }); + } + delete(){ + fetch(`${this.info.api}/channels/${this.channel.snowflake}/messages/${this.id}`,{ + headers: this.headers, + method: "DELETE", + }); + } + deleteEvent(){ + if(this.div){ + this.div.innerHTML=""; + this.div=undefined; + } + const prev=this.channel.idToPrev.get(this.id); + const next=this.channel.idToNext.get(this.id); + if(prev){ + this.channel.idToPrev.delete(this.id); + } + if(next){ + this.channel.idToNext.delete(this.id); + } + if(prev&&next){ + this.channel.idToPrev.set(next,prev); + this.channel.idToNext.set(prev,next); + } + this.channel.messageids.delete(this.snowflake); + if(prev){ + const prevmessage=this.channel.messages.get(prev); + if(prevmessage){ + prevmessage.generateMessage(); + } + } + if(this.channel.lastmessage===this){ + if(prev){ + this.channel.lastmessage=this.channel.messages.get(prev); + }else{ + this.channel.lastmessage=undefined; + } + } + } + reactdiv:WeakRef; + blockedPropigate(){ + const previd=this.channel.idToPrev.get(this.id); + if(!previd){ + this.generateMessage(); + return; + } + const premessage=this.channel.messages.get(previd); + if(premessage?.author===this.author){ + premessage.blockedPropigate(); + }else{ + this.generateMessage(); + } + } + generateMessage(premessage?:Message|undefined,ignoredblock=false){ + if(!this.div)return; + if(!premessage){ + premessage=this.channel.messages.get(this.channel.idToPrev.get(this.id) as string); + } + const div=this.div; + if(this===this.channel.replyingto){ + div.classList.add("replying"); + } + div.innerHTML=""; + const build = document.createElement("div"); - let remove = false - for (const thing of this.reactions) { - if (thing.emoji.name === emoji) { - remove = thing.me - break - } + build.classList.add("flexltr","message"); + div.classList.remove("zeroheight"); + if(this.author.relationshipType===2){ + if(ignoredblock){ + if(premessage?.author!==this.author){ + const span=document.createElement("span"); + span.textContent="You have this user blocked, click to hide these messages."; + div.append(span); + span.classList.add("blocked"); + span.onclick=_=>{ + const scroll=this.channel.infinite.scrollTop; + let next:Message|undefined=this; + while(next?.author===this.author){ + next.generateMessage(); + next=this.channel.messages.get(this.channel.idToNext.get(next.id) as string); + } + if(this.channel.infinite.scroll&&scroll){ + this.channel.infinite.scroll.scrollTop=scroll; + } + }; + } + }else{ + div.classList.remove("topMessage"); + if(premessage?.author===this.author){ + div.classList.add("zeroheight"); + premessage.blockedPropigate(); + div.appendChild(build); + return div; + }else{ + build.classList.add("blocked","topMessage"); + const span=document.createElement("span"); + let count=1; + let next=this.channel.messages.get(this.channel.idToNext.get(this.id) as string); + while(next?.author===this.author){ + count++; + next=this.channel.messages.get(this.channel.idToNext.get(next.id) as string); + } + span.textContent=`You have this user blocked, click to see the ${count} blocked messages.`; + build.append(span); + span.onclick=_=>{ + const scroll=this.channel.infinite.scrollTop; + const func=this.channel.infinite.snapBottom(); + let next:Message|undefined=this; + while(next?.author===this.author){ + next.generateMessage(undefined,true); + next=this.channel.messages.get(this.channel.idToNext.get(next.id) as string); + console.log("loopy"); + } + if(this.channel.infinite.scroll&&scroll){ + func(); + this.channel.infinite.scroll.scrollTop=scroll; + } + }; + div.appendChild(build); + return div; + } + } + } + if(this.message_reference){ + const replyline=document.createElement("div"); + const line=document.createElement("hr"); + const minipfp=document.createElement("img"); + minipfp.classList.add("replypfp"); + replyline.appendChild(line); + replyline.appendChild(minipfp); + const username=document.createElement("span"); + replyline.appendChild(username); + const reply=document.createElement("div"); + username.classList.add("username"); + reply.classList.add("replytext"); + replyline.appendChild(reply); + const line2=document.createElement("hr"); + replyline.appendChild(line2); + line2.classList.add("reply"); + line.classList.add("startreply"); + replyline.classList.add("replyflex"); + this.channel.getmessage(this.message_reference.message_id).then(message=>{ + if(message.author.relationshipType===2){ + username.textContent="Blocked user"; + return; + } + const author=message.author; + reply.appendChild(message.content.makeHTML({stdsize: true})); + minipfp.src=author.getpfpsrc(); + author.bind(minipfp); + username.textContent=author.username; + author.bind(username); + }); + reply.onclick=_=>{ + this.channel.infinite.focus(this.message_reference.message_id); + }; + div.appendChild(replyline); + } + div.appendChild(build); + if({0: true,19: true}[this.type]||this.attachments.length!==0){ + const pfpRow = document.createElement("div"); + pfpRow.classList.add("flexltr"); + let pfpparent, current; + if(premessage!=null){ + pfpparent??=premessage; + let pfpparent2=pfpparent.all; + pfpparent2??=pfpparent; + const old=(new Date(pfpparent2.timestamp).getTime())/1000; + const newt=(new Date(this.timestamp).getTime())/1000; + current=(newt-old)>600; + } + const combine=(premessage?.author?.snowflake!=this.author.snowflake)||(current)||this.message_reference; + if(combine){ + const pfp=this.author.buildpfp(); + this.author.bind(pfp,this.guild,false); + pfpRow.appendChild(pfp); + }else{ + div["pfpparent"]=pfpparent; + } + pfpRow.classList.add("pfprow"); + build.appendChild(pfpRow); + const text=document.createElement("div"); + text.classList.add("flexttb"); + const texttxt=document.createElement("div"); + texttxt.classList.add("commentrow","flexttb"); + text.appendChild(texttxt); + if(combine){ + const username=document.createElement("span"); + username.classList.add("username"); + this.author.bind(username,this.guild); + div.classList.add("topMessage"); + username.textContent=this.author.username; + const userwrap=document.createElement("div"); + userwrap.classList.add("flexltr"); + userwrap.appendChild(username); + if(this.author.bot){ + const username=document.createElement("span"); + username.classList.add("bot"); + username.textContent="BOT"; + userwrap.appendChild(username); + } + const time=document.createElement("span"); + time.textContent=" "+formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + userwrap.appendChild(time); - } - let reactiontxt:string; - if(emoji instanceof Emoji){ - reactiontxt=`${emoji.name}:${emoji.id}`; - }else{ - reactiontxt=encodeURIComponent(emoji); - } - fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${reactiontxt}/@me`, { - method: remove ? "DELETE" : "PUT", - headers: this.headers - }) - } - giveData(messagejson:messagejson){ - const func=this.channel.infinite.snapBottom(); - for(const thing of Object.keys(messagejson)){ - if(thing==="attachments"){ - this.attachments=[]; - for(const thing of messagejson.attachments){ - this.attachments.push(new File(thing,this)); - } - continue; - }else if(thing==="content"){ - this.content=new MarkDown(messagejson[thing],this.channel); - continue; - }else if(thing ==="id"){ - this.snowflake=new SnowFlake(messagejson.id,this); - continue; - }else if(thing==="member"){ - Member.new(messagejson.member as memberjson,this.guild).then(_=>{ - this.member=_ as Member; - }); - continue; - }else if(thing ==="embeds"){ - this.embeds=[]; - for(const thing in messagejson.embeds){ - console.log(thing,messagejson.embeds) - this.embeds[thing]=new Embed(messagejson.embeds[thing],this); - } - continue; - } - this[thing]=messagejson[thing]; - } - if(messagejson.reactions?.length){ - console.log(messagejson.reactions,":3"); - } + texttxt.appendChild(userwrap); + }else{ + div.classList.remove("topMessage"); + } + const messaged=this.content.makeHTML(); + div["txt"]=messaged; + const messagedwrap=document.createElement("div"); + messagedwrap.classList.add("flexttb"); + messagedwrap.appendChild(messaged); + texttxt.appendChild(messagedwrap); - this.author=new User(messagejson.author,this.localuser); - for(const thing in messagejson.mentions){ - this.mentions[thing]=new User(messagejson.mentions[thing],this.localuser); - } - if(!this.member&&this.guild.id!=="@me"){ - this.author.resolvemember(this.guild).then(_=>{ - this.member=_; - }) - } - if(this.mentions.length||this.mention_roles.length){//currently mention_roles isn't implemented on the spacebar servers - console.log(this.mentions,this.mention_roles) - } - if(this.mentionsuser(this.localuser.user)){ - console.log(this); - } - if(this.div){ - this.generateMessage(); - } - func(); - } - canDelete(){ - return this.channel.hasPermission("MANAGE_MESSAGES")||this.author.snowflake===this.localuser.user.snowflake; - } - get channel(){ - return this.owner; - } - get guild(){ - return this.owner.guild; - } - get localuser(){ - return this.owner.localuser; - } - get info(){ - return this.owner.info; - } - messageevents(obj:HTMLDivElement){ - const func=Message.contextmenu.bindContextmenu(obj,this,undefined); - this.div=obj; - obj.classList.add("messagediv"); - } - deleteDiv(){ - if(!this.div) return; - try{ - this.div.remove(); - this.div=undefined; - }catch(e){ - console.error(e) - } - } - mentionsuser(userd:User|Member){ - if(userd instanceof User){ - return this.mentions.includes(userd); - }else if(userd instanceof Member){ - return this.mentions.includes(userd.user); - } - } - getimages(){ - const build:File[]=[]; - for(const thing of this.attachments){ - if(thing.content_type.startsWith('image/')){ - build.push(thing); - } - } - return build; - } - async edit(content){ - return await fetch(this.info.api+"/channels/"+this.channel.snowflake+"/messages/"+this.id,{ - method: "PATCH", - headers: this.headers, - body:JSON.stringify({content:content}) - }); - } - delete(){ - fetch(`${this.info.api}/channels/${this.channel.snowflake}/messages/${this.id}`,{ - headers:this.headers, - method:"DELETE", - }) - } - deleteEvent(){ - if(this.div){ - this.div.innerHTML=""; - this.div=undefined; - } - const prev=this.channel.idToPrev.get(this.id); - const next=this.channel.idToNext.get(this.id); - if(prev){ - this.channel.idToPrev.delete(this.id) - } - if(next){ - this.channel.idToNext.delete(this.id) - } - if(prev&&next){ - this.channel.idToPrev.set(next,prev); - this.channel.idToNext.set(prev,next); - } - this.channel.messageids.delete(this.snowflake); - if(prev){ - const prevmessage=this.channel.messages.get(prev); - if(prevmessage){ - prevmessage.generateMessage(); - } - } - if(this.channel.lastmessage===this){ - if(prev){ - this.channel.lastmessage=this.channel.messages.get(prev); - }else{ - this.channel.lastmessage=undefined; - } - } - } - reactdiv:WeakRef; - blockedPropigate(){ - const previd=this.channel.idToPrev.get(this.id); - if(!previd){ - this.generateMessage(); - return; - } - const premessage=this.channel.messages.get(previd); - if(premessage?.author===this.author){ - premessage.blockedPropigate(); - }else{ - this.generateMessage(); - } - } - generateMessage(premessage:Message|undefined=undefined,ignoredblock=false){ - if(!this.div) return; - if(!premessage){ - premessage=this.channel.messages.get(this.channel.idToPrev.get(this.id) as string); - } - const div=this.div; - if(this===this.channel.replyingto){ - div.classList.add("replying"); - } - div.innerHTML=""; - const build = document.createElement('div'); + build.appendChild(text); + if(this.attachments.length){ + console.log(this.attachments); + const attach = document.createElement("div"); + attach.classList.add("flexltr"); + for(const thing of this.attachments){ + attach.appendChild(thing.getHTML()); + } + messagedwrap.appendChild(attach); + } + if(this.embeds.length){ + console.log(this.embeds); + const embeds = document.createElement("div"); + embeds.classList.add("flexltr"); + for(const thing of this.embeds){ + embeds.appendChild(thing.generateHTML()); + } + messagedwrap.appendChild(embeds); + } + // + }else if(this.type===7){ + const text=document.createElement("div"); + text.classList.add("flexttb"); + const texttxt=document.createElement("div"); + text.appendChild(texttxt); + build.appendChild(text); + texttxt.classList.add("flexltr"); + const messaged=document.createElement("span"); + div["txt"]=messaged; + messaged.textContent="welcome: "; + texttxt.appendChild(messaged); - build.classList.add("flexltr","message"); - div.classList.remove("zeroheight") - if(this.author.relationshipType===2){ - if(ignoredblock){ - if(premessage?.author!==this.author){ - const span=document.createElement("span"); - span.textContent=`You have this user blocked, click to hide these messages.`; - div.append(span); - span.classList.add("blocked") - span.onclick=_=>{ - const scroll=this.channel.infinite.scrollTop; - let next:Message|undefined=this; - while(next?.author===this.author){ - next.generateMessage(undefined); - next=this.channel.messages.get(this.channel.idToNext.get(next.id) as string); - } - if(this.channel.infinite.scroll&&scroll){ - this.channel.infinite.scroll.scrollTop=scroll; - } - } - } - }else{ - div.classList.remove("topMessage"); - if(premessage?.author===this.author){ - div.classList.add("zeroheight") - premessage.blockedPropigate(); - div.appendChild(build); - return div; - }else{ - build.classList.add("blocked","topMessage") - const span=document.createElement("span"); - let count=1; - let next=this.channel.messages.get(this.channel.idToNext.get(this.id) as string); - while(next?.author===this.author){ - count++; - next=this.channel.messages.get(this.channel.idToNext.get(next.id) as string); - } - span.textContent=`You have this user blocked, click to see the ${count} blocked messages.`; - build.append(span); - span.onclick=_=>{ - const scroll=this.channel.infinite.scrollTop; - const func=this.channel.infinite.snapBottom(); - let next:Message|undefined=this; - while(next?.author===this.author){ - next.generateMessage(undefined,true); - next=this.channel.messages.get(this.channel.idToNext.get(next.id) as string); - console.log("loopy") - } - if(this.channel.infinite.scroll&&scroll){ - func(); - this.channel.infinite.scroll.scrollTop=scroll; - } - } - div.appendChild(build); - return div; - } - } - } - if(this.message_reference){ - const replyline=document.createElement("div"); - const line=document.createElement("hr"); - const minipfp=document.createElement("img") - minipfp.classList.add("replypfp"); - replyline.appendChild(line); - replyline.appendChild(minipfp); - const username=document.createElement("span"); - replyline.appendChild(username); - const reply=document.createElement("div"); - username.classList.add("username"); - reply.classList.add("replytext"); - replyline.appendChild(reply); - const line2=document.createElement("hr"); - replyline.appendChild(line2); - line2.classList.add("reply"); - line.classList.add("startreply"); - replyline.classList.add("replyflex") - this.channel.getmessage(this.message_reference.message_id).then(message=>{ - if(message.author.relationshipType===2){ - username.textContent="Blocked user"; - return; - } - const author=message.author; - reply.appendChild(message.content.makeHTML({stdsize:true})); - minipfp.src=author.getpfpsrc() - author.bind(minipfp); - username.textContent=author.username; - author.bind(username); - }); - reply.onclick=_=>{ - this.channel.infinite.focus(this.message_reference.message_id); - } - div.appendChild(replyline); - } - div.appendChild(build); - if({0:true,19:true}[this.type]||this.attachments.length!==0){ - const pfpRow = document.createElement('div'); - pfpRow.classList.add("flexltr"); - let pfpparent, current - if(premessage!=null){ - pfpparent??=premessage; - let pfpparent2=pfpparent.all; - pfpparent2??=pfpparent; - const old=(new Date(pfpparent2.timestamp).getTime())/1000; - const newt=(new Date(this.timestamp).getTime())/1000; - current=(newt-old)>600; - } - const combine=(premessage?.author?.snowflake!=this.author.snowflake)||(current)||this.message_reference - if(combine){ - const pfp=this.author.buildpfp(); - this.author.bind(pfp,this.guild,false); - pfpRow.appendChild(pfp); - }else{ - div["pfpparent"]=pfpparent; - } - pfpRow.classList.add("pfprow") - build.appendChild(pfpRow); - const text=document.createElement("div"); - text.classList.add("flexttb") - const texttxt=document.createElement("div"); - texttxt.classList.add("commentrow","flexttb"); - text.appendChild(texttxt); - if(combine){ - const username=document.createElement("span"); - username.classList.add("username") - this.author.bind(username,this.guild); - div.classList.add("topMessage"); - username.textContent=this.author.username; - const userwrap=document.createElement("div"); - userwrap.classList.add("flexltr"); - userwrap.appendChild(username) - if(this.author.bot){ - const username=document.createElement("span"); - username.classList.add("bot") - username.textContent="BOT"; - userwrap.appendChild(username) - } - const time=document.createElement("span"); - time.textContent=" "+formatTime(new Date(this.timestamp)); - time.classList.add("timestamp") - userwrap.appendChild(time); + const username=document.createElement("span"); + username.textContent=this.author.username; + //this.author.profileclick(username); + this.author.bind(username,this.guild); + texttxt.appendChild(username); + username.classList.add("username"); - texttxt.appendChild(userwrap) - }else{ - div.classList.remove("topMessage"); - } - const messaged=this.content.makeHTML(); - div["txt"]=messaged; - const messagedwrap=document.createElement("div"); - messagedwrap.classList.add("flexttb") - messagedwrap.appendChild(messaged) - texttxt.appendChild(messagedwrap) + const time=document.createElement("span"); + time.textContent=" "+formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + texttxt.append(time); + div.classList.add("topMessage"); + } + div["all"]=this; + const reactions=document.createElement("div"); + reactions.classList.add("flexltr","reactiondiv"); + this.reactdiv=new WeakRef(reactions); + this.updateReactions(); + div.append(reactions); + return(div); + } + updateReactions(){ + const reactdiv=this.reactdiv.deref(); + if(!reactdiv)return; + const func=this.channel.infinite.snapBottom(); + reactdiv.innerHTML=""; + for(const thing of this.reactions){ + const reaction=document.createElement("div"); + reaction.classList.add("reaction"); + if(thing.me){ + reaction.classList.add("meReacted"); + } + let emoji:HTMLElement; + if(thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)){ + if(/\d{17,21}/.test(thing.emoji.name)) thing.emoji.id=thing.emoji.name;//Should stop being a thing once the server fixes this bug + const emo=new Emoji(thing.emoji as {name:string,id:string,animated:boolean},this.guild); + emoji=emo.getHTML(false); + }else{ + emoji=document.createElement("p"); + emoji.textContent=thing.emoji.name; + } + const count=document.createElement("p"); + count.textContent=""+thing.count; + count.classList.add("reactionCount"); + reaction.append(count); + reaction.append(emoji); + reactdiv.append(reaction); - build.appendChild(text) - if(this.attachments.length){ - console.log(this.attachments) - const attach = document.createElement("div"); - attach.classList.add("flexltr"); - for(const thing of this.attachments){ - attach.appendChild(thing.getHTML()) - } - messagedwrap.appendChild(attach) - } - if(this.embeds.length){ - console.log(this.embeds); - const embeds = document.createElement("div") - embeds.classList.add("flexltr"); - for(const thing of this.embeds){ - embeds.appendChild(thing.generateHTML()); - } - messagedwrap.appendChild(embeds) - } - // - }else if(this.type===7){ - - const text=document.createElement("div"); - text.classList.add("flexttb") - const texttxt=document.createElement("div"); - text.appendChild(texttxt); - build.appendChild(text); - texttxt.classList.add("flexltr"); - const messaged=document.createElement("span"); - div["txt"]=messaged; - messaged.textContent="welcome: "; - texttxt.appendChild(messaged); - - const username=document.createElement("span"); - username.textContent=this.author.username; - //this.author.profileclick(username); - this.author.bind(username,this.guild); - texttxt.appendChild(username); - username.classList.add("username"); - - const time=document.createElement("span"); - time.textContent=" "+formatTime(new Date(this.timestamp)); - time.classList.add("timestamp"); - texttxt.append(time); - div.classList.add("topMessage") - - } - div["all"]=this; - const reactions=document.createElement("div"); - reactions.classList.add("flexltr","reactiondiv"); - this.reactdiv=new WeakRef(reactions); - this.updateReactions(); - div.append(reactions) - return(div) - } - updateReactions(){ - const reactdiv=this.reactdiv.deref(); - if(!reactdiv) return; - const func=this.channel.infinite.snapBottom(); - reactdiv.innerHTML=""; - for(const thing of this.reactions){ - const reaction=document.createElement("div"); - reaction.classList.add("reaction"); - if(thing.me){ - reaction.classList.add("meReacted") - } - let emoji:HTMLElement; - if (thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)) { - if (/\d{17,21}/.test(thing.emoji.name)) thing.emoji.id=thing.emoji.name;//Should stop being a thing once the server fixes this bug - const emo=new Emoji(thing.emoji as {name:string,id:string,animated:boolean},this.guild); - emoji=emo.getHTML(false); - }else{ - emoji=document.createElement("p"); - emoji.textContent=thing.emoji.name; - } - const count=document.createElement("p"); - count.textContent=""+thing.count; - count.classList.add("reactionCount"); - reaction.append(count); - reaction.append(emoji); - reactdiv.append(reaction); - - reaction.onclick=_=>{ - this.reactionToggle(thing.emoji.name); - } - } - func(); - } - reactionAdd(data:{name:string},member:Member|{id:string}){ - for(const thing of this.reactions){ - if(thing.emoji.name===data.name){ - thing.count++; - if(member.id===this.localuser.user.id){ - thing.me=true; - this.updateReactions(); - return; - } - } - } - this.reactions.push({ - count:1, - emoji:data, - me:member.id===this.localuser.user.id - }); - this.updateReactions(); - } - reactionRemove(data:{name:string},id:string){ - console.log("test"); - for(const i in this.reactions){ - const thing=this.reactions[i]; - console.log(thing,data); - if(thing.emoji.name===data.name){ - thing.count--; - if(thing.count===0){ - this.reactions.splice(+i,1); - this.updateReactions(); - return; - } - if(id===this.localuser.user.id){ - thing.me=false; - this.updateReactions(); - return; - } - } - } - } - reactionRemoveAll() { - this.reactions = []; - this.updateReactions(); - } - reactionRemoveEmoji(emoji:Emoji) { - for (const i in this.reactions) { - const reaction = this.reactions[i]; - if ((reaction.emoji.id && reaction.emoji.id == emoji.id) || (!reaction.emoji.id && reaction.emoji.name == emoji.name)) { - this.reactions.splice(+i, 1); - this.updateReactions(); - break; - } - } - } - buildhtml(premessage:Message|undefined=undefined){ - if(this.div){console.error(`HTML for ${this.snowflake} already exists, aborting`);return;} - try{ - const div=document.createElement("div"); - this.div=div; - this.messageevents(div); - return this.generateMessage(premessage); - }catch(e){ - console.error(e); - } - } + reaction.onclick=_=>{ + this.reactionToggle(thing.emoji.name); + }; + } + func(); + } + reactionAdd(data:{name:string},member:Member|{id:string}){ + for(const thing of this.reactions){ + if(thing.emoji.name===data.name){ + thing.count++; + if(member.id===this.localuser.user.id){ + thing.me=true; + this.updateReactions(); + return; + } + } + } + this.reactions.push({ + count: 1, + emoji: data, + me: member.id===this.localuser.user.id + }); + this.updateReactions(); + } + reactionRemove(data:{name:string},id:string){ + console.log("test"); + for(const i in this.reactions){ + const thing=this.reactions[i]; + console.log(thing,data); + if(thing.emoji.name===data.name){ + thing.count--; + if(thing.count===0){ + this.reactions.splice(Number(i),1); + this.updateReactions(); + return; + } + if(id===this.localuser.user.id){ + thing.me=false; + this.updateReactions(); + return; + } + } + } + } + reactionRemoveAll(){ + this.reactions = []; + this.updateReactions(); + } + reactionRemoveEmoji(emoji:Emoji){ + for(const i in this.reactions){ + const reaction = this.reactions[i]; + if((reaction.emoji.id && reaction.emoji.id == emoji.id) || (!reaction.emoji.id && reaction.emoji.name == emoji.name)){ + this.reactions.splice(Number(i), 1); + this.updateReactions(); + break; + } + } + } + buildhtml(premessage?:Message|undefined):HTMLElement{ + if(this.div){ + console.error(`HTML for ${this.snowflake} already exists, aborting`);return this.div; + } + try{ + const div=document.createElement("div"); + this.div=div; + this.messageevents(div); + return this.generateMessage(premessage) as HTMLElement; + }catch(e){ + console.error(e); + } + return this.div as HTMLElement; + } } -let now = new Date().toLocaleDateString(); +const now = new Date().toLocaleDateString(); const yesterday = new Date(now); yesterday.setDate(new Date().getDate() - 1); -let yesterdayStr=yesterday.toLocaleDateString(); -function formatTime(date:Date) { +const yesterdayStr=yesterday.toLocaleDateString(); +function formatTime(date:Date){ + const datestring=date.toLocaleDateString(); + const formatTime = (date:Date)=>date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); - const datestring=date.toLocaleDateString(); - const formatTime = (date:Date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - - if (datestring=== now) { - return `Today at ${formatTime(date)}`; - } else if (datestring===yesterdayStr) { - return `Yesterday at ${formatTime(date)}`; - } else { - return `${date.toLocaleDateString()} at ${formatTime(date)}`; - } + if(datestring=== now){ + return`Today at ${formatTime(date)}`; + }else if(datestring===yesterdayStr){ + return`Yesterday at ${formatTime(date)}`; + }else{ + return`${date.toLocaleDateString()} at ${formatTime(date)}`; + } } Message.setup(); -export { Message }; +export{ Message }; diff --git a/webpage/permissions.ts b/webpage/permissions.ts index 8ce18e9..a4856f7 100644 --- a/webpage/permissions.ts +++ b/webpage/permissions.ts @@ -1,323 +1,323 @@ class Permissions{ - allow:bigint; - deny:bigint; - readonly hasDeny:boolean; - constructor(allow:string,deny:string=""){ - this.hasDeny=!!deny; - try{ - this.allow = BigInt(allow); - this.deny = BigInt(deny); - }catch(e){ - this.allow = 0n; - this.deny = 0n; - console.error(`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`) - } - } - getPermissionbit(b:number,big:bigint) : boolean{ - return Boolean((big>>BigInt(b))&1n); - } - setPermissionbit(b:number,state:boolean,big:bigint) : bigint{ - const bit=1n<>BigInt(b))&1n); + } + setPermissionbit(b:number,state:boolean,big:bigint) : bigint{ + const bit=1n< - - - - Jank Client - - - - - - - -
-

Create an account


-
-
-
-

- -
-
-
- -
+ + + + Jank Client + + + + + + + +
+

Create an account


+ +
+
+

+ +
+
+
+ +
-
-
- -
-
-
- -
+
+
+ +
+
+
+ +
-
-
- -
+
+
+ +
-
-
- -
+
+
+ +
-
- I agree to the Terms of Service: - -
+
+ I agree to the Terms of Service: + +
-

-
+

+
-
- - -Already have an account? -
- - +
+ + + Already have an account? +
+ + diff --git a/webpage/register.ts b/webpage/register.ts index 0169a3b..c6a27a4 100644 --- a/webpage/register.ts +++ b/webpage/register.ts @@ -1,109 +1,110 @@ -import {checkInstance, adduser} from "./login.js"; +import{checkInstance, adduser}from"./login.js"; if(document.getElementById("register")){ -document.getElementById("register").addEventListener("submit", registertry); + document.getElementById("register").addEventListener("submit", registertry); } async function registertry(e){ + e.preventDefault(); + const elements=e.srcElement; + const email=elements[1].value; + const username=elements[2].value; + if(elements[3].value!==elements[4].value){ + document.getElementById("wrong").textContent="Passwords don't match"; + return; + } + const password=elements[3].value; + const dateofbirth=elements[5].value; + const apiurl=new URL(JSON.parse(localStorage.getItem("instanceinfo")).api); - e.preventDefault(); - const elements=e.srcElement; - const email=elements[1].value; - const username=elements[2].value; - if(elements[3].value!==elements[4].value){ - document.getElementById("wrong").textContent="Passwords don't match"; - return; - } - const password=elements[3].value; - const dateofbirth=elements[5].value; - const apiurl=new URL(JSON.parse(localStorage.getItem("instanceinfo")).api) + await fetch(apiurl+"/auth/register",{ + body: JSON.stringify({ + date_of_birth: dateofbirth, + email, + username, + password, + consent: elements[6].checked, + captcha_key: elements[7]?.value + }), + headers: { + "content-type": "application/json" + }, + method: "POST" + }).then(e=>{ + e.json().then(e=>{ + if(e.captcha_sitekey){ + const capt=document.getElementById("h-captcha"); + if(!capt.children.length){ + const capty=document.createElement("div"); + capty.classList.add("h-captcha"); - await fetch(apiurl+"/auth/register",{ - body:JSON.stringify({ - date_of_birth:dateofbirth, - email:email, - username:username, - password:password, - consent:elements[6].checked, - captcha_key:elements[7]?.value - }), - headers:{ - "content-type": "application/json" - }, - method:"POST" - }).then(e=>{ - e.json().then(e=>{ - if(e.captcha_sitekey){ - const capt=document.getElementById("h-captcha"); - if(!capt.children.length){ - const capty=document.createElement("div"); - capty.classList.add("h-captcha"); - - capty.setAttribute("data-sitekey", e.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(!e.token){ - console.log(e); - if(e.errors.consent){ - error(elements[6],e.errors.consent._errors[0].message); - }else if(e.errors.password){ - error(elements[3],"Password: "+e.errors.password._errors[0].message); - }else if(e.errors.username){ - error(elements[2],"Username: "+e.errors.username._errors[0].message); - }else if(e.errors.email){ - error(elements[1],"Email: "+e.errors.email._errors[0].message); - }else if(e.errors.date_of_birth){ - error(elements[5],"Date of Birth: "+e.errors.date_of_birth._errors[0].message); - }else{ - document.getElementById("wrong").textContent=e.errors[Object.keys(e.errors)[0]]._errors[0].message; - } - }else{ - adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:email,token:e.token}).username=username; - localStorage.setItem("token",e.token); - const redir=new URLSearchParams(window.location.search).get("goback"); - if(redir){ - window.location.href = redir; - }else{ - window.location.href = '/channels/@me'; - } - } - }) - }) - //document.getElementById("wrong").textContent=h; - // console.log(h); + capty.setAttribute("data-sitekey", e.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(!e.token){ + console.log(e); + if(e.errors.consent){ + error(elements[6],e.errors.consent._errors[0].message); + }else if(e.errors.password){ + error(elements[3],"Password: "+e.errors.password._errors[0].message); + }else if(e.errors.username){ + error(elements[2],"Username: "+e.errors.username._errors[0].message); + }else if(e.errors.email){ + error(elements[1],"Email: "+e.errors.email._errors[0].message); + }else if(e.errors.date_of_birth){ + error(elements[5],"Date of Birth: "+e.errors.date_of_birth._errors[0].message); + }else{ + document.getElementById("wrong").textContent=e.errors[Object.keys(e.errors)[0]]._errors[0].message; + } + }else{ + adduser({serverurls: JSON.parse(localStorage.getItem("instanceinfo")),email,token: e.token}).username=username; + localStorage.setItem("token",e.token); + const redir=new URLSearchParams(window.location.search).get("goback"); + if(redir){ + window.location.href = redir; + }else{ + window.location.href = "/channels/@me"; + } + } + }); + }); + //document.getElementById("wrong").textContent=h; + // console.log(h); } function error(e:HTMLFormElement,message:string){ - const p=e.parentElement; - let element=p.getElementsByClassName("suberror")[0] as HTMLElement; - if(!element){ - const div=document.createElement("div"); - div.classList.add("suberror","suberrora"); - p.append(div); - element=div; - }else{ - element.classList.remove("suberror"); - setTimeout(_=>{element.classList.add("suberror")},100); - } - element.textContent=message; + const p=e.parentElement; + let element=p.getElementsByClassName("suberror")[0] as HTMLElement; + if(!element){ + const div=document.createElement("div"); + div.classList.add("suberror","suberrora"); + p.append(div); + element=div; + }else{ + element.classList.remove("suberror"); + setTimeout(_=>{ + element.classList.add("suberror"); + },100); + } + element.textContent=message; } let TOSa=document.getElementById("TOSa"); async function tosLogic(){ - const apiurl=new URL(JSON.parse(localStorage.getItem("instanceinfo")).api) - const tosPage=(await (await fetch(apiurl.toString()+"/ping")).json()).instance.tosPage; - if(tosPage){ - document.getElementById("TOSbox").innerHTML="I agree to the Terms of Service:"; - TOSa=document.getElementById("TOSa"); - (TOSa as HTMLAnchorElement).href=tosPage; - }else{ - document.getElementById("TOSbox").textContent="This instance has no Terms of Service, accept ToS anyways:"; - TOSa=null; - } - console.log(tosPage); + const apiurl=new URL(JSON.parse(localStorage.getItem("instanceinfo")).api); + const tosPage=(await (await fetch(apiurl.toString()+"/ping")).json()).instance.tosPage; + if(tosPage){ + document.getElementById("TOSbox").innerHTML="I agree to the Terms of Service:"; + TOSa=document.getElementById("TOSa"); + (TOSa as HTMLAnchorElement).href=tosPage; + }else{ + document.getElementById("TOSbox").textContent="This instance has no Terms of Service, accept ToS anyways:"; + TOSa=null; + } + console.log(tosPage); } tosLogic(); diff --git a/webpage/role.ts b/webpage/role.ts index edb87e6..13f4609 100644 --- a/webpage/role.ts +++ b/webpage/role.ts @@ -1,162 +1,170 @@ -import {Permissions} from "./permissions.js"; -import {Localuser} from "./localuser.js"; -import {Guild} from "./guild.js"; -import { SnowFlake } from "./snowflake.js"; -import { rolesjson } from "./jsontypes.js"; +import{Permissions}from"./permissions.js"; +import{Localuser}from"./localuser.js"; +import{Guild}from"./guild.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ rolesjson }from"./jsontypes.js"; class Role{ - permissions:Permissions; - owner:Guild; - color:number; - readonly snowflake:SnowFlake; - name:string; - info:Guild["info"]; - hoist:boolean; - icon:string; - mentionable:boolean; - unicode_emoji:string; - headers:Guild["headers"]; - get id(){ - return this.snowflake.id; - } - constructor(json:rolesjson, owner:Guild){ - this.headers=owner.headers; - this.info=owner.info; - for(const thing of Object.keys(json)){ - if(thing==="id"){ - this.snowflake=new SnowFlake(json.id,this); - continue; - } - this[thing]=json[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)}`; - } + permissions:Permissions; + owner:Guild; + color:number; + readonly snowflake:SnowFlake; + name:string; + info:Guild["info"]; + hoist:boolean; + icon:string; + mentionable:boolean; + unicode_emoji:string; + headers:Guild["headers"]; + get id(){ + return this.snowflake.id; + } + constructor(json:rolesjson, owner:Guild){ + this.headers=owner.headers; + this.info=owner.info; + for(const thing of Object.keys(json)){ + if(thing==="id"){ + this.snowflake=new SnowFlake(json.id,this); + continue; + } + this[thing]=json[thing]; + } + this.permissions=new Permissions(json.permissions); + this.owner=owner; + } + get guild():Guild{ + return this.owner; + } + get localuser():Localuser{ + return this.guild.localuser; + } + getColor():string|null{ + if(this.color===0){ + return null; + } + return`#${this.color.toString(16)}`; + } } -export {Role}; -import {Options} from "./settings.js"; +export{Role}; +import{Options}from"./settings.js"; class PermissionToggle implements OptionsElement{ - readonly rolejson:{name:string,readableName:string,description:string}; - permissions:Permissions; - owner:Options; - value:number; - constructor(roleJSON:PermissionToggle["rolejson"],permissions:Permissions,owner:Options){ - this.rolejson=roleJSON; - this.permissions=permissions; - this.owner=owner; - } - watchForChange(){}; - generateHTML():HTMLElement{ - const div=document.createElement("div"); - div.classList.add("setting"); - const name=document.createElement("span"); - name.textContent=this.rolejson.readableName; - name.classList.add("settingsname"); - div.append(name); + readonly rolejson:{name:string,readableName:string,description:string}; + permissions:Permissions; + owner:Options; + value:number; + constructor(roleJSON:PermissionToggle["rolejson"],permissions:Permissions,owner:Options){ + this.rolejson=roleJSON; + this.permissions=permissions; + this.owner=owner; + } + watchForChange(){} + generateHTML():HTMLElement{ + const div=document.createElement("div"); + div.classList.add("setting"); + const name=document.createElement("span"); + name.textContent=this.rolejson.readableName; + name.classList.add("settingsname"); + div.append(name); - div.append(this.generateCheckbox()); - const p=document.createElement("p"); - p.textContent=this.rolejson.description; - div.appendChild(p); - return div; - } - generateCheckbox():HTMLElement{ - const div=document.createElement("div"); - div.classList.add("tritoggle"); - const state=this.permissions.getPermission(this.rolejson.name); + div.append(this.generateCheckbox()); + const p=document.createElement("p"); + p.textContent=this.rolejson.description; + div.appendChild(p); + return div; + } + generateCheckbox():HTMLElement{ + const div=document.createElement("div"); + div.classList.add("tritoggle"); + const state=this.permissions.getPermission(this.rolejson.name); - const on=document.createElement("input"); - on.type="radio"; - on.name=this.rolejson.name; - div.append(on); - if(state===1){on.checked=true;}; - on.onclick=_=>{ - this.permissions.setPermission(this.rolejson.name,1); - this.owner.changed(); - } + const on=document.createElement("input"); + on.type="radio"; + on.name=this.rolejson.name; + div.append(on); + if(state===1){ + on.checked=true; + } + on.onclick=_=>{ + this.permissions.setPermission(this.rolejson.name,1); + this.owner.changed(); + }; - const no=document.createElement("input"); - no.type="radio"; - no.name=this.rolejson.name; - div.append(no); - if(state===0){no.checked=true;}; - no.onclick=_=>{ - this.permissions.setPermission(this.rolejson.name,0); - this.owner.changed(); - } - if(this.permissions.hasDeny){ - const off=document.createElement("input"); - off.type="radio"; - off.name=this.rolejson.name; - div.append(off); - if(state===-1){off.checked=true;}; - off.onclick=_=>{ - this.permissions.setPermission(this.rolejson.name,-1); - this.owner.changed(); - } - } - return div; - } - submit(){ + const no=document.createElement("input"); + no.type="radio"; + no.name=this.rolejson.name; + div.append(no); + if(state===0){ + no.checked=true; + } + no.onclick=_=>{ + this.permissions.setPermission(this.rolejson.name,0); + this.owner.changed(); + }; + if(this.permissions.hasDeny){ + const off=document.createElement("input"); + off.type="radio"; + off.name=this.rolejson.name; + div.append(off); + if(state===-1){ + off.checked=true; + } + off.onclick=_=>{ + this.permissions.setPermission(this.rolejson.name,-1); + this.owner.changed(); + }; + } + return div; + } + submit(){ - } + } } -import { OptionsElement,Buttons } from "./settings.js"; +import{ OptionsElement,Buttons }from"./settings.js"; class RoleList extends Buttons{ - readonly permissions:[SnowFlake,Permissions][]; - permission:Permissions; - readonly guild:Guild; - readonly channel:boolean; - readonly declare buttons:[string,string][]; - readonly options:Options; - onchange:Function; - curid:string; - constructor(permissions:[SnowFlake,Permissions][],guild:Guild,onchange:Function,channel=false){ - super("Roles"); - this.guild=guild; - this.permissions=permissions; - this.channel=channel; - this.onchange=onchange; - const options=new Options("",this); - if(channel){ - this.permission=new Permissions("0","0"); - }else{ - this.permission=new Permissions("0"); - } - for(const thing of Permissions.info){ - options.options.push(new PermissionToggle(thing,this.permission,options)); - } - for(const i of permissions){ - console.log(i); - this.buttons.push([i[0].getObject().name,i[0].id])// - } - this.options=options; - } - handleString(str:string):HTMLElement{ - this.curid=str; - const arr=this.permissions.find(_=>_[0].id===str); - if(arr){ - const perm=arr[1]; - this.permission.deny=perm.deny; - this.permission.allow=perm.allow; - this.options.name=SnowFlake.getSnowFlakeFromID(str,Role).getObject().name; - this.options.haschanged=false; - } - return this.options.generateHTML(); - } - save(){ - this.onchange(this.curid,this.permission); - } + readonly permissions:[SnowFlake,Permissions][]; + permission:Permissions; + readonly guild:Guild; + readonly channel:boolean; + readonly declare buttons:[string,string][]; + readonly options:Options; + onchange:Function; + curid:string; + constructor(permissions:[SnowFlake,Permissions][],guild:Guild,onchange:Function,channel=false){ + super("Roles"); + this.guild=guild; + this.permissions=permissions; + this.channel=channel; + this.onchange=onchange; + const options=new Options("",this); + if(channel){ + this.permission=new Permissions("0","0"); + }else{ + this.permission=new Permissions("0"); + } + for(const thing of Permissions.info){ + options.options.push(new PermissionToggle(thing,this.permission,options)); + } + for(const i of permissions){ + console.log(i); + this.buttons.push([i[0].getObject().name,i[0].id]);// + } + this.options=options; + } + handleString(str:string):HTMLElement{ + this.curid=str; + const arr=this.permissions.find(_=>_[0].id===str); + if(arr){ + const perm=arr[1]; + this.permission.deny=perm.deny; + this.permission.allow=perm.allow; + this.options.name=SnowFlake.getSnowFlakeFromID(str,Role).getObject().name; + this.options.haschanged=false; + } + return this.options.generateHTML(); + } + save(){ + this.onchange(this.curid,this.permission); + } } -export {RoleList} +export{RoleList}; diff --git a/webpage/service.ts b/webpage/service.ts index 877ec77..2f274aa 100644 --- a/webpage/service.ts +++ b/webpage/service.ts @@ -1,87 +1,93 @@ function deleteoldcache(){ - caches.delete("cache"); - console.log("this ran :P") + caches.delete("cache"); + console.log("this ran :P"); } async function putInCache(request, response){ - console.log(request,response); - const cache = await caches.open('cache'); - console.log("Grabbed") - try{ - console.log(await cache.put(request, response)); - }catch(error){ - console.error(error); - } -}; + console.log(request,response); + const cache = await caches.open("cache"); + console.log("Grabbed"); + try{ + console.log(await cache.put(request, response)); + }catch(error){ + console.error(error); + } +} console.log("test"); -let lastcache -self.addEventListener("activate", async (event) => { - console.log("test2"); - checkCache(); -}) +let lastcache; +self.addEventListener("activate", async event=>{ + console.log("test2"); + checkCache(); +}); async function checkCache(){ - if(checkedrecently){ - return; - } - const promise=await caches.match("/getupdates"); - if(promise){ - lastcache= await promise.text(); - } - console.log(lastcache); - fetch("/getupdates").then(async data=>{ - const text=await data.clone().text(); - console.log(text,lastcache) - if(lastcache!==text){ - deleteoldcache(); - putInCache("/getupdates",data.clone()); - } - checkedrecently=true; - setTimeout(_=>{checkedrecently=false},1000*60*30); - }) + 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(_=>{ + checkedrecently=false; + },1000*60*30); + }); } var checkedrecently=false; function samedomain(url){ - return new URL(url).origin===self.origin; + return new URL(url).origin===self.origin; } function isindexhtml(url){ - console.log(url); - if(new URL(url).pathname.startsWith("/channels")){ - return true; - } - return false; + console.log(url); + if(new URL(url).pathname.startsWith("/channels")){ + return true; + } + return false; } async function getfile(event){ - checkCache(); - if(!samedomain(event.request.url)){ - return await fetch(event.request.clone()); - } - const responseFromCache = await caches.match(event.request.url); - console.log(responseFromCache,caches); - if (responseFromCache) { - console.log("cache hit") - return responseFromCache; - } - if(isindexhtml(event.request.url)){ - console.log("is index.html") - const responseFromCache = await caches.match("/index.html"); - if (responseFromCache) { - console.log("cache hit") - return responseFromCache; - } - const responseFromNetwork = await fetch("/index.html"); - await putInCache("/index.html",responseFromNetwork.clone()); - return responseFromNetwork; - } - const responseFromNetwork = await fetch(event.request.clone()); - console.log(event.request.clone()); - await putInCache(event.request.clone(),responseFromNetwork.clone()); - try{ - return responseFromNetwork; - }catch(e){console.error(e)} + checkCache(); + if(!samedomain(event.request.url)){ + return await fetch(event.request.clone()); + } + const responseFromCache = await caches.match(event.request.url); + console.log(responseFromCache,caches); + if(responseFromCache){ + console.log("cache hit"); + return responseFromCache; + } + if(isindexhtml(event.request.url)){ + console.log("is index.html"); + const responseFromCache = await caches.match("/index.html"); + if(responseFromCache){ + console.log("cache hit"); + return responseFromCache; + } + const responseFromNetwork = await fetch("/index.html"); + await putInCache("/index.html",responseFromNetwork.clone()); + return responseFromNetwork; + } + const responseFromNetwork = await fetch(event.request.clone()); + console.log(event.request.clone()); + await putInCache(event.request.clone(),responseFromNetwork.clone()); + try{ + return responseFromNetwork; + }catch(e){ + console.error(e); + } } -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); + } +}); diff --git a/webpage/settings.ts b/webpage/settings.ts index 7f98425..90969eb 100644 --- a/webpage/settings.ts +++ b/webpage/settings.ts @@ -7,896 +7,905 @@ interface OptionsElement {// } //future me stuff class Buttons implements OptionsElement{ - readonly name:string; - readonly buttons:[string,Options|string][]; - buttonList:HTMLDivElement; - warndiv:HTMLElement; - value:unknown; - constructor(name:string){ - this.buttons=[]; - this.name=name; - } - add(name:string,thing:Options|undefined=undefined){ - if(!thing){thing=new Options(name,this)} - this.buttons.push([name,thing]); - return thing; - } - generateHTML(){ - const buttonList=document.createElement("div"); - buttonList.classList.add("Buttons"); - buttonList.classList.add("flexltr"); - this.buttonList=buttonList; - const htmlarea=document.createElement("div"); - htmlarea.classList.add("flexgrow"); - const buttonTable=document.createElement("div"); - buttonTable.classList.add("flexttb","settingbuttons"); - for(const thing of this.buttons){ - const button=document.createElement("button"); - button.classList.add("SettingsButton"); - button.textContent=thing[0]; - button.onclick=_=>{ - this.generateHTMLArea(thing[1],htmlarea); - if(this.warndiv){ - this.warndiv.remove(); - } - } - buttonTable.append(button); - } - this.generateHTMLArea(this.buttons[0][1],htmlarea); - buttonList.append(buttonTable); - buttonList.append(htmlarea); - return buttonList; - } - handleString(str:string):HTMLElement{ - const div=document.createElement("span"); - div.textContent=str; - return div; - } - private generateHTMLArea(buttonInfo:Options|string,htmlarea:HTMLElement){ - let html:HTMLElement; - if(buttonInfo instanceof Options){ - buttonInfo.subOptions=undefined; - html=buttonInfo.generateHTML(); - }else{ - html=this.handleString(buttonInfo); - } - htmlarea.innerHTML=""; - htmlarea.append(html); - } - changed(html:HTMLElement){ - this.warndiv=html; - this.buttonList.append(html); - } - watchForChange(){} - save(){} - submit(){ + readonly name:string; + readonly buttons:[string,Options|string][]; + buttonList:HTMLDivElement; + warndiv:HTMLElement; + value:unknown; + constructor(name:string){ + this.buttons=[]; + this.name=name; + } + add(name:string,thing?:Options|undefined){ + if(!thing){ + thing=new Options(name,this); + } + this.buttons.push([name,thing]); + return thing; + } + generateHTML(){ + const buttonList=document.createElement("div"); + buttonList.classList.add("Buttons"); + buttonList.classList.add("flexltr"); + this.buttonList=buttonList; + const htmlarea=document.createElement("div"); + htmlarea.classList.add("flexgrow"); + const buttonTable=document.createElement("div"); + buttonTable.classList.add("flexttb","settingbuttons"); + for(const thing of this.buttons){ + const button=document.createElement("button"); + button.classList.add("SettingsButton"); + button.textContent=thing[0]; + button.onclick=_=>{ + this.generateHTMLArea(thing[1],htmlarea); + if(this.warndiv){ + this.warndiv.remove(); + } + }; + buttonTable.append(button); + } + this.generateHTMLArea(this.buttons[0][1],htmlarea); + buttonList.append(buttonTable); + buttonList.append(htmlarea); + return buttonList; + } + handleString(str:string):HTMLElement{ + const div=document.createElement("span"); + div.textContent=str; + return div; + } + private generateHTMLArea(buttonInfo:Options|string,htmlarea:HTMLElement){ + let html:HTMLElement; + if(buttonInfo instanceof Options){ + buttonInfo.subOptions=undefined; + html=buttonInfo.generateHTML(); + }else{ + html=this.handleString(buttonInfo); + } + htmlarea.innerHTML=""; + htmlarea.append(html); + } + changed(html:HTMLElement){ + this.warndiv=html; + this.buttonList.append(html); + } + watchForChange(){} + save(){} + submit(){ - } + } } class TextInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onSubmit:(str:string)=>void; - value:string; - input:WeakRef; - password:boolean; - constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText="",password=false}={}){ - this.label=label; - this.value=initText; - this.owner=owner; - this.onSubmit=onSubmit; - this.password=password; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - const input=document.createElement("input"); - input.value=this.value; - input.type=this.password?"password":"text"; - input.oninput=this.onChange.bind(this); - this.input=new WeakRef(input); - div.append(input); - return div; - } - private onChange(ev:Event){ - this.owner.changed(); - const input=this.input.deref(); - if(input){ - const value=input.value as string; - this.onchange(value); - this.value=value; - } - } - onchange:(str:string)=>void=_=>{}; - watchForChange(func:(str:string)=>void){ - this.onchange=func; - } - submit(){ - this.onSubmit(this.value); - } + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:string)=>void; + value:string; + input:WeakRef; + password:boolean; + constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText="",password=false}={}){ + this.label=label; + this.value=initText; + this.owner=owner; + this.onSubmit=onSubmit; + this.password=password; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const input=document.createElement("input"); + input.value=this.value; + input.type=this.password?"password":"text"; + input.oninput=this.onChange.bind(this); + this.input=new WeakRef(input); + div.append(input); + return div; + } + private onChange(ev:Event){ + this.owner.changed(); + const input=this.input.deref(); + if(input){ + const value=input.value as string; + this.onchange(value); + this.value=value; + } + } + onchange:(str:string)=>void=_=>{}; + watchForChange(func:(str:string)=>void){ + this.onchange=func; + } + submit(){ + this.onSubmit(this.value); + } } class SettingsText implements OptionsElement{ - readonly onSubmit:(str:string)=>void; - value:void; - readonly text:string - constructor(text:string){ - this.text=text; - } - generateHTML():HTMLSpanElement{ - const span=document.createElement("span"); - span.innerText=this.text; - return span; - } - watchForChange(){} - submit(){} + readonly onSubmit:(str:string)=>void; + value:void; + readonly text:string; + constructor(text:string){ + this.text=text; + } + generateHTML():HTMLSpanElement{ + const span=document.createElement("span"); + span.innerText=this.text; + return span; + } + watchForChange(){} + submit(){} } class SettingsTitle implements OptionsElement{ - readonly onSubmit:(str:string)=>void; - value:void; - readonly text:string - constructor(text:string){ - this.text=text; - } - generateHTML():HTMLSpanElement{ - const span=document.createElement("h2"); - span.innerText=this.text; - return span; - } - watchForChange(){} - submit(){} + readonly onSubmit:(str:string)=>void; + value:void; + readonly text:string; + constructor(text:string){ + this.text=text; + } + generateHTML():HTMLSpanElement{ + const span=document.createElement("h2"); + span.innerText=this.text; + return span; + } + watchForChange(){} + submit(){} } class CheckboxInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onSubmit:(str:boolean)=>void; - value:boolean; - input:WeakRef; - constructor(label:string,onSubmit:(str:boolean)=>void,owner:Options,{initState=false}={}){ - this.label=label; - this.value=initState; - this.owner=owner; - this.onSubmit=onSubmit; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - const input=document.createElement("input"); - input.type="checkbox"; - input.checked=this.value; - input.oninput=this.onChange.bind(this); - this.input=new WeakRef(input); - div.append(input); - return div; - } - private onChange(ev:Event){ - this.owner.changed(); - const input=this.input.deref(); - if(input){ - const value=input.checked as boolean; - this.onchange(value); - this.value=value; - } - } - onchange:(str:boolean)=>void=_=>{}; - watchForChange(func:(str:boolean)=>void){ - this.onchange=func; - } - submit(){ - this.onSubmit(this.value); - } + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:boolean)=>void; + value:boolean; + input:WeakRef; + constructor(label:string,onSubmit:(str:boolean)=>void,owner:Options,{initState=false}={}){ + this.label=label; + this.value=initState; + this.owner=owner; + this.onSubmit=onSubmit; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const input=document.createElement("input"); + input.type="checkbox"; + input.checked=this.value; + input.oninput=this.onChange.bind(this); + this.input=new WeakRef(input); + div.append(input); + return div; + } + private onChange(ev:Event){ + this.owner.changed(); + const input=this.input.deref(); + if(input){ + const value=input.checked as boolean; + this.onchange(value); + this.value=value; + } + } + onchange:(str:boolean)=>void=_=>{}; + watchForChange(func:(str:boolean)=>void){ + this.onchange=func; + } + submit(){ + this.onSubmit(this.value); + } } class ButtonInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onClick:()=>void; - textContent:string; - value: void; - constructor(label:string,textContent:string,onClick:()=>void,owner:Options,{}={}){ - this.label=label; - this.owner=owner; - this.onClick=onClick; - this.textContent=textContent; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - const button=document.createElement("button"); - button.textContent=this.textContent; - button.onclick=this.onClickEvent.bind(this); - div.append(button); - return div; - } - private onClickEvent(ev:Event){ - this.onClick(); - } - watchForChange(){} - submit(){} + readonly label:string; + readonly owner:Options; + readonly onClick:()=>void; + textContent:string; + value: void; + constructor(label:string,textContent:string,onClick:()=>void,owner:Options,{}={}){ + this.label=label; + this.owner=owner; + this.onClick=onClick; + this.textContent=textContent; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const button=document.createElement("button"); + button.textContent=this.textContent; + button.onclick=this.onClickEvent.bind(this); + div.append(button); + return div; + } + private onClickEvent(ev:Event){ + this.onClick(); + } + watchForChange(){} + submit(){} } class ColorInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onSubmit:(str:string)=>void; - colorContent:string; - input:WeakRef; - value: string; - constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initColor=""}={}){ - this.label=label; - this.colorContent=initColor; - this.owner=owner; - this.onSubmit=onSubmit; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - const input=document.createElement("input"); - input.value=this.colorContent; - input.type="color"; - input.oninput=this.onChange.bind(this); - this.input=new WeakRef(input); - div.append(input); - return div; - } - private onChange(ev:Event){ - this.owner.changed(); - const input=this.input.deref(); - if(input){ - const value=input.value as string; - this.value=value; - this.onchange(value); - this.colorContent=value; - } - } - onchange:(str:string)=>void=_=>{}; - watchForChange(func:(str:string)=>void){ - this.onchange=func; - } - submit(){ - this.onSubmit(this.colorContent); - } + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:string)=>void; + colorContent:string; + input:WeakRef; + value: string; + constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initColor=""}={}){ + this.label=label; + this.colorContent=initColor; + this.owner=owner; + this.onSubmit=onSubmit; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const input=document.createElement("input"); + input.value=this.colorContent; + input.type="color"; + input.oninput=this.onChange.bind(this); + this.input=new WeakRef(input); + div.append(input); + return div; + } + private onChange(ev:Event){ + this.owner.changed(); + const input=this.input.deref(); + if(input){ + const value=input.value as string; + this.value=value; + this.onchange(value); + this.colorContent=value; + } + } + onchange:(str:string)=>void=_=>{}; + watchForChange(func:(str:string)=>void){ + this.onchange=func; + } + submit(){ + this.onSubmit(this.colorContent); + } } class SelectInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onSubmit:(str:number)=>void; - options:string[]; - index:number; - select:WeakRef - get value(){ - return this.index; - } - constructor(label:string,onSubmit:(str:number)=>void,options:string[],owner:Options,{defaultIndex=0}={}){ - this.label=label; - this.index=defaultIndex; - this.owner=owner; - this.onSubmit=onSubmit; - this.options=options; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - const select=document.createElement("select"); + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:number)=>void; + options:string[]; + index:number; + select:WeakRef; + get value(){ + return this.index; + } + constructor(label:string,onSubmit:(str:number)=>void,options:string[],owner:Options,{defaultIndex=0}={}){ + this.label=label; + this.index=defaultIndex; + this.owner=owner; + this.onSubmit=onSubmit; + this.options=options; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const select=document.createElement("select"); - select.onchange=this.onChange.bind(this); - for(const thing of this.options){ - const option = document.createElement("option"); - option.textContent=thing; - select.appendChild(option); - } - this.select=new WeakRef(select); - select.selectedIndex=this.index; - div.append(select); - return div; - } - private onChange(ev:Event){ - this.owner.changed(); - const select=this.select.deref(); - if(select){ - const value=select.selectedIndex; - this.onchange(value); - this.index=value; - } - } - onchange:(str:number)=>void=_=>{}; - watchForChange(func:(str:number)=>void){ - this.onchange=func; - } - submit(){ - this.onSubmit(this.index); - } + select.onchange=this.onChange.bind(this); + for(const thing of this.options){ + const option = document.createElement("option"); + option.textContent=thing; + select.appendChild(option); + } + this.select=new WeakRef(select); + select.selectedIndex=this.index; + div.append(select); + return div; + } + private onChange(ev:Event){ + this.owner.changed(); + const select=this.select.deref(); + if(select){ + const value=select.selectedIndex; + this.onchange(value); + this.index=value; + } + } + onchange:(str:number)=>void=_=>{}; + watchForChange(func:(str:number)=>void){ + this.onchange=func; + } + submit(){ + this.onSubmit(this.index); + } } class MDInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onSubmit:(str:string)=>void; - value:string; - input:WeakRef - constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText=""}={}){ - this.label=label; - this.value=initText; - this.owner=owner; - this.onSubmit=onSubmit; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - div.append(document.createElement("br")); - const input=document.createElement("textarea"); - input.value=this.value; - input.oninput=this.onChange.bind(this); - this.input=new WeakRef(input); - div.append(input); - return div; - } - onChange(ev:Event){ - this.owner.changed(); - const input=this.input.deref(); - if(input){ - const value=input.value as string; - this.onchange(value); - this.value=value; - } - } - onchange:(str:string)=>void=_=>{}; - watchForChange(func:(str:string)=>void){ - this.onchange=func; - } - submit(){ - this.onSubmit(this.value); - } + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:string)=>void; + value:string; + input:WeakRef; + constructor(label:string,onSubmit:(str:string)=>void,owner:Options,{initText=""}={}){ + this.label=label; + this.value=initText; + this.owner=owner; + this.onSubmit=onSubmit; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + div.append(document.createElement("br")); + const input=document.createElement("textarea"); + input.value=this.value; + input.oninput=this.onChange.bind(this); + this.input=new WeakRef(input); + div.append(input); + return div; + } + onChange(ev:Event){ + this.owner.changed(); + const input=this.input.deref(); + if(input){ + const value=input.value as string; + this.onchange(value); + this.value=value; + } + } + onchange:(str:string)=>void=_=>{}; + watchForChange(func:(str:string)=>void){ + this.onchange=func; + } + submit(){ + this.onSubmit(this.value); + } } class FileInput implements OptionsElement{ - readonly label:string; - readonly owner:Options; - readonly onSubmit:(str:FileList|null)=>void; - input:WeakRef - value:FileList|null; - clear:boolean; - constructor(label:string,onSubmit:(str:FileList)=>void,owner:Options,{clear=false}={}){ - this.label=label; - this.owner=owner; - this.onSubmit=onSubmit; - this.clear=clear; - } - generateHTML():HTMLDivElement{ - const div=document.createElement("div"); - const span=document.createElement("span"); - span.textContent=this.label; - div.append(span); - const input=document.createElement("input"); - input.type="file"; - input.oninput=this.onChange.bind(this); - this.input=new WeakRef(input); - div.append(input); - if(this.clear){ - const button=document.createElement("button"); - button.textContent="Clear"; - button.onclick=_=>{ - if(this.onchange){this.onchange(null)}; - this.value=null; - this.owner.changed(); - } - div.append(button); - } - return div; - } - onChange(ev:Event){ - this.owner.changed(); - const input=this.input.deref(); - if(this.onchange&&input){ - this.value=input.files; - this.onchange(input.files); - } - } - onchange:((str:FileList|null)=>void)|null=null; - watchForChange(func:(str:FileList|null)=>void){ - this.onchange=func; - } - submit(){ - const input=this.input.deref(); - if(input){ - this.onSubmit(input.files); - } - } + readonly label:string; + readonly owner:Options; + readonly onSubmit:(str:FileList|null)=>void; + input:WeakRef; + value:FileList|null; + clear:boolean; + constructor(label:string,onSubmit:(str:FileList)=>void,owner:Options,{clear=false}={}){ + this.label=label; + this.owner=owner; + this.onSubmit=onSubmit; + this.clear=clear; + } + generateHTML():HTMLDivElement{ + const div=document.createElement("div"); + const span=document.createElement("span"); + span.textContent=this.label; + div.append(span); + const input=document.createElement("input"); + input.type="file"; + input.oninput=this.onChange.bind(this); + this.input=new WeakRef(input); + div.append(input); + if(this.clear){ + const button=document.createElement("button"); + button.textContent="Clear"; + button.onclick=_=>{ + if(this.onchange){ + this.onchange(null); + } + this.value=null; + this.owner.changed(); + }; + div.append(button); + } + return div; + } + onChange(ev:Event){ + this.owner.changed(); + const input=this.input.deref(); + if(this.onchange&&input){ + this.value=input.files; + this.onchange(input.files); + } + } + onchange:((str:FileList|null)=>void)|null=null; + watchForChange(func:(str:FileList|null)=>void){ + this.onchange=func; + } + submit(){ + const input=this.input.deref(); + if(input){ + this.onSubmit(input.files); + } + } } class HtmlArea implements OptionsElement{ - submit: () => void; - html:(()=>HTMLElement)|HTMLElement; - value:void; - constructor(html:(()=>HTMLElement)|HTMLElement,submit:()=>void){ - this.submit=submit; - this.html=html; - } - generateHTML(): HTMLElement { - if(this.html instanceof Function){ - return this.html(); - }else{ - return this.html; - } - } - watchForChange(){}; + submit: () => void; + html:(()=>HTMLElement)|HTMLElement; + value:void; + constructor(html:(()=>HTMLElement)|HTMLElement,submit:()=>void){ + this.submit=submit; + this.html=html; + } + generateHTML(): HTMLElement{ + if(this.html instanceof Function){ + return this.html(); + }else{ + return this.html; + } + } + watchForChange(){} } class Options implements OptionsElement{ - name:string; - haschanged=false; - readonly options:OptionsElement[]; - readonly owner:Buttons|Options|Form; - readonly ltr:boolean; - value:void; - readonly html:WeakMap,WeakRef>=new WeakMap(); - container:WeakRef=new WeakRef(document.createElement("div")); - constructor(name:string,owner:Buttons|Options|Form,{ltr=false}={}){ - this.name=name; - this.options=[]; - this.owner=owner; - this.ltr=ltr; - } - removeAll(){ - while(this.options.length){ - this.options.pop(); - } - const container=this.container.deref(); - if(container){ - container.innerHTML=""; - } - } - watchForChange(){}; - addOptions(name:string,{ltr=false}={}){ - const options=new Options(name,this,{ltr}); - this.options.push(options); - this.generate(options); - return options; - } - subOptions:Options|Form|undefined; - addSubOptions(name:string,{ltr=false}={}){ - const options=new Options(name,this,{ltr}); - this.subOptions=options; - const container=this.container.deref(); - if(container){ - this.generateContainter(); - }else{ - throw Error("Tried to make a subOptions when the options weren't rendered"); - } - return options; - } - addSubForm(name:string,onSubmit:((arg1:object)=>void),{ltr=false,submitText="Submit",fetchURL="",headers={},method="POST",traditionalSubmit=false}={}){ - const options=new Form(name,this,onSubmit,{ltr,submitText,fetchURL,headers,method,traditionalSubmit}); - this.subOptions=options; - const container=this.container.deref(); - if(container){ - this.generateContainter(); - }else{ - throw Error("Tried to make a subForm when the options weren't rendered"); - } - return options; - } - returnFromSub(){ - this.subOptions=undefined; - this.generateContainter(); - } - addSelect(label:string,onSubmit:(str:number)=>void,selections:string[],{defaultIndex=0}={}){ - const select=new SelectInput(label,onSubmit,selections,this,{defaultIndex}); - this.options.push(select); - this.generate(select); - return select; - } - addFileInput(label:string,onSubmit:(files:FileList)=>void,{clear=false}={}){ - const FI=new FileInput(label,onSubmit,this,{clear}); - this.options.push(FI); - this.generate(FI); - return FI; - } - addTextInput(label:string,onSubmit:(str:string)=>void,{initText="",password=false}={}){ - const textInput=new TextInput(label,onSubmit,this,{initText,password}); - this.options.push(textInput); - this.generate(textInput); - return textInput; - } - addColorInput(label:string,onSubmit:(str:string)=>void,{initColor=""}={}){ - const colorInput=new ColorInput(label,onSubmit,this,{initColor}); - this.options.push(colorInput); - this.generate(colorInput); - return colorInput; - } - addMDInput(label:string,onSubmit:(str:string)=>void,{initText=""}={}){ - const mdInput=new MDInput(label,onSubmit,this,{initText}); - this.options.push(mdInput); - this.generate(mdInput); - return mdInput; - } - addHTMLArea(html:(()=>HTMLElement)|HTMLElement,submit:()=>void=()=>{}){ - const htmlarea=new HtmlArea(html,submit); - this.options.push(htmlarea); - this.generate(htmlarea); - return htmlarea; - } - addButtonInput(label:string,textContent:string,onSubmit:()=>void){ - const button=new ButtonInput(label,textContent,onSubmit,this); - this.options.push(button); - this.generate(button); - return button; - } - addCheckboxInput(label:string,onSubmit:(str:boolean)=>void,{initState=false}={}){ - const box=new CheckboxInput(label,onSubmit,this,{initState}); - this.options.push(box); - this.generate(box); - return box; - } - addText(str:string){ - const text=new SettingsText(str); - this.options.push(text); - this.generate(text); - return text; - } - addTitle(str:string){ - const text=new SettingsTitle(str); - this.options.push(text); - this.generate(text); - return text; - } - generate(elm:OptionsElement){ - const container=this.container.deref(); - if(container){ - const div=document.createElement("div"); - if(!(elm instanceof Options)){ - div.classList.add("optionElement") - } - const html=elm.generateHTML(); - div.append(html); - this.html.set(elm,new WeakRef(div)); - container.append(div); - } - } - title:WeakRef=new WeakRef(document.createElement("h2")); - generateHTML():HTMLElement{ - const div=document.createElement("div"); - div.classList.add("titlediv"); - const title=document.createElement("h2"); - title.textContent=this.name; - div.append(title); - if(this.name!=="") title.classList.add("settingstitle"); - this.title=new WeakRef(title); - const container=document.createElement("div"); - this.container=new WeakRef(container); - container.classList.add(this.ltr?"flexltr":"flexttb","flexspace"); - this.generateContainter(); - div.append(container); - return div; - } - generateContainter(){ - const container=this.container.deref(); - if(container){ - const title=this.title.deref(); - if(title) title.innerHTML=""; - container.innerHTML=""; - if(this.subOptions){ - container.append(this.subOptions.generateHTML());//more code needed, though this is enough for now - if(title){ - const name=document.createElement("span"); - name.innerText=this.name; - name.classList.add("clickable"); - name.onclick=()=>{ - this.returnFromSub(); - } - title.append(name," > ",this.subOptions.name) - } - }else{ - for(const thing of this.options){ - this.generate(thing); - } - if(title){ - title.innerText=this.name; - } - } - if(title&&title.innerText!==""){ - title.classList.add("settingstitle"); - }else if(title){ - title.classList.remove("settingstitle"); - } + name:string; + haschanged=false; + readonly options:OptionsElement[]; + readonly owner:Buttons|Options|Form; + readonly ltr:boolean; + value:void; + readonly html:WeakMap,WeakRef>=new WeakMap(); + container:WeakRef=new WeakRef(document.createElement("div")); + constructor(name:string,owner:Buttons|Options|Form,{ltr=false}={}){ + this.name=name; + this.options=[]; + this.owner=owner; + this.ltr=ltr; + } + removeAll(){ + while(this.options.length){ + this.options.pop(); + } + const container=this.container.deref(); + if(container){ + container.innerHTML=""; + } + } + watchForChange(){} + addOptions(name:string,{ltr=false}={}){ + const options=new Options(name,this,{ltr}); + this.options.push(options); + this.generate(options); + return options; + } + subOptions:Options|Form|undefined; + addSubOptions(name:string,{ltr=false}={}){ + const options=new Options(name,this,{ltr}); + this.subOptions=options; + const container=this.container.deref(); + if(container){ + this.generateContainter(); + }else{ + throw new Error("Tried to make a subOptions when the options weren't rendered"); + } + return options; + } + addSubForm(name:string,onSubmit:((arg1:object)=>void),{ltr=false,submitText="Submit",fetchURL="",headers={},method="POST",traditionalSubmit=false}={}){ + const options=new Form(name,this,onSubmit,{ltr,submitText,fetchURL,headers,method,traditionalSubmit}); + this.subOptions=options; + const container=this.container.deref(); + if(container){ + this.generateContainter(); + }else{ + throw new Error("Tried to make a subForm when the options weren't rendered"); + } + return options; + } + returnFromSub(){ + this.subOptions=undefined; + this.generateContainter(); + } + addSelect(label:string,onSubmit:(str:number)=>void,selections:string[],{defaultIndex=0}={}){ + const select=new SelectInput(label,onSubmit,selections,this,{defaultIndex}); + this.options.push(select); + this.generate(select); + return select; + } + addFileInput(label:string,onSubmit:(files:FileList)=>void,{clear=false}={}){ + const FI=new FileInput(label,onSubmit,this,{clear}); + this.options.push(FI); + this.generate(FI); + return FI; + } + addTextInput(label:string,onSubmit:(str:string)=>void,{initText="",password=false}={}){ + const textInput=new TextInput(label,onSubmit,this,{initText,password}); + this.options.push(textInput); + this.generate(textInput); + return textInput; + } + addColorInput(label:string,onSubmit:(str:string)=>void,{initColor=""}={}){ + const colorInput=new ColorInput(label,onSubmit,this,{initColor}); + this.options.push(colorInput); + this.generate(colorInput); + return colorInput; + } + addMDInput(label:string,onSubmit:(str:string)=>void,{initText=""}={}){ + const mdInput=new MDInput(label,onSubmit,this,{initText}); + this.options.push(mdInput); + this.generate(mdInput); + return mdInput; + } + addHTMLArea(html:(()=>HTMLElement)|HTMLElement,submit:()=>void=()=>{}){ + const htmlarea=new HtmlArea(html,submit); + this.options.push(htmlarea); + this.generate(htmlarea); + return htmlarea; + } + addButtonInput(label:string,textContent:string,onSubmit:()=>void){ + const button=new ButtonInput(label,textContent,onSubmit,this); + this.options.push(button); + this.generate(button); + return button; + } + addCheckboxInput(label:string,onSubmit:(str:boolean)=>void,{initState=false}={}){ + const box=new CheckboxInput(label,onSubmit,this,{initState}); + this.options.push(box); + this.generate(box); + return box; + } + addText(str:string){ + const text=new SettingsText(str); + this.options.push(text); + this.generate(text); + return text; + } + addTitle(str:string){ + const text=new SettingsTitle(str); + this.options.push(text); + this.generate(text); + return text; + } + generate(elm:OptionsElement){ + const container=this.container.deref(); + if(container){ + const div=document.createElement("div"); + if(!(elm instanceof Options)){ + div.classList.add("optionElement"); + } + const html=elm.generateHTML(); + div.append(html); + this.html.set(elm,new WeakRef(div)); + container.append(div); + } + } + title:WeakRef=new WeakRef(document.createElement("h2")); + generateHTML():HTMLElement{ + const div=document.createElement("div"); + div.classList.add("titlediv"); + const title=document.createElement("h2"); + title.textContent=this.name; + div.append(title); + if(this.name!=="") title.classList.add("settingstitle"); + this.title=new WeakRef(title); + const container=document.createElement("div"); + this.container=new WeakRef(container); + container.classList.add(this.ltr?"flexltr":"flexttb","flexspace"); + this.generateContainter(); + div.append(container); + return div; + } + generateContainter(){ + const container=this.container.deref(); + if(container){ + const title=this.title.deref(); + if(title) title.innerHTML=""; + container.innerHTML=""; + if(this.subOptions){ + container.append(this.subOptions.generateHTML());//more code needed, though this is enough for now + if(title){ + const name=document.createElement("span"); + name.innerText=this.name; + name.classList.add("clickable"); + name.onclick=()=>{ + this.returnFromSub(); + }; + title.append(name," > ",this.subOptions.name); + } + }else{ + for(const thing of this.options){ + this.generate(thing); + } + if(title){ + title.innerText=this.name; + } + } + if(title&&title.innerText!==""){ + title.classList.add("settingstitle"); + }else if(title){ + title.classList.remove("settingstitle"); + } + }else{ + console.warn("tried to generate container, but it did not exist"); + } + } + changed(){ + if(this.owner instanceof Options||this.owner instanceof Form){ + this.owner.changed(); + return; + } + if(!this.haschanged){ + const div=document.createElement("div"); + div.classList.add("flexltr","savediv"); + const span=document.createElement("span"); + div.append(span); + span.textContent="Careful, you have unsaved changes"; + const button=document.createElement("button"); + button.textContent="Save changes"; + div.append(button); + this.haschanged=true; + this.owner.changed(div); - }else{ - console.warn("tried to generate container, but it did not exist"); - } - } - changed(){ - if(this.owner instanceof Options||this.owner instanceof Form){ - this.owner.changed(); - return; - } - if(!this.haschanged){ - const div=document.createElement("div"); - div.classList.add("flexltr","savediv"); - const span=document.createElement("span"); - div.append(span); - span.textContent="Careful, you have unsaved changes"; - const button=document.createElement("button"); - button.textContent="Save changes"; - div.append(button); - this.haschanged=true; - this.owner.changed(div); - - button.onclick=_=>{ - if(this.owner instanceof Buttons){ - this.owner.save(); - } - div.remove(); - this.submit(); - } - } - } - submit(){ - this.haschanged=false; - for(const thing of this.options){ - thing.submit(); - } - } + button.onclick=_=>{ + if(this.owner instanceof Buttons){ + this.owner.save(); + } + div.remove(); + this.submit(); + }; + } + } + submit(){ + this.haschanged=false; + for(const thing of this.options){ + thing.submit(); + } + } } class FormError extends Error{ - elem:OptionsElement; - message:string; - constructor(elem:OptionsElement,message:string){ - super(message); - this.message=message; - this.elem=elem; - } + elem:OptionsElement; + message:string; + constructor(elem:OptionsElement,message:string){ + super(message); + this.message=message; + this.elem=elem; + } } -export {FormError}; +export{FormError}; class Form implements OptionsElement{ - name:string; - readonly options:Options; - readonly owner:Options; - readonly ltr:boolean; - readonly names:Map>=new Map(); - readonly required:WeakSet>=new WeakSet(); - readonly submitText:string; - readonly fetchURL:string; - readonly headers={}; - readonly method:string; - value:object; - traditionalSubmit:boolean; - values={}; - constructor(name:string,owner:Options,onSubmit:((arg1:object)=>void),{ltr=false,submitText="Submit",fetchURL="",headers={},method="POST",traditionalSubmit=false}={}){ - this.traditionalSubmit=traditionalSubmit; - this.name=name; - this.method=method; - this.submitText=submitText; - this.options=new Options("",this,{ltr}); - this.owner=owner; - this.fetchURL=fetchURL; - this.headers=headers; - this.ltr=ltr; - this.onSubmit=onSubmit; - } - setValue(key:string,value:any){//the value can't really be anything, but I don't care enough to fix this - this.values[key]=value; - } - addSelect(label:string,formName:string,selections:string[],{defaultIndex=0,required=false}={}){ - const select=this.options.addSelect(label,_=>{},selections,{defaultIndex}); - this.names.set(formName,select); - if(required){ - this.required.add(select); - } - return select; - } - addFileInput(label:string,formName:string,{required=false}={}){ - const FI=this.options.addFileInput(label,_=>{},{}); - this.names.set(formName,FI); - if(required){ - this.required.add(FI); - } - return FI; - } + name:string; + readonly options:Options; + readonly owner:Options; + readonly ltr:boolean; + readonly names:Map>=new Map(); + readonly required:WeakSet>=new WeakSet(); + readonly submitText:string; + readonly fetchURL:string; + readonly headers={}; + readonly method:string; + value:object; + traditionalSubmit:boolean; + values={}; + constructor(name:string,owner:Options,onSubmit:((arg1:object)=>void),{ltr=false,submitText="Submit",fetchURL="",headers={},method="POST",traditionalSubmit=false}={}){ + this.traditionalSubmit=traditionalSubmit; + this.name=name; + this.method=method; + this.submitText=submitText; + this.options=new Options("",this,{ltr}); + this.owner=owner; + this.fetchURL=fetchURL; + this.headers=headers; + this.ltr=ltr; + this.onSubmit=onSubmit; + } + setValue(key:string,value:any){//the value can't really be anything, but I don't care enough to fix this + this.values[key]=value; + } + addSelect(label:string,formName:string,selections:string[],{defaultIndex=0,required=false}={}){ + const select=this.options.addSelect(label,_=>{},selections,{defaultIndex}); + this.names.set(formName,select); + if(required){ + this.required.add(select); + } + return select; + } + addFileInput(label:string,formName:string,{required=false}={}){ + const FI=this.options.addFileInput(label,_=>{},{}); + this.names.set(formName,FI); + if(required){ + this.required.add(FI); + } + return FI; + } - addTextInput(label:string,formName:string,{initText="",required=false,password=false}={}){ - const textInput=this.options.addTextInput(label,_=>{},{initText,password}); - this.names.set(formName,textInput); - if(required){ - this.required.add(textInput); - } - return textInput; - } - addColorInput(label:string,formName:string,{initColor="",required=false}={}){ - const colorInput=this.options.addColorInput(label,_=>{},{initColor}); - this.names.set(formName,colorInput); - if(required){ - this.required.add(colorInput); - } - return colorInput; - } + addTextInput(label:string,formName:string,{initText="",required=false,password=false}={}){ + const textInput=this.options.addTextInput(label,_=>{},{initText,password}); + this.names.set(formName,textInput); + if(required){ + this.required.add(textInput); + } + return textInput; + } + addColorInput(label:string,formName:string,{initColor="",required=false}={}){ + const colorInput=this.options.addColorInput(label,_=>{},{initColor}); + this.names.set(formName,colorInput); + if(required){ + this.required.add(colorInput); + } + return colorInput; + } - addMDInput(label:string,formName:string,{initText="",required=false}={}){ - const mdInput=this.options.addMDInput(label,_=>{},{initText}); - this.names.set(formName,mdInput); - if(required){ - this.required.add(mdInput); - } - return mdInput; - } + addMDInput(label:string,formName:string,{initText="",required=false}={}){ + const mdInput=this.options.addMDInput(label,_=>{},{initText}); + this.names.set(formName,mdInput); + if(required){ + this.required.add(mdInput); + } + return mdInput; + } - addCheckboxInput(label:string,formName:string,{initState=false,required=false}={}){ - const box=this.options.addCheckboxInput(label,_=>{},{initState}); - this.names.set(formName,box); - if(required){ - this.required.add(box); - } - return box; - } - addText(str:string){ - this.options.addText(str); - } - addTitle(str:string){ - this.options.addTitle(str); - } - generateHTML():HTMLElement{ - const div=document.createElement("div"); - div.append(this.options.generateHTML()); - div.classList.add("FormSettings"); - if(!this.traditionalSubmit){ - const button=document.createElement("button"); - button.onclick=_=>{ - this.submit(); - } - button.textContent=this.submitText; - div.append(button) - } - return div; - } - onSubmit:((arg1:object)=>void); - watchForChange(func:(arg1:object)=>void){ - this.onSubmit=func; - }; - changed(){ - if(this.traditionalSubmit){ - this.owner.changed(); - } - } - submit(){ - const build={}; - for(const key of Object.keys(this.values)){ - const thing=this.values[key]; - if(thing instanceof Function){ - try{ - build[key]=thing(); - }catch(e:any){ - if(e instanceof FormError){ - const elm=this.options.html.get(e.elem); - if(elm){ - const html=elm.deref(); - if(html){ - this.makeError(html,e.message); - } - } - } - return; - } - }else{ - build[key]=thing; - } - } - for(const thing of this.names.keys()){ - if(thing==="") continue; - const input=this.names.get(thing) as OptionsElement; - if(input instanceof SelectInput){ - build[thing]=input.options[input.value]; - continue; - } - build[thing]=input.value; - } - if(this.fetchURL!==""){ - fetch(this.fetchURL,{ - method:this.method, - body:JSON.stringify(build), - headers:this.headers - }).then(_=>_.json()).then(json=>{ - if(json.errors&&this.errors(json.errors)) return; - this.onSubmit(json); - }) - }else{ - this.onSubmit(build); - } - console.warn("needs to be implemented") - } - errors(errors:{code:number,message:string,errors:{[key:string]:{_errors:{message:string,code:string}}}}){ - if(!(errors instanceof Object)){return}; - for(const error of Object.keys(errors)){ - const elm=this.names.get(error); - if(elm){ - const ref=this.options.html.get(elm); - if(ref&&ref.deref()){ - const html=ref.deref() as HTMLDivElement; - this.makeError(html,errors[error]._errors[0].message) - return true; - } - } - } - return false; - } - error(formElm:string,errorMessage:string){ - const elm=this.names.get(formElm); - if(elm){ - const htmlref=this.options.html.get(elm); - if(htmlref){ - const html=htmlref.deref(); - if(html){ - this.makeError(html,errorMessage); - } - } - }else{ - console.warn(formElm+" is not a valid form property") - } - } - makeError(e:HTMLDivElement,message:string){ - let element=e.getElementsByClassName("suberror")[0] as HTMLElement; - if(!element){ - const div=document.createElement("div"); - div.classList.add("suberror","suberrora"); - e.append(div); - element=div; - }else{ - element.classList.remove("suberror"); - setTimeout(_=>{element.classList.add("suberror")},100); - } - element.textContent=message; - } + addCheckboxInput(label:string,formName:string,{initState=false,required=false}={}){ + const box=this.options.addCheckboxInput(label,_=>{},{initState}); + this.names.set(formName,box); + if(required){ + this.required.add(box); + } + return box; + } + addText(str:string){ + this.options.addText(str); + } + addTitle(str:string){ + this.options.addTitle(str); + } + generateHTML():HTMLElement{ + const div=document.createElement("div"); + div.append(this.options.generateHTML()); + div.classList.add("FormSettings"); + if(!this.traditionalSubmit){ + const button=document.createElement("button"); + button.onclick=_=>{ + this.submit(); + }; + button.textContent=this.submitText; + div.append(button); + } + return div; + } + onSubmit:((arg1:object)=>void); + watchForChange(func:(arg1:object)=>void){ + this.onSubmit=func; + } + changed(){ + if(this.traditionalSubmit){ + this.owner.changed(); + } + } + submit(){ + const build={}; + for(const key of Object.keys(this.values)){ + const thing=this.values[key]; + if(thing instanceof Function){ + try{ + build[key]=thing(); + }catch(e:any){ + if(e instanceof FormError){ + const elm=this.options.html.get(e.elem); + if(elm){ + const html=elm.deref(); + if(html){ + this.makeError(html,e.message); + } + } + } + return; + } + }else{ + build[key]=thing; + } + } + for(const thing of this.names.keys()){ + if(thing==="")continue; + const input=this.names.get(thing) as OptionsElement; + if(input instanceof SelectInput){ + build[thing]=input.options[input.value]; + continue; + } + build[thing]=input.value; + } + if(this.fetchURL!==""){ + fetch(this.fetchURL,{ + method: this.method, + body: JSON.stringify(build), + headers: this.headers + }).then(_=>_.json()).then(json=>{ + if(json.errors&&this.errors(json.errors))return; + this.onSubmit(json); + }); + }else{ + this.onSubmit(build); + } + console.warn("needs to be implemented"); + } + errors(errors:{code:number,message:string,errors:{[key:string]:{_errors:{message:string,code:string}}}}){ + if(!(errors instanceof Object)){ + return; + } + for(const error of Object.keys(errors)){ + const elm=this.names.get(error); + if(elm){ + const ref=this.options.html.get(elm); + if(ref&&ref.deref()){ + const html=ref.deref() as HTMLDivElement; + this.makeError(html,errors[error]._errors[0].message); + return true; + } + } + } + return false; + } + error(formElm:string,errorMessage:string){ + const elm=this.names.get(formElm); + if(elm){ + const htmlref=this.options.html.get(elm); + if(htmlref){ + const html=htmlref.deref(); + if(html){ + this.makeError(html,errorMessage); + } + } + }else{ + console.warn(formElm+" is not a valid form property"); + } + } + makeError(e:HTMLDivElement,message:string){ + let element=e.getElementsByClassName("suberror")[0] as HTMLElement; + if(!element){ + const div=document.createElement("div"); + div.classList.add("suberror","suberrora"); + e.append(div); + element=div; + }else{ + element.classList.remove("suberror"); + setTimeout(_=>{ + element.classList.add("suberror"); + },100); + } + element.textContent=message; + } } class Settings extends Buttons{ - static readonly Buttons=Buttons; - static readonly Options=Options; - html:HTMLElement|null; - constructor(name:string){ - super(name); - } - addButton(name:string,{ltr=false}={}):Options{ - const options=new Options(name,this,{ltr}); - this.add(name,options); - return options; - } - show(){ - const background=document.createElement("div"); - background.classList.add("background"); + static readonly Buttons=Buttons; + static readonly Options=Options; + html:HTMLElement|null; + constructor(name:string){ + super(name); + } + addButton(name:string,{ltr=false}={}):Options{ + const options=new Options(name,this,{ltr}); + this.add(name,options); + return options; + } + show(){ + const background=document.createElement("div"); + background.classList.add("background"); - const title=document.createElement("h2"); - title.textContent=this.name; - title.classList.add("settingstitle") - background.append(title); + const title=document.createElement("h2"); + title.textContent=this.name; + title.classList.add("settingstitle"); + background.append(title); - background.append(this.generateHTML()); + background.append(this.generateHTML()); - const exit=document.createElement("span"); - exit.textContent="✖"; - exit.classList.add("exitsettings"); - background.append(exit); - exit.onclick=_=>{this.hide();}; - document.body.append(background); - this.html=background; - } - hide(){ - if(this.html){ - this.html.remove(); - this.html=null; - } - } + const exit=document.createElement("span"); + exit.textContent="✖"; + exit.classList.add("exitsettings"); + background.append(exit); + exit.onclick=_=>{ + this.hide(); + }; + document.body.append(background); + this.html=background; + } + hide(){ + if(this.html){ + this.html.remove(); + this.html=null; + } + } } -export {Settings,OptionsElement,Buttons,Options} +export{Settings,OptionsElement,Buttons,Options}; diff --git a/webpage/snowflake.ts b/webpage/snowflake.ts index eb5e048..0c4873f 100644 --- a/webpage/snowflake.ts +++ b/webpage/snowflake.ts @@ -1,95 +1,95 @@ class SnowFlake{ - public readonly id:string; - private static SnowFlakes:Map>>>=new Map(); - private static readonly FinalizationRegistry=new FinalizationRegistry((a:[string,WeakKey])=>{ - SnowFlake.SnowFlakes.get(a[1]).delete(a[0]); - }); - private obj:x; - constructor(id:string,obj:x){ - if(!obj){ - this.id=id; - return; - } - if(!SnowFlake.SnowFlakes.get(obj.constructor)){ - SnowFlake.SnowFlakes.set(obj.constructor,new Map()); - } - if(SnowFlake.SnowFlakes.get(obj.constructor).get(id)){ - const snowflake=SnowFlake.SnowFlakes.get(obj.constructor).get(id).deref(); - if(snowflake){ - snowflake.obj=obj; - return snowflake; - }else{ - SnowFlake.SnowFlakes.get(obj.constructor).delete(id); - } - } - this.id=id; - SnowFlake.SnowFlakes.get(obj.constructor).set(id,new WeakRef(this)); - SnowFlake.FinalizationRegistry.register(this,[id,obj.constructor]); - this.obj=obj; - } - static clear(){//this is kinda a temp solution, it should be fixed, though its not that easy to do so - this.SnowFlakes=new Map(); - } - /** + public readonly id:string; + private static SnowFlakes:Map>>>=new Map(); + private static readonly FinalizationRegistry=new FinalizationRegistry((a:[string,WeakKey])=>{ + SnowFlake.SnowFlakes.get(a[1]).delete(a[0]); + }); + private obj:x; + constructor(id:string,obj:x){ + if(!obj){ + this.id=id; + return; + } + if(!SnowFlake.SnowFlakes.get(obj.constructor)){ + SnowFlake.SnowFlakes.set(obj.constructor,new Map()); + } + if(SnowFlake.SnowFlakes.get(obj.constructor).get(id)){ + const snowflake=SnowFlake.SnowFlakes.get(obj.constructor).get(id).deref(); + if(snowflake){ + snowflake.obj=obj; + return snowflake; + }else{ + SnowFlake.SnowFlakes.get(obj.constructor).delete(id); + } + } + this.id=id; + SnowFlake.SnowFlakes.get(obj.constructor).set(id,new WeakRef(this)); + SnowFlake.FinalizationRegistry.register(this,[id,obj.constructor]); + this.obj=obj; + } + static clear(){//this is kinda a temp solution, it should be fixed, though its not that easy to do so + this.SnowFlakes=new Map(); + } + /** * Just to clarify bc TS, it returns a SnowFlake\ which is what you entered with the type parameter * @deprecated **/ - static getSnowFlakeFromID(id:string,type: abstract new(...args: never) => T): SnowFlake{ - if(!SnowFlake.SnowFlakes.get(type)){ - SnowFlake.SnowFlakes.set(type,new Map()); - } - const snowflake=SnowFlake.SnowFlakes.get(type).get(id); - if(snowflake){ - const obj=snowflake.deref(); - if(obj){ - return obj; - }else{ - SnowFlake.SnowFlakes.get(type).delete(id); - } - } - { - const snowflake=new SnowFlake(id,undefined); + static getSnowFlakeFromID(id:string,type: abstract new(...args: never) => T): SnowFlake{ + if(!SnowFlake.SnowFlakes.get(type)){ + SnowFlake.SnowFlakes.set(type,new Map()); + } + const snowflake=SnowFlake.SnowFlakes.get(type).get(id); + if(snowflake){ + const obj=snowflake.deref(); + if(obj){ + return obj; + }else{ + SnowFlake.SnowFlakes.get(type).delete(id); + } + } + { + const snowflake=new SnowFlake(id,undefined); - SnowFlake.SnowFlakes.get(type).set(id,new WeakRef(snowflake)); - SnowFlake.FinalizationRegistry.register(this,[id,type]); + SnowFlake.SnowFlakes.get(type).set(id,new WeakRef(snowflake)); + SnowFlake.FinalizationRegistry.register(this,[id,type]); - return snowflake; - } - } - /** + return snowflake; + } + } + /** * @deprecated * * */ - static hasSnowFlakeFromID(id:string,type:any){ - if(!SnowFlake.SnowFlakes.get(type)){ - return false; - } - const flake=SnowFlake.SnowFlakes.get(type).get(id); - if(flake){ - const flake2=flake.deref()?.getObject(); - if(flake2){ - return true; - }else{ - return false; - } - }else{ - return false; - } - } - getUnixTime():number{ - try{ - return Number((BigInt(this.id)>>22n)+1420070400000n); - }catch{ - console.error(`The ID is corrupted, it's ${this.id} when it should be some number.`) - return 0; - } - } - toString(){ - return this.id; - } - getObject():x{ - return this.obj; - } + static hasSnowFlakeFromID(id:string,type:any){ + if(!SnowFlake.SnowFlakes.get(type)){ + return false; + } + const flake=SnowFlake.SnowFlakes.get(type).get(id); + if(flake){ + const flake2=flake.deref()?.getObject(); + if(flake2){ + return true; + }else{ + return false; + } + }else{ + return false; + } + } + getUnixTime():number{ + try{ + return Number((BigInt(this.id)>>22n)+1420070400000n); + }catch{ + console.error(`The ID is corrupted, it's ${this.id} when it should be some number.`); + return 0; + } + } + toString(){ + return this.id; + } + getObject():x{ + return this.obj; + } } -export {SnowFlake}; +export{SnowFlake}; diff --git a/webpage/user.ts b/webpage/user.ts index a3032cf..8944285 100644 --- a/webpage/user.ts +++ b/webpage/user.ts @@ -1,431 +1,432 @@ //const usercache={}; -import {Member} from "./member.js"; -import {MarkDown} from "./markdown.js"; -import {Contextmenu} from "./contextmenu.js"; -import {Localuser} from "./localuser.js"; -import {Guild} from "./guild.js"; -import { SnowFlake } from "./snowflake.js"; -import { presencejson, userjson } from "./jsontypes.js"; +import{Member}from"./member.js"; +import{MarkDown}from"./markdown.js"; +import{Contextmenu}from"./contextmenu.js"; +import{Localuser}from"./localuser.js"; +import{Guild}from"./guild.js"; +import{ SnowFlake }from"./snowflake.js"; +import{ presencejson, userjson }from"./jsontypes.js"; class User{ - owner:Localuser; - hypotheticalpfp:boolean; - snowflake:SnowFlake; - avatar:string|null; - username:string; - nickname:string|null=null; - relationshipType:0|1|2|3|4=0; - bio:MarkDown; - discriminator:string; - pronouns:string; - bot:boolean; - public_flags: number; - accent_color: number; - banner: string|undefined; - hypotheticalbanner:boolean; - premium_since: string; - premium_type: number; - theme_colors: string; - badge_ids: string[]; - members: WeakMap>=new WeakMap(); - private status:string; - clone(){ - return new User({ - username:this.username, - id:this.id+"#clone", - public_flags:this.public_flags, - discriminator:this.discriminator, - avatar:this.avatar, - accent_color:this.accent_color, - banner:this.banner, - bio:this.bio.rawString, - premium_since:this.premium_since, - premium_type:this.premium_type, - bot:this.bot, - theme_colors:this.theme_colors, - pronouns:this.pronouns, - badge_ids:this.badge_ids - },this.owner) - } - public getPresence(presence:presencejson|undefined){ - if(presence){ - this.setstatus(presence.status); - }else{ - this.setstatus("offline"); - } - } - setstatus(status:string){ - this.status=status; - } - async getStatus(){ - if(this.status){ - return this.status; - }else{ - return "offline"; - } - } - get id(){ - return this.snowflake.id; - } - static contextmenu=new Contextmenu("User Menu"); - static setUpContextMenu(){ - this.contextmenu.addbutton("Copy user id",function(this:User){ - navigator.clipboard.writeText(this.id); - }); - this.contextmenu.addbutton("Message user",function(this:User){ - fetch(this.info.api+"/users/@me/channels", - {method:"POST", - body:JSON.stringify({"recipients":[this.id]}), - headers: this.localuser.headers - }); - }); - this.contextmenu.addbutton("Block user",function(this:User){ - this.block(); - },null,function(){ - return this.relationshipType!==2 - }); + owner:Localuser; + hypotheticalpfp:boolean; + snowflake:SnowFlake; + avatar:string|null; + username:string; + nickname:string|null=null; + relationshipType:0|1|2|3|4=0; + bio:MarkDown; + discriminator:string; + pronouns:string; + bot:boolean; + public_flags: number; + accent_color: number; + banner: string|undefined; + hypotheticalbanner:boolean; + premium_since: string; + premium_type: number; + theme_colors: string; + badge_ids: string[]; + members: WeakMap>=new WeakMap(); + private status:string; + clone(){ + return new User({ + username: this.username, + id: this.id+"#clone", + public_flags: this.public_flags, + discriminator: this.discriminator, + avatar: this.avatar, + accent_color: this.accent_color, + banner: this.banner, + bio: this.bio.rawString, + premium_since: this.premium_since, + premium_type: this.premium_type, + bot: this.bot, + theme_colors: this.theme_colors, + pronouns: this.pronouns, + badge_ids: this.badge_ids + },this.owner); + } + public getPresence(presence:presencejson|undefined){ + if(presence){ + this.setstatus(presence.status); + }else{ + this.setstatus("offline"); + } + } + setstatus(status:string){ + this.status=status; + } + async getStatus(){ + if(this.status){ + return this.status; + }else{ + return"offline"; + } + } + get id(){ + return this.snowflake.id; + } + static contextmenu=new Contextmenu("User Menu"); + static setUpContextMenu(){ + this.contextmenu.addbutton("Copy user id",function(this:User){ + navigator.clipboard.writeText(this.id); + }); + this.contextmenu.addbutton("Message user",function(this:User){ + fetch(this.info.api+"/users/@me/channels", + {method: "POST", + body: JSON.stringify({recipients: [this.id]}), + headers: this.localuser.headers + }); + }); + this.contextmenu.addbutton("Block user",function(this:User){ + this.block(); + },null,function(){ + return this.relationshipType!==2; + }); - this.contextmenu.addbutton("Unblock user",function(this:User){ - this.unblock(); - },null,function(){ - return this.relationshipType===2 - }); - this.contextmenu.addbutton("Friend request",function(this:User){ - fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{ - method:"PUT", - headers:this.owner.headers, - body:JSON.stringify({ - type:1 - }) - }) - }); - this.contextmenu.addbutton("Kick member",function(this:User,member:Member){ - member.kick(); - },null,function(member){ - if(!member) return false; - const us=member.guild.member; - if(member.id===us.id){ - return false; - } - if(member.id===member.guild.properties.owner_id){ - return false; - } - return (us.hasPermission("KICK_MEMBERS"))||false; - }); - this.contextmenu.addbutton("Ban member",function(this:User,member:Member){ - member.ban(); - },null,function(member){ - if(!member) return false; - const us=member.guild.member; - if(member.id===us.id){ - return false; - } - if(member.id===member.guild.properties.owner_id){ - return false; - } - return (us.hasPermission("BAN_MEMBERS"))||false; - }); - } - static checkuser(user:User|userjson,owner:Localuser):User{ - if(owner.userMap.has(user.id)){ - return owner.userMap.get(user.id) as User; - }else{ - const tempuser=new User(user as userjson,owner,true) - owner.userMap.set(user.id,tempuser); - return tempuser; - } - } - get info(){ - return this.owner.info; - } - get localuser(){ - return this.owner; - } - constructor(userjson:userjson,owner:Localuser,dontclone=false){ - this.owner=owner; - if(!owner){console.error("missing localuser")} - if(dontclone){ - for(const thing of Object.keys(userjson)){ - if(thing==="bio"){ - this.bio=new MarkDown(userjson[thing],this.localuser); - continue; - } - if(thing === "id"){ - this.snowflake=new SnowFlake(userjson[thing],this); - continue; - } - this[thing]=userjson[thing]; - } - this.hypotheticalpfp=false; - }else{ - return User.checkuser(userjson,owner); - } - } - async resolvemember(guild:Guild){ - return await Member.resolveMember(this,guild); - } + this.contextmenu.addbutton("Unblock user",function(this:User){ + this.unblock(); + },null,function(){ + return this.relationshipType===2; + }); + this.contextmenu.addbutton("Friend request",function(this:User){ + fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{ + method: "PUT", + headers: this.owner.headers, + body: JSON.stringify({ + type: 1 + }) + }); + }); + this.contextmenu.addbutton("Kick member",function(this:User,member:Member){ + member.kick(); + },null,member=>{ + if(!member)return false; + const us=member.guild.member; + if(member.id===us.id){ + return false; + } + if(member.id===member.guild.properties.owner_id){ + return false; + } + return(us.hasPermission("KICK_MEMBERS"))||false; + }); + this.contextmenu.addbutton("Ban member",function(this:User,member:Member){ + member.ban(); + },null,member=>{ + if(!member)return false; + const us=member.guild.member; + if(member.id===us.id){ + return false; + } + if(member.id===member.guild.properties.owner_id){ + return false; + } + return(us.hasPermission("BAN_MEMBERS"))||false; + }); + } + static checkuser(user:User|userjson,owner:Localuser):User{ + if(owner.userMap.has(user.id)){ + return owner.userMap.get(user.id) as User; + }else{ + const tempuser=new User(user as userjson,owner,true); + owner.userMap.set(user.id,tempuser); + return tempuser; + } + } + get info(){ + return this.owner.info; + } + get localuser(){ + return this.owner; + } + constructor(userjson:userjson,owner:Localuser,dontclone=false){ + this.owner=owner; + if(!owner){ + console.error("missing localuser"); + } + if(dontclone){ + for(const thing of Object.keys(userjson)){ + if(thing==="bio"){ + this.bio=new MarkDown(userjson[thing],this.localuser); + continue; + } + if(thing === "id"){ + this.snowflake=new SnowFlake(userjson[thing],this); + continue; + } + this[thing]=userjson[thing]; + } + this.hypotheticalpfp=false; + }else{ + return User.checkuser(userjson,owner); + } + } + async resolvemember(guild:Guild){ + return await Member.resolveMember(this,guild); + } - async getUserProfile(){ - return (await fetch(`${this.info.api}/users/${this.id.replace("#clone","")}/profile?with_mutual_guilds=true&with_mutual_friends=true`,{ - headers:this.localuser.headers - })).json() - } - resolving:false|Promise=false; - async getBadge(id:string){ - if(this.localuser.badges.has(id)){ - return this.localuser.badges.get(id); - }else{ - if(this.resolving) - { - await this.resolving; - return this.localuser.badges.get(id); - } + async getUserProfile(){ + return(await fetch(`${this.info.api}/users/${this.id.replace("#clone","")}/profile?with_mutual_guilds=true&with_mutual_friends=true`,{ + headers: this.localuser.headers + })).json(); + } + resolving:false|Promise=false; + async getBadge(id:string){ + if(this.localuser.badges.has(id)){ + return this.localuser.badges.get(id); + }else{ + if(this.resolving){ + await this.resolving; + return this.localuser.badges.get(id); + } - const prom=await this.getUserProfile(); - this.resolving=prom; - const badges=prom.badges; - this.resolving=false; - for(const thing of badges){ - this.localuser.badges.set(thing.id,thing); - } - return this.localuser.badges.get(id); - } - } - buildpfp(){ - const pfp=document.createElement('img'); - pfp.loading="lazy"; - pfp.src=this.getpfpsrc(); - pfp.classList.add("pfp"); - pfp.classList.add("userid:"+this.id); - return pfp; - } - async buildstatuspfp(){ - const div=document.createElement("div"); - div.style.position="relative"; - const pfp=this.buildpfp(); - div.append(pfp); - { - const status=document.createElement("div"); - status.classList.add("statusDiv"); - switch(await this.getStatus()){ - case "offline": - status.classList.add("offlinestatus"); - break; - case "online": - default: - status.classList.add("onlinestatus"); - break; - } - div.append(status); - } - return div; - } - userupdate(json:userjson){ - if(json.avatar!==this.avatar){ - console.log - this.changepfp(json.avatar); - } - } - bind(html:HTMLElement,guild:Guild|null=null,error=true){ - if(guild&&guild.id!=="@me"){ - Member.resolveMember(this,guild).then(_=>{ - User.contextmenu.bindContextmenu(html,this,_); - if(_===undefined&&error){ - const error=document.createElement("span"); - error.textContent="!"; - error.classList.add("membererror"); - html.after(error); - return; - } - if(_){ - _.bind(html); - } - }).catch(_=>{ - console.log(_) - }); - } - if(guild){ - this.profileclick(html,guild); - }else{ - this.profileclick(html); - } - } - static async resolve(id:string,localuser:Localuser){ - const json=await fetch(localuser.info.api.toString()+"/users/"+id+"/profile", - {headers:localuser.headers} - ).then(_=>_.json()); - return new User(json,localuser); - } - changepfp(update:string|null){ - this.avatar=update; - this.hypotheticalpfp=false; - const src=this.getpfpsrc(); - console.log(src) - for(const thing of document.getElementsByClassName("userid:"+this.id)){ - (thing as HTMLImageElement).src=src; - } - } - block(){ - fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{ - method:"PUT", - headers:this.owner.headers, - body:JSON.stringify({ - type:2 - }) - }) - this.relationshipType=2; - const channel=this.localuser.channelfocus; - if(channel){ - for(const thing of channel.messages){ - thing[1].generateMessage(); - } - } - } - unblock(){ - fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{ - method:"DELETE", - headers:this.owner.headers, - }) - this.relationshipType=0; - const channel=this.localuser.channelfocus; - if(channel){ - for(const thing of channel.messages){ - thing[1].generateMessage(); - } - } - } - getpfpsrc(){ - if(this.hypotheticalpfp&&this.avatar){ - return this.avatar; - } - if(this.avatar!=null){ - return this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.avatar+".png"; - }else{ - const int=new Number((BigInt(this.id.replace("#clone","")) >> 22n) % 6n); - return this.info.cdn+`/embed/avatars/${int}.png`; - } - } - createjankpromises(){ - new Promise(_=>{}) - } - async buildprofile(x:number,y:number,guild:Guild|null=null){ - if(Contextmenu.currentmenu!=""){ - Contextmenu.currentmenu.remove(); - } + const prom=await this.getUserProfile(); + this.resolving=prom; + const badges=prom.badges; + this.resolving=false; + for(const thing of badges){ + this.localuser.badges.set(thing.id,thing); + } + return this.localuser.badges.get(id); + } + } + buildpfp(){ + const pfp=document.createElement("img"); + pfp.loading="lazy"; + pfp.src=this.getpfpsrc(); + pfp.classList.add("pfp"); + pfp.classList.add("userid:"+this.id); + return pfp; + } + async buildstatuspfp(){ + const div=document.createElement("div"); + div.style.position="relative"; + const pfp=this.buildpfp(); + div.append(pfp); + { + const status=document.createElement("div"); + status.classList.add("statusDiv"); + switch(await this.getStatus()){ + case"offline": + status.classList.add("offlinestatus"); + break; + case"online": + default: + status.classList.add("onlinestatus"); + break; + } + div.append(status); + } + return div; + } + userupdate(json:userjson){ + if(json.avatar!==this.avatar){ + console.log; + this.changepfp(json.avatar); + } + } + bind(html:HTMLElement,guild:Guild|null=null,error=true){ + if(guild&&guild.id!=="@me"){ + Member.resolveMember(this,guild).then(_=>{ + User.contextmenu.bindContextmenu(html,this,_); + if(_===undefined&&error){ + const error=document.createElement("span"); + error.textContent="!"; + error.classList.add("membererror"); + html.after(error); + return; + } + if(_){ + _.bind(html); + } + }).catch(_=>{ + console.log(_); + }); + } + if(guild){ + this.profileclick(html,guild); + }else{ + this.profileclick(html); + } + } + static async resolve(id:string,localuser:Localuser){ + const json=await fetch(localuser.info.api.toString()+"/users/"+id+"/profile", + {headers: localuser.headers} + ).then(_=>_.json()); + return new User(json,localuser); + } + changepfp(update:string|null){ + this.avatar=update; + this.hypotheticalpfp=false; + const src=this.getpfpsrc(); + console.log(src); + for(const thing of document.getElementsByClassName("userid:"+this.id)){ + (thing as HTMLImageElement).src=src; + } + } + block(){ + fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{ + method: "PUT", + headers: this.owner.headers, + body: JSON.stringify({ + type: 2 + }) + }); + this.relationshipType=2; + const channel=this.localuser.channelfocus; + if(channel){ + for(const thing of channel.messages){ + thing[1].generateMessage(); + } + } + } + unblock(){ + fetch(`${this.info.api}/users/@me/relationships/${this.id}`,{ + method: "DELETE", + headers: this.owner.headers, + }); + this.relationshipType=0; + const channel=this.localuser.channelfocus; + if(channel){ + for(const thing of channel.messages){ + thing[1].generateMessage(); + } + } + } + getpfpsrc(){ + if(this.hypotheticalpfp&&this.avatar){ + return this.avatar; + } + if(this.avatar!=null){ + return this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.avatar+".png"; + }else{ + const int=new Number((BigInt(this.id.replace("#clone","")) >> 22n) % 6n); + return this.info.cdn+`/embed/avatars/${int}.png`; + } + } + createjankpromises(){ + new Promise(_=>{}); + } + async buildprofile(x:number,y:number,guild:Guild|null=null){ + if(Contextmenu.currentmenu!=""){ + Contextmenu.currentmenu.remove(); + } - const div=document.createElement("div"); + const div=document.createElement("div"); - if(this.accent_color){ - div.style.setProperty("--accent_color","#"+this.accent_color.toString(16).padStart(6,"0")); - }else{ - div.style.setProperty("--accent_color","transparent"); - } - if(this.banner){ - const banner=document.createElement("img") - let src:string; - if(!this.hypotheticalbanner){ - src=this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.banner+".png"; - }else{ - src=this.banner; - } - console.log(src,this.banner); - banner.src=src; - banner.classList.add("banner"); - div.append(banner); - } - if(x!==-1){ - div.style.left=x+"px"; - div.style.top=y+"px"; - div.classList.add("profile","flexttb"); - }else{ - this.setstatus("online"); - div.classList.add("hypoprofile","flexttb"); - } - const badgediv=document.createElement("div"); - badgediv.classList.add("badges"); - (async ()=>{ - if(!this.badge_ids) return; - for(const id of this.badge_ids){ - const badgejson=await this.getBadge(id); - if(badgejson){ - const badge=document.createElement(badgejson.link?"a":"div"); - badge.classList.add("badge") - const img=document.createElement("img"); - img.src=badgejson.icon; - badge.append(img); - const span=document.createElement("span"); - span.textContent=badgejson.description; - badge.append(span); - if(badge instanceof HTMLAnchorElement){ - badge.href=badgejson.link; - } - badgediv.append(badge); - } - } - })() - { - const pfp=await this.buildstatuspfp(); - div.appendChild(pfp); - } - { - const userbody=document.createElement("div"); - userbody.classList.add("infosection"); - div.appendChild(userbody); - const usernamehtml=document.createElement("h2"); - usernamehtml.textContent=this.username; - userbody.appendChild(usernamehtml); - userbody.appendChild(badgediv); - const discrimatorhtml=document.createElement("h3"); - discrimatorhtml.classList.add("tag"); - discrimatorhtml.textContent=this.username+"#"+this.discriminator; - userbody.appendChild(discrimatorhtml) + if(this.accent_color){ + div.style.setProperty("--accent_color","#"+this.accent_color.toString(16).padStart(6,"0")); + }else{ + div.style.setProperty("--accent_color","transparent"); + } + if(this.banner){ + const banner=document.createElement("img"); + let src:string; + if(!this.hypotheticalbanner){ + src=this.info.cdn+"/avatars/"+this.id.replace("#clone","")+"/"+this.banner+".png"; + }else{ + src=this.banner; + } + console.log(src,this.banner); + banner.src=src; + banner.classList.add("banner"); + div.append(banner); + } + if(x!==-1){ + div.style.left=x+"px"; + div.style.top=y+"px"; + div.classList.add("profile","flexttb"); + }else{ + this.setstatus("online"); + div.classList.add("hypoprofile","flexttb"); + } + const badgediv=document.createElement("div"); + badgediv.classList.add("badges"); + (async ()=>{ + if(!this.badge_ids)return; + for(const id of this.badge_ids){ + const badgejson=await this.getBadge(id); + if(badgejson){ + const badge=document.createElement(badgejson.link?"a":"div"); + badge.classList.add("badge"); + const img=document.createElement("img"); + img.src=badgejson.icon; + badge.append(img); + const span=document.createElement("span"); + span.textContent=badgejson.description; + badge.append(span); + if(badge instanceof HTMLAnchorElement){ + badge.href=badgejson.link; + } + badgediv.append(badge); + } + } + })(); + { + const pfp=await this.buildstatuspfp(); + div.appendChild(pfp); + } + { + const userbody=document.createElement("div"); + userbody.classList.add("infosection"); + div.appendChild(userbody); + const usernamehtml=document.createElement("h2"); + usernamehtml.textContent=this.username; + userbody.appendChild(usernamehtml); + userbody.appendChild(badgediv); + const discrimatorhtml=document.createElement("h3"); + discrimatorhtml.classList.add("tag"); + discrimatorhtml.textContent=this.username+"#"+this.discriminator; + userbody.appendChild(discrimatorhtml); - const pronounshtml=document.createElement("p"); - pronounshtml.textContent=this.pronouns; - pronounshtml.classList.add("pronouns"); - userbody.appendChild(pronounshtml) + const pronounshtml=document.createElement("p"); + pronounshtml.textContent=this.pronouns; + pronounshtml.classList.add("pronouns"); + userbody.appendChild(pronounshtml); - const rule=document.createElement("hr"); - userbody.appendChild(rule); - const biohtml=this.bio.makeHTML(); - userbody.appendChild(biohtml); - if(guild){ - Member.resolveMember(this,guild).then(member=>{ - if(!member) return; - const roles=document.createElement("div"); - roles.classList.add("rolesbox"); - for(const role of member.roles){ - const div=document.createElement("div"); - div.classList.add("rolediv"); - const color=document.createElement("div"); - div.append(color); - color.style.setProperty("--role-color","#"+role.color.toString(16).padStart(6,"0")) - color.classList.add("colorrolediv"); - const span=document.createElement("span"); - div.append(span); - span.textContent=role.name; - roles.append(div); - } - userbody.append(roles); - }); - } - } - console.log(div); + const rule=document.createElement("hr"); + userbody.appendChild(rule); + const biohtml=this.bio.makeHTML(); + userbody.appendChild(biohtml); + if(guild){ + Member.resolveMember(this,guild).then(member=>{ + if(!member)return; + const roles=document.createElement("div"); + roles.classList.add("rolesbox"); + for(const role of member.roles){ + const div=document.createElement("div"); + div.classList.add("rolediv"); + const color=document.createElement("div"); + div.append(color); + color.style.setProperty("--role-color","#"+role.color.toString(16).padStart(6,"0")); + color.classList.add("colorrolediv"); + const span=document.createElement("span"); + div.append(span); + span.textContent=role.name; + roles.append(div); + } + userbody.append(roles); + }); + } + } + console.log(div); - if(x!==-1){ - Contextmenu.currentmenu=div; - document.body.appendChild(div) - Contextmenu.keepOnScreen(div); - } - return div; - } - profileclick(obj:HTMLElement,guild:Guild|undefined=undefined){ - obj.onclick=e=>{ - this.buildprofile(e.clientX,e.clientY,guild); - e.stopPropagation(); - } - } + if(x!==-1){ + Contextmenu.currentmenu=div; + document.body.appendChild(div); + Contextmenu.keepOnScreen(div); + } + return div; + } + profileclick(obj:HTMLElement,guild?:Guild|undefined){ + obj.onclick=e=>{ + this.buildprofile(e.clientX,e.clientY,guild); + e.stopPropagation(); + }; + } } User.setUpContextMenu(); -export {User}; +export{User};