update instancejson more

MY HEAD HURTS NOW ;(

.

.

more work

Finish rewrite

all finished
This commit is contained in:
MathMan05
2024-09-16 19:11:05 -05:00
committed by Scott Gould
parent 3a61417de2
commit 758bd7cc7a
113 changed files with 12520 additions and 20427 deletions

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +0,0 @@
class Contextmenu<x,y>{
static currentmenu:HTMLElement|"";
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 as Node)){
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{};
}
private makemenu(x:number,y:number,addinfo:x,other:y){
const div=document.createElement("div");
div.classList.add("contextmenu","flexttb");
let visibleButtons=0;
for(const thing of this.buttons){
if(!thing[3].bind(addinfo)(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);
}
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";
}
}
}
Contextmenu.setup();
export{Contextmenu};

View File

@@ -1,278 +0,0 @@
type dialogjson=[
"hdiv",...dialogjson[]
]|[
"vdiv",...dialogjson[]
]|[
"img",string,[number,number]|undefined|["fit"]
]|[
"checkbox",string,boolean,(this:HTMLInputElement,e:Event)=>unknown
]|[
"button",string,string,(this:HTMLButtonElement,e:Event)=>unknown
]|[
"mdbox",string,string,(this:HTMLTextAreaElement,e:Event)=>unknown
]|[
"textbox",string,string,(this:HTMLInputElement,e:Event)=>unknown
]|[
"fileupload",string,(this:HTMLInputElement,e:Event)=>unknown
]|[
"text",string
]|[
"title",string
]|[
"radio",string,string[],(this:unknown,e:string)=>unknown,number
]|[
"html",HTMLElement
]|[
"select",string,string[],(this:HTMLSelectElement,e:Event)=>unknown,number
]|[
"tabs",[string,dialogjson][]
]
class Dialog{
layout:dialogjson;
onclose: Function;
onopen: Function;
html:HTMLDivElement;
background: HTMLDivElement;
constructor(layout:dialogjson,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:dialogjson):HTMLElement{
switch(array[0]){
case"img":
const img=document.createElement("img");
img.src=array[1];
if(array[2]!=undefined){
if(array[2].length===2){
img.width=array[2][0];
img.height=array[2][1];
}else if(array[2][0]==="fit"){
img.classList.add("imgfit");
}
}
return img;
case"hdiv":
const hdiv=document.createElement("div");
hdiv.classList.add("flexltr")
for(const thing of array){
if(thing==="hdiv"){
continue;
}
hdiv.appendChild(this.tohtml(thing));
}
return hdiv;
case"vdiv":
const vdiv=document.createElement("div");
vdiv.classList.add("flexttb");
for(const thing of array){
if(thing==="vdiv"){
continue;
}
vdiv.appendChild(this.tohtml(thing));
}
return vdiv;
case"checkbox":
{
const div=document.createElement("div");
const checkbox = document.createElement("input");
div.appendChild(checkbox);
const label=document.createElement("span");
checkbox.checked=array[2];
label.textContent=array[1];
div.appendChild(label);
checkbox.addEventListener("change",array[3]);
checkbox.type = "checkbox";
return div;
}
case"button":
{
const div=document.createElement("div");
const input = document.createElement("button");
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");
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");
label.textContent=array[1];
div.append(label);
div.appendChild(select);
for(const thing of array[2]){
const option = document.createElement("option");
option.textContent=thing;
select.appendChild(option);
}
select.selectedIndex=array[4];
select.addEventListener("change",array[3]);
return div;
}
case"tabs":{
const table=document.createElement("div");
table.classList.add("flexttb");
const tabs=document.createElement("div");
tabs.classList.add("flexltr")
tabs.classList.add("tabbed-head");
table.appendChild(tabs);
const content=document.createElement("div");
content.classList.add("tabbed-content");
table.appendChild(content);
let shown:HTMLElement|undefined;
for(const thing of array[1]){
const button=document.createElement("button");
button.textContent=thing[0];
tabs.appendChild(button);
const html=this.tohtml(thing[1]);
content.append(html);
if(!shown){
shown=html;
}else{
html.style.display="none";
}
button.addEventListener("click",_=>{
if(shown){
shown.style.display="none";
}
html.style.display="";
shown=html;
});
}
return table;
}
default:
console.error("can't find element:"+array[0]," full element:",array);
return document.createElement("span");
}
}
show(){
this.onopen();
console.log("fullscreen");
this.background=document.createElement("div");
this.background.classList.add("background");
document.body.appendChild(this.background);
document.body.appendChild(this.html);
this.background.onclick = _=>{
this.hide();
};
}
hide(){
document.body.removeChild(this.background);
document.body.removeChild(this.html);
}
}
export{Dialog};

View File

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

View File

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

Binary file not shown.

View File

@@ -1,230 +0,0 @@
import{ Contextmenu }from"./contextmenu.js";
import{ Guild }from"./guild.js";
import{ emojijson }from"./jsontypes.js";
import{ Localuser }from"./localuser.js";
class Emoji{
static emojis:{
name:string,
emojis:{
name:string,
emoji:string,
}[]
}[];
name:string;
id:string;
animated:boolean;
owner:Guild|Localuser;
get guild(){
if(this.owner instanceof Guild){
return this.owner;
}
}
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);
for(let i=0;i<length;i++){
array[i]=read8();
}
//console.log(array);
return new TextDecoder("utf-8").decode(array.buffer);
}
const build:{name:string,emojis:{name:string,emoji:string}[]}[]=[];
let cats=read16();
for(;cats!==0;cats--){
const name=readString16();
const emojis:{
name:string,
skin_tone_support:boolean,
emoji:string
}[]=[];
let emojinumber=read16();
for(;emojinumber!==0;emojinumber--){
//console.log(emojis);
const name=readString8();
const len=read8();
const skin_tone_support=len>127;
const emoji=readStringNo(len-(Number(skin_tone_support)*128));
emojis.push({
name,
skin_tone_support,
emoji
});
}
build.push({
name,
emojis
});
}
this.emojis=build;
console.log(build);
}
static grabEmoji(){
fetch("/emoji.bin").then(e=>{
return e.arrayBuffer();
}).then(e=>{
Emoji.decodeEmojiList(e);
});
}
static async emojiPicker(x:number,y:number, localuser:Localuser):Promise<Emoji|string>{
let res:(r:Emoji|string)=>void;
const promise:Promise<Emoji|string>=new Promise(r=>{
res=r;
});
const menu=document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
menu.style.top=y+"px";
menu.style.left=x+"px";
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");
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);
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);
emojiElem.addEventListener("click", ()=>{
res(emojiClass);
if(Contextmenu.currentmenu!==""){
Contextmenu.currentmenu.remove();
}
});
}
};
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);
let i=0;
for(const thing of Emoji.emojis){
const select=document.createElement("div");
select.textContent=thing.emojis[0].emoji;
select.classList.add("emojiSelect");
selection.append(select);
const clickEvent=()=>{
title.textContent=thing.name;
body.innerHTML="";
for(const emojit of thing.emojis){
const emoji=document.createElement("div");
emoji.classList.add("emojiSelect");
emoji.textContent=emojit.emoji;
body.append(emoji);
emoji.onclick=_=>{
res(emojit.emoji);
if(Contextmenu.currentmenu!==""){
Contextmenu.currentmenu.remove();
}
};
}
};
select.onclick=clickEvent;
if(i===0){
clickEvent();
}
i++;
}
menu.append(selection);
menu.append(body);
return promise;
}
}
Emoji.grabEmoji();
export{Emoji};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

View File

@@ -1,145 +0,0 @@
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;
}
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};

View File

@@ -1,617 +0,0 @@
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, invitejson }from"./jsontypes.js";
import{ User }from"./user.js";
class Guild extends SnowFlake{
owner:Localuser;
headers:Localuser["headers"];
channels:Channel[];
properties:guildjson["properties"];
member_count:number;
roles:Role[];
roleids:Map<string,Role>;
prevchannel:Channel|undefined;
message_notifications:number;
headchannels:Channel[];
position:number;
parent_id:string;
member:Member;
html:HTMLElement;
emojis:emojijson[];
large:boolean;
static contextmenu=new Contextmenu<Guild,undefined>("guild menu");
static setupcontextmenu(){
Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){
navigator.clipboard.writeText(this.id);
});
Guild.contextmenu.addbutton("Mark as read",function(this:Guild){
this.markAsRead();
});
Guild.contextmenu.addbutton("Notifications",function(this:Guild){
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("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){
},null,_=>true,_=>false);
Guild.contextmenu.addbutton("Settings",function(this:Guild){
this.generateSettings();
});
/* -----things left for later-----
guild.contextmenu.addbutton("Leave Guild",function(){
console.log(this)
this.deleteChannel();
},null,_=>{return thisuser.isAdmin()})
guild.contextmenu.addbutton("Mute Guild",function(){
editchannelf(this);
},null,_=>{return thisuser.isAdmin()})
*/
}
generateSettings(){
const settings=new Settings("Settings for "+this.properties.name);
{
const overview=settings.addButton("Overview");
const form=overview.addForm("",_=>{},{
headers:this.headers,
traditionalSubmit:true,
fetchURL:this.info.api+"/guilds/"+this.id,
method:"PATCH"
})
form.addTextInput("Name:","name",{initText:this.properties.name});
form.addMDInput("Description:","description",{initText:this.properties.description});
form.addFileInput("Banner:","banner",{clear:true});
form.addFileInput("Icon:","icon",{clear:true});
let region=this.properties.region;
if(!region){
region="";
}
form.addTextInput("Region:","region",{initText:region});
}
const s1=settings.addButton("roles");
const permlist:[Role,Permissions][]=[];
for(const thing of this.roles){
permlist.push([thing,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){
super("@me");
return;
}
if(json.stickers.length){
console.log(json.stickers,":3");
}
super(json.id);
this.large=json.large;
this.member_count=json.member_count;
this.emojis = json.emojis;
this.owner=owner;
this.headers=this.owner.headers;
this.channels=[];
this.properties=json.properties;
this.roles=[];
this.roleids=new Map();
this.message_notifications=0;
for(const roley of json.roles){
const roleh=new Role(roley,this);
this.roles.push(roleh);
this.roleids.set(roleh.id,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=_;
}
});
}
this.perminfo??={channels:{}};
for(const thing of json.channels){
const temp=new Channel(thing,this);
this.channels.push(temp);
this.localuser.channelids.set(temp.id,temp);
}
this.headchannels=[];
for(const thing of this.channels){
const parent=thing.resolveparent(this);
if(!parent){
this.headchannels.push(thing);
}
}
this.prevchannel=this.localuser.channelids.get(this.perminfo.prevchannel);
}
get perminfo(){
return this.localuser.perminfo.guilds[this.id];
}
set perminfo(e){
this.localuser.perminfo.guilds[this.id]=e;
}
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:"all"|"only mentions"|"none"){
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.id,{
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:string,position:number|undefined,parent_id:string|undefined}[]=[];
for(const thing of this.headchannels){
const thisthing:{id:string,position:number|undefined,parent_id:string|undefined}={id: thing.id,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=undefined;
}
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.id+"/channels",{
method: "PATCH",
headers: this.headers,
body: JSON.stringify([thing])
});
}
}else{
fetch(this.info.api+"/guilds/"+this.id+"/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;
});
}
static generateGuildIcon(guild:Guild|(invitejson["guild"] & {info:{cdn:string}})){
const divy=document.createElement("div");
divy.classList.add("servernoti");
const noti=document.createElement("div");
noti.classList.add("unread");
divy.append(noti);
if(guild instanceof Guild){
guild.localuser.guildhtml.set(guild.id,divy);
}
let icon:string|null
if(guild instanceof Guild){
icon=guild.properties.icon;
}else{
icon=guild.icon;
}
if(icon!==null){
const img=document.createElement("img");
img.classList.add("pfp","servericon");
img.src=guild.info.cdn+"/icons/"+guild.id+"/"+icon+".png";
divy.appendChild(img);
if(guild instanceof Guild){
img.onclick=()=>{
console.log(guild.loadGuild);
guild.loadGuild();
guild.loadChannel();
};
Guild.contextmenu.bindContextmenu(img,guild,undefined);
}
}else{
const div=document.createElement("div");
let name:string
if(guild instanceof Guild){
name=guild.properties.name;
}else{
name=guild.name;
}
const build=name.replace(/'s /g, " ").replace(/\w+/g, word=>word[0]).replace(/\s/g, "");
div.textContent=build;
div.classList.add("blankserver","servericon");
divy.appendChild(div);
if(guild instanceof Guild){
div.onclick=()=>{
guild.loadGuild();
guild.loadChannel();
};
Guild.contextmenu.bindContextmenu(div,guild,undefined);
}
}
return divy;
}
generateGuildIcon(){
return Guild.generateGuildIcon(this);
}
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();
}
]
]
]);
full.show();
}
async delete(){
return fetch(this.info.api+"/guilds/"+this.id+"/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");
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:string,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.id,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){
const channel=this.localuser.channelids.get(ID);
if(channel){
channel.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){
const channel=this.localuser.channelids.get(json.id);
if(channel){
channel.updateChannel(json);
this.headchannels=[];
for(const thing of this.channels){
thing.children=[];
}
this.headchannels=[];
for(const thing of this.channels){
const parent=thing.resolveparent(this);
if(!parent){
this.headchannels.push(thing);
}
}
this.printServers();
}
}
createChannelpac(json:channeljson){
const thischannel=new Channel(json,this);
this.localuser.channelids.set(json.id,thischannel);
this.channels.push(thischannel);
thischannel.resolveparent(this);
if(!thischannel.parent){
this.headchannels.push(thischannel);
}
this.calculateReorder();
this.printServers();
return thischannel;
}
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){
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){
name=this.value;
}],
["button","","submit",()=>{
console.log(name,category);
this.createChannel(name,category);
channelselect.hide();
}]
]);
channelselect.show();
}
delChannel(json:channeljson){
const channel=this.localuser.channelids.get(json.id);
this.localuser.channelids.delete(json.id);
if(!channel) return;
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);
if(thing!==channel){
build.push(thing)
}else{
console.log("fail");
if(thing.parent){
thing.parent.delChannel(json);
}
}
}
this.channels=build;
*/
this.printServers();
}
createChannel(name:string,type:number){
fetch(this.info.api+"/guilds/"+this.id+"/channels",{
method: "POST",
headers: this.headers,
body: JSON.stringify({name, type})
});
}
async createRole(name:string){
const fetched=await fetch(this.info.api+"/guilds/"+this.id+"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.id,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.id+"/roles/"+role.id,{
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 };

View File

@@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss">
</head>
<body class="Dark-theme">
<div id="titleDiv">
<img src="/logo.svg" width="40">
<h1 id="pageTitle">Jank Client</h1>
<a href="https://sb-jankclient.vanillaminigames.net/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" class="TitleButtons"><h1>Spacebar Guild</h1></a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"><h1>Github</h1></a>
</div>
<div class="flexttb">
<div class="flexttb pagehead"><h1>Welcome to Jank Client</h1></div>
<div class="pagebox">
<p>Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including:</p>
<ul>
<li>Direct Messaging</li>
<li>Reactions support</li>
<li>Invites</li>
<li>Account switching</li>
<li>User settings</li>
</ul>
</div>
<div class="pagebox">
<h2>Spacebar compatible Instances:</h2>
<div id="instancebox">
</div>
</div>
<div class="pagebox">
<h2>Contribute to Jank Client</h2>
<p>We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out some typos.</p><br>
</a><a href="https://github.com/MathMan05/JankClient" class="TitleButtons"><h1>Github</h1></a>
</div>
</div>
</body>
<script src="/home.js" type="module"></script>
</html>

View File

@@ -1,64 +0,0 @@
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");
{
const textbox=document.createElement("div");
textbox.classList.add("flexttb","instatancetextbox");
const title=document.createElement("h2");
title.innerText=instance.name;
if(instance.online!==undefined){
const status=document.createElement("span");
status.innerText=instance.online?"Online":"Offline";
status.classList.add("instanceStatus");
title.append(status);
}
textbox.append(title);
if(instance.description||instance.descriptionLong){
const p=document.createElement("p");
if(instance.descriptionLong){
p.innerText=instance.descriptionLong;
}else if(instance.description){
p.innerText=instance.description;
}
textbox.append(p);
}
statbox.append(textbox);
}
if(instance.uptime){
const stats=document.createElement("div");
stats.classList.add("flexltr");
const span=document.createElement("span");
span.innerText=`Uptime: All time: ${Math.round(instance.uptime.alltime*100)}% This week: ${Math.round(instance.uptime.weektime*100)}% Today: ${Math.round(instance.uptime.daytime*100)}%`;
stats.append(span);
statbox.append(stats);
}
div.append(statbox);
div.onclick=_=>{
if(instance.online){
window.location.href="/register.html?instance="+encodeURI(instance.name);
}else{
alert("Instance is offline, can't connect");
}
};
serverbox.append(div);
}
});

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><g style="fill:red;stroke:red;stroke-opacity:1"><path d="m25 73 54-11 46-25v106l-40-24-60-11z" style="fill:red;fill-opacity:1;stroke:red;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" transform="matrix(1.6277 0 0 1.5863 -30 -56)"/><path d="m32 90 13 2-5 32 38 7 5-32 14 2-7 46-66-11 7-46z" style="fill:red;fill-opacity:1;stroke:red;stroke-width:3.84496;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" transform="matrix(1.6277 0 0 1.5863 -30 -56)"/></g></svg>

Before

Width:  |  Height:  |  Size: 606 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path d="m15 54 75 64 75-64" style="fill:none;stroke:red;stroke-width:29.0323;stroke-linecap:round;stroke-linejoin:round"/></svg>

Before

Width:  |  Height:  |  Size: 191 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path d="m117 165 13-148M47 165 60 17m108 46H20m140 65H12" style="fill:red;stroke:red;stroke-width:24.2188;stroke-linecap:round;stroke-dasharray:none"/></svg>

Before

Width:  |  Height:  |  Size: 220 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><g fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="21.9"><path d="m64 44.5 85.7.3-.5 124.3-85.2-.4.5-124.2Z"/><path d="M31.5 141.6 32 11.5h-.5l89.6.3"/></g></svg>

Before

Width:  |  Height:  |  Size: 262 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><g fill="red"><path d="M139 155a11 11 0 0 1-3.3 4.9 11 11 0 0 1-8 2.6 11 11 0 0 1-10.2-11.4h-16.8v.5a11 11 0 0 1-11.1 11 11 11 0 0 1-11-11v-.5H62a11 11 0 0 1-2.7 7.7 11 11 0 0 1-15.6 1 11 11 0 0 1-3.8-7.2l1 12.6h97.3z"/><path stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.3" d="M100.1 61.5a11 11 0 0 1 .6 3v86.6h16.8a11 11 0 0 1 0-.3l6.3-87a11 11 0 0 1 .5-2.3z"/><path stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.3" d="M146.7 33.2H32.4a14.1 14.1 0 0 0-14 15.2l8.5 117.8a14.1 14.1 0 0 0 14 13.1h97.4a14.1 14.1 0 0 0 14-13l8.5-118a14.1 14.1 0 0 0-14-15Zm0 14.2-1 15a11 11 0 0 1 .2 3l-6.3 87a11 11 0 0 1-.6 2.7l-.7 10.1H41l-1-12.6a11 11 0 0 1 0-.2l-6.3-87a11 11 0 0 1 0-.3l-1.2-17.7Z"/><path stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.3" d="M146.7 47.4H32.5L33.7 65A11 11 0 0 1 44 53.6a11 11 0 0 1 11.3 7.9H79a11 11 0 0 1 10.6-7.9 11 11 0 0 1 10.5 7.9h24.2a11 11 0 0 1 11.3-7.9 11 11 0 0 1 7.5 3.8 11 11 0 0 1 2.5 5z"/><path stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.3" d="M55.3 61.5a11 11 0 0 1 .5 2.2l6.3 87.1a11 11 0 0 1 0 .3h16.4V64.5a11 11 0 0 1 .5-3z"/><path stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="11.7" d="M19.9 15.6h140.4v5.6H19.9z"/><path stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="14.2" d="M76 7.1h27.6v7.5H76z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="red" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="27.7" d="m112.8 40.4 26.8 26.8-84.5 84.7-41.3 14.3L28.2 125Zm26.5-26.6 26.9 26.9-4.3 4.4L135 18.2z"/></svg>

Before

Width:  |  Height:  |  Size: 260 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><circle cx="90" cy="90" r="64.4" style="fill:none;stroke:red;stroke-width:11.1467;stroke-linejoin:bevel;stroke-dasharray:none"/><g transform="rotate(4.6 -290 396.6) scale(.71167)"><circle cx="89.9" cy="86.9" r="15.5" style="fill:none;stroke:red;stroke-width:11.24121975;stroke-linejoin:bevel;stroke-dasharray:none"/><path d="M134.9 34.6 90.2 71.2l.3.2c6.7.3 12.5 4.8 14.3 11.2l.7.5zM88 102.3a15.5 15.5 0 0 1-12.6-9.9l-1.9-1.6-15.4 34 30-22.6Z" style="fill:red;stroke:red;stroke-width:4.2154574;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"/></g></svg>

Before

Width:  |  Height:  |  Size: 633 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path d="M88.5 13.4 20.7 66.3h10l57.8-44.8 56.8 44.8h9.4z" style="fill:red;stroke:red;stroke-width:3;stroke-linecap:round;stroke-linejoin:round" transform="translate(26 32.6) scale(.72993)"/><path d="M111.3 10.6v22.8l23.4 18.4h2.4V10.6Z" style="fill:red;stroke:red;stroke-linejoin:bevel" transform="translate(26 32.6) scale(.72993)"/><path d="M131.6 88.7 90.6 56 48.4 89.3v49.4H79v-32.2h21.8v32.2h30.7z" style="fill:red;stroke:red;stroke-width:2.18979;stroke-linecap:round;stroke-linejoin:round"/></svg>

Before

Width:  |  Height:  |  Size: 565 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><g fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"><path d="M155.5 164c0-36.7 0-73.4-17.2-91.8C121.1 54 86.7 54 52.3 54"/><path d="M67.8 16 26 53.9l42.3 44.4"/></g></svg>

Before

Width:  |  Height:  |  Size: 274 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><g fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round"><circle cx="90.7" cy="90" r="50.3" stroke-width="28"/><path stroke-width="22.4" d="M83.7 11.2h14v26.3h-14zm0 131.2h14v26.3h-14zm-44.8 8.3-10-9.9 18.7-18.6 9.9 9.9zM131.6 58l-9.9-10 18.6-18.6 10 10zm37.2 25.2v14h-26.3v-14zm-131.2 0v14H11.3v-14zm-8.3-44.8 9.9-10 18.6 18.7L48 57zm92.7 92.7 10-9.9 18.6 18.7-10 9.9z"/></g></svg>

Before

Width:  |  Height:  |  Size: 462 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path d="M5 51h21l28-26h17v130H54l-28-25-21 1Z" style="fill:red;stroke-width:1.21692;stroke-dasharray:none"/><path d="M100 38c27 12 38 45 26 71-4 9-10 16-18 22" style="fill:none;fill-rule:evenodd;stroke:red;stroke-width:25;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1" transform="rotate(5 50 -2)"/><path d="M130 8c41 23 56 76 34 117-8 14-19 25-32 33" style="fill:none;fill-rule:evenodd;stroke:red;stroke-width:25;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1" transform="rotate(1 -346 -882) scale(1.02257)"/></svg>

Before

Width:  |  Height:  |  Size: 605 B

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss">
<link rel="manifest" href="/manifest.json">
</head>
<body class="Dark-theme">
<script src="/index.js" type="module"></script>
<div id="loading" class="loading">
<div id="centerdiv">
<img src="/logo.svg" style="width:3in;height:3in;">
<h1>Jank Client is loading</h1>
<h2 id="load-desc">This shouldn't take long</h2>
<h1 id="switchaccounts">Switch Accounts</h1>
</div>
</div>
<div class="flexltr" id="page">
<div id="neunence">
<div id="servers"></div>
</div>
<div class="flexttb channelflex">
<div class="servertd" id="servertd">
<h2 id="serverName">Server Name</h2>
</div>
<div id="channels"></div>
<div class="flexltr" id="userdock">
<div class="flexltr" id="userinfo">
<img id="userpfp" class="pfp">
<div class="userflex">
<p id="username">USERNAME</p>
<p id="status">STATUS</p>
</div>
</div>
<div id="user-actions">
<span id="settings" class="svgtheme svg-settings"></span>
</div>
</div>
</div>
<div class="flexttb messageflex">
<div class="servertd channelnamediv">
<span id="mobileback" hidden></span>
<span id="channelname">Channel name</span>
<span id="channelTopic" hidden>Channel topic</span>
</div>
<div id="channelw">
<div id="loadingdiv">
</div>
</div>
<div id="pasteimage"></div>
<div id="replybox" class="hideReplyBox"></div>
<div id="typediv">
<div id="realbox">
<div id="typebox" contentEditable="true"></div>
</div>
<div id="typing" class="hidden">
<p id="typingtext">typing</p>
<div class="loading-indicator">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,229 +0,0 @@
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";
}
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 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);
}
{
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",()=>{
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;
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;
/*
function getguildinfo(){
const path=window.location.pathname.split("/");
const channel=path[3];
this.ws.send(JSON.stringify({op: 14, d: {guild_id: path[2], channels: {[channel]: [[0, 99]]}}}));
}
*/
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);
});
});
setTheme();
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");
};
}
})();

View File

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

View File

@@ -1,28 +0,0 @@
[
{
"name":"Spacebar",
"description":"The official Spacebar instance.",
"image":"https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/branding/png/Spacebar__Icon-Discord.png",
"url":"https://spacebar.chat"
},
{
"name": "Fastbar",
"description": "The best Spacebar instance with 95% uptime, running under on a NVME drive running with bleeding edge stuff <3",
"image": "https://avatars.githubusercontent.com/u/65827291",
"url": "https://greysilly7.xyz",
"language": "ENG",
"country": "United States",
"display": true,
"urls":{
"wellknown": "https://greysilly7.xyz",
"api": "https://spacebar.greysilly7.xyz/api/v9",
"cdn": "https://spacebar.greysilly7.xyz",
"gateway": "wss://spacebar.greysilly7.xyz"
},
"contactInfo":{
"dicord": "greysilly7",
"github": "https://github.com/greysilly7",
"email": "greysilly7@gmail.com"
}
}
]

View File

@@ -1,22 +0,0 @@
<body class="Dark-theme">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jank Client</title>
<meta content="Invite" property="og:title">
<meta content="You shouldn't see this, but this is an invite URL" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss">
</head>
<div>
<div id="invitebody">
<div id="inviteimg"></div>
<h1 id="invitename">Server Name</h1>
<p id="invitedescription">Someone invited you to Server Name</p>
<button id="AcceptInvite">Accept Invite</button>
</div>
</div>
<script type="module" src="/invite.js"></script>
</body>

View File

@@ -1,118 +0,0 @@
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);
}
});
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 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);
})();

View File

@@ -1,453 +0,0 @@
type readyjson={
op:0;
t:"READY";
s:number;
d:{
v:number;
user:mainuserjson;
user_settings:{
index: number,
afk_timeout: number,
allow_accessibility_detection: boolean,
animate_emoji: boolean,
animate_stickers: number,
contact_sync_enabled: boolean,
convert_emoticons: boolean,
custom_status: string,
default_guilds_restricted: boolean,
detect_platform_accounts: boolean,
developer_mode: boolean,
disable_games_tab: boolean,
enable_tts_command: boolean,
explicit_content_filter: 0,
friend_discovery_flags: 0,
friend_source_flags: {
all: boolean
},//might be missing things here
gateway_connected: boolean,
gif_auto_play: boolean,
guild_folders: [],//need an example of this not empty
guild_positions: [],//need an example of this not empty
inline_attachment_media: boolean,
inline_embed_media: boolean,
locale: string,
message_display_compact: boolean,
native_phone_integration_enabled: boolean,
render_embeds: boolean,
render_reactions: boolean,
restricted_guilds: [],//need an example of this not empty
show_current_game: boolean,
status: string,
stream_notifications_enabled: boolean,
theme: string,
timezone_offset: number,
view_nsfw_guilds: boolean
};
guilds:guildjson[];
relationships:{
id:string,
type:0|1|2|3|4,
nickname:string|null,
user:userjson
}[];
read_state:{
entries:{
id: string,
channel_id: string,
last_message_id: string,
last_pin_timestamp: string,
mention_count: number //in theory, the server doesn't actually send this as far as I'm aware
}[],
partial: boolean,
version: number
};
user_guild_settings:{
entries:{
channel_overrides: unknown[],//will have to find example
message_notifications: number,
flags: number,
hide_muted_channels: boolean,
mobile_push: boolean,
mute_config: null,
mute_scheduled_events: boolean,
muted: boolean,
notify_highlights: number,
suppress_everyone: boolean,
suppress_roles: boolean,
version: number,
guild_id: string
}[],
partial: boolean,
version: number
};
private_channels:dirrectjson[];
session_id:string;
country_code:string;
users:userjson[];
merged_members:memberjson[];
sessions:{
active: boolean,
activities: [],//will need to find example of this
client_info: {
version: number
},
session_id: string,
status: string
}[];
resume_gateway_url:string;
consents:{
personalization: {
consented: boolean
}
};
experiments: [],//not sure if I need to do this :P
guild_join_requests: [],//need to get examples
connected_accounts: [],//need to get examples
guild_experiments: [],//need to get examples
geo_ordered_rtc_regions: [],//need to get examples
api_code_version: number,
friend_suggestion_count: number,
analytics_token: string,
tutorial: boolean,
session_type: string,
auth_session_id_hash: string,
notification_settings: {
flags: number
}
}
}
type mainuserjson= userjson & {
flags: number,
mfa_enabled?: boolean,
email?: string,
phone?: string,
verified: boolean,
nsfw_allowed: boolean,
premium: boolean,
purchased_flags: number,
premium_usage_flags: number,
disabled: boolean
}
type userjson={
username: string,
discriminator: string,
id: string,
public_flags: number,
avatar: string|null,
accent_color: number,
banner?: string,
bio: string,
bot: boolean,
premium_since: string,
premium_type: number,
theme_colors: string,
pronouns: string,
badge_ids: string[],
}
type memberjson= {
index?:number,
id: string,
user: userjson|null,
guild_id: string,
guild: {
id: string
}|null,
nick?: string,
roles: string[],
joined_at: string,
premium_since: string,
deaf: boolean,
mute: boolean,
pending: boolean,
last_message_id?: boolean//What???
}
type emojijson={
name:string,
id?:string,
animated?:boolean
}
type guildjson={
application_command_counts: {[key:string]:number},
channels: channeljson[],
data_mode: string,
emojis: emojijson[],
guild_scheduled_events: [],
id: string,
large: boolean,
lazy: boolean,
member_count: number,
premium_subscription_count: number,
properties: {
region: string|null,
name: string,
description: string,
icon: string,
splash: string,
banner: string,
features: string[],
preferred_locale: string,
owner_id: string,
application_id: string,
afk_channel_id: string,
afk_timeout: number,
system_channel_id: string,
verification_level: number,
explicit_content_filter: number,
default_message_notifications: number,
mfa_level: number,
vanity_url_code: number,
premium_tier: number,
premium_progress_bar_enabled: boolean,
system_channel_flags: number,
discovery_splash: string,
rules_channel_id: string,
public_updates_channel_id: string,
max_video_channel_users: number,
max_members: number,
nsfw_level: number,
hub_type: null,
home_header: null,
id: string,
latest_onboarding_question_id: string,
max_stage_video_channel_users: number,
nsfw: boolean,
safety_alerts_channel_id: string
},
roles: rolesjson[],
stage_instances: [],
stickers: [],
threads: [],
version: string,
guild_hashes: {},
joined_at: string
}
type channeljson={
id: string,
created_at: string,
name: string,
icon: string,
type: number,
last_message_id: string,
guild_id: string,
parent_id: string,
last_pin_timestamp: string,
default_auto_archive_duration: number,
permission_overwrites: {
id:string,
allow:string,
deny:string,
}[],
video_quality_mode: null,
nsfw: boolean,
topic: string,
retention_policy_id: string,
flags: number,
default_thread_rate_limit_per_user: number,
position: number
}
type rolesjson={
id: string,
guild_id: string,
color: number,
hoist: boolean,
managed: boolean,
mentionable: boolean,
name: string,
permissions: string,
position: number,
icon: string,
unicode_emoji: string,
flags: number
}
type dirrectjson={
id: string,
flags: number,
last_message_id: string,
type: number,
recipients: userjson[],
is_spam: boolean
}
type messagejson={
id: string,
channel_id: string,
guild_id: string,
author: userjson,
member?: memberjson,
content: string,
timestamp: string,
edited_timestamp: string,
tts: boolean,
mention_everyone: boolean,
mentions: [], //need examples to fix
mention_roles: [], //need examples to fix
attachments: filejson[],
embeds: embedjson[],
reactions: {
count:number,
emoji:emojijson,//very likely needs expanding
me:boolean,
}[],
nonce: string,
pinned: boolean,
type: number
}
type filejson={
id:string,
filename:string,
content_type:string,
width?:number,
height?:number,
proxy_url:string|undefined,
url:string,
size:number
};
type embedjson={
type:string|null,
color?:number,
author:{
icon_url?:string,
name?:string,
url?:string,
title?:string,
},
title?:string,
url?:string,
description?:string,
fields?:{
name:string,
value:string,
inline:boolean,
}[],
footer?:{
icon_url?:string,
text?:string,
thumbnail?:string,
},
timestamp?:string,
thumbnail:{
proxy_url:string,
url:string,
width:number,
height:number
},
provider:{
name:string,
},
video?:{
url: string,
width?: number|null,
height?: number|null,
proxy_url?: string
},
invite?:{
url:string,
code:string
}
}
type invitejson={
code: string,
temporary: boolean,
uses: number,
max_use: number,
max_age: number,
created_at: string,
expires_at: string,
guild_id: string,
channel_id: string,
inviter_id: string,
target_user_id: string|null,
target_user_type: string|null,
vanity_url: string|null,
flags: number,
guild: guildjson["properties"],
channel: channeljson,
inviter: userjson
}
type presencejson={
status: string,
since: number|null,
activities: any[],//bit more complicated but not now
afk: boolean,
user?:userjson,
}
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_REMOVE_ALL"|"MESSAGE_REACTION_REMOVE_EMOJI"
}|{
op:0,
t:"GUILD_MEMBERS_CHUNK",
d:memberChunk,
s:number
}|{
op:0,
d:{
id:string,
guild_id?:string,
channel_id:string
},
s:number,
t:"MESSAGE_DELETE"
}|{
op:0,
d:{
guild_id?:string,
channel_id:string
}&messagejson,
s:number,
t:"MESSAGE_UPDATE"
}|messageCreateJson|readyjson|{
op:11,
s:undefined,
d:{}
}|{
op:10,
s:undefined,
d:{
heartbeat_interval:number
}
}|{
op: 0,
t: "MESSAGE_REACTION_ADD",
d: {
user_id: string,
channel_id: string,
message_id: string,
guild_id?: string,
emoji: emojijson,
member?: memberjson
},
s: number
}|{
op: 0,
t: "MESSAGE_REACTION_REMOVE",
d: {
user_id: string,
channel_id: string,
message_id: string,
guild_id: string,
emoji: emojijson
},
"s": 3
}
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,invitejson};

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +0,0 @@
<body class="Dark-theme">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss">
</head>
<div id="logindiv">
<h1>Login</h1><br>
<form id="form" submit="check(e)">
<label for="instance"><b>Instance:</b></label><br>
<p id="verify"></p>
<input type="search" list="instances" placeholder="Instance URL" name="instance" id="instancein" value="" id="instancein" required><br><br>
<label for="uname"><b>Email:</b></label><br>
<input type="text" placeholder="Enter email address" name="uname" id="uname" required><br><br>
<label for="psw"><b>Password:</b></label><br>
<input type="password" placeholder="Enter Password" name="psw" id="psw" required><br><br><br><br>
<p class="wrongred" id="wrong"></p>
<div id="h-captcha">
</div>
<button type="submit">Login</button>
</form>
<a href="/register.html" id="switch">Don't have an account?</a>
</div>
<datalist id="instances"></datalist>
<script src="/login.js" type="module"></script>
</body>

View File

@@ -1,454 +0,0 @@
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 instances:{name:string,description?:string,descriptionLong?:string,image?:string,url?:string,display?:boolean,online?:boolean,
uptime:{alltime:number,daytime:number,weektime:number},
urls:{wellknown:string,api:string,cdn:string,gateway:string,login?:string}}[]|null;
setTheme();
function getBulkUsers(){
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.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"));
}
function setDefaults(){
let userinfos=getBulkInfo();
if(!userinfos){
localStorage.setItem("userinfos",JSON.stringify({
currentuser: null,
users: {},
preferences:
{
theme: "Dark",
notifications: false,
notisound: "three",
},
}));
userinfos=getBulkInfo();
}
if(userinfos.users===undefined){
userinfos.users={};
}
if(userinfos.accent_color===undefined){
userinfos.accent_color="#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;
this.json.localuserStore??={};
if(!this.serverurls||!this.email||!this.token){
console.error("There are fundamentally missing pieces of info missing from this user");
}
}
set pfpsrc(e){
this.json.pfpsrc=e;
this.updateLocal();
}
get pfpsrc(){
return this.json.pfpsrc;
}
set username(e){
this.json.username=e;
this.updateLocal();
}
get username(){
return this.json.username;
}
set localuserStore(e){
this.json.localuserStore=e;
this.updateLocal();
}
get localuserStore(){
return this.json.localuserStore;
}
get uid(){
return this.email+this.serverurls.wellknown;
}
toJSON(){
return this.json;
}
updateLocal(){
const info=getBulkInfo();
info.users[this.uid]=this.toJSON();
localStorage.setItem("userinfos",JSON.stringify(info));
}
}
function adduser(user){
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;
let timeout;
let instanceinfo;
const stringURLMap=new Map<string,string>();
const stringURLsMap=new Map<string,{wellknown:string,api:string,cdn:string,gateway:string,login?:string}>();
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.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{
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/");
}
}
async function login(username:string, password:string, captcha:string){
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){
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()");
}
}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();
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);
}
//this currently does not work, and need to be implemented better at some time.
/*
if ("serviceWorker" in navigator){
navigator.serviceWorker.register("/service.js", {
scope: "/",
}).then((registration) => {
let serviceWorker:ServiceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
console.log("installing");
} else if (registration.waiting) {
serviceWorker = registration.waiting;
console.log("waiting");
} else if (registration.active) {
serviceWorker = registration.active;
console.log("active");
}
if (serviceWorker) {
console.log(serviceWorker.state);
serviceWorker.addEventListener("statechange", (e) => {
console.log(serviceWorker.state);
});
}
})
}
*/
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("");
}
}
export{checkInstance};
trimswitcher();
export{mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser,getapiurls,adduser};
const datalist=document.getElementById("instances");
console.warn(datalist);
export function getInstances(){
return instances;
}
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}}[])=>{
instances=json;
if(datalist){
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("");
}
});

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 135.5 135.5"><path fill="navy" d="m5.3.6 96.2-.5 24.4 70.3-101.5.5L0 .6z"/><path fill="#000036" d="m99.5.4 1.9-.2 24.4 70.3-7.2.2L94.2.4z"/><ellipse cx="74.4" cy="54.1" rx="7.3" ry="21.6"/><ellipse cx="39.9" cy="54.1" rx="7.3" ry="21.6"/><path fill="navy" d="m15.4 65.4 96.2-.6 24.4 70.3-101.5.6-24.4-70.3z"/><path fill="#000034" d="m38 130.4 96.2-.6 1.7 5.7-101.5.6-1.7-5.7z"/><path fill="#000036" d="m111.4 71 1.8-.2 22.8 65-7.2.2-22.7-65z"/><path d="M33.4 65.3H21a7.3 21.6 0 0 0 6.3 10.4 7.3 21.6 0 0 0 6.2-10.5zm22.1-.2a7.3 21.6 0 0 0 6.2 10.7A7.3 21.6 0 0 0 68 65z"/></svg>

Before

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,12 +0,0 @@
{
"name": "Jank Client",
"icons": [
{
"src": "/logo.svg",
"sizes": "512x512"
}
],
"start_url":"/channels/@me",
"display":"standalone",
"theme_color":"#05050a"
}

View File

@@ -1,752 +0,0 @@
import{ Channel }from"./channel.js";
import { Dialog } from "./dialog.js";
import{ Emoji }from"./emoji.js";
import { Guild } from "./guild.js";
import{ Localuser }from"./localuser.js";
import { Member } from "./member.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 localuser(){
if(this.owner instanceof Localuser){
return this.owner;
}else{
return this.owner.localuser;
}
}
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.length;i++){
if(txt[i]==="\n"||i===0){
const first=i===0;
if(first){
i--;
}
let element:HTMLElement=document.createElement("span");
let keepys="";
if(txt[i+1]==="#"){
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");
}
if(keep){
element.append(keepys);
//span.appendChild(document.createElement("br"));
}
element.appendChild(this.markdown(build,{keep,stdsize}));
span.append(element);
}finally{
i-=1;
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){
build=build.replaceAll("\n","");
}
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]==="*"){
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;
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[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[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;
}
}
if((!keep)&&txt[i]==="h" && txt[i + 1]==="t" && txt[i + 2]==="t" && txt[i + 3]==="p"){
let build="http";
let j = i+4;
const endchars=new Set(["\\", "<", ">", "|", "]"," "]);
for(; txt[j] !== undefined; j++){
const char=txt[j];
if(endchars.has(char)){
break;
}
build+=char;
}
if(URL.canParse(build)){
appendcurrent();
const a=document.createElement("a");
//a.href=build;
MarkDown.safeLink(a,build);
a.textContent=build;
a.target="_blank";
i=j-1;
span.appendChild(a);
continue;
}
}
if(txt[i]==="<" && (txt[i + 1]==="@"||txt[i + 1]==="#")){
let id="";
let j = i+2;
const numbers=new Set(["0", "1", "2", "3", "4","5","6","7","8","9"]);
for(; txt[j] !== undefined; j++){
const char=txt[j];
if(!numbers.has(char)){
break;
}
id+=char;
}
if(txt[j]===">"){
appendcurrent();
const mention=document.createElement("span");
mention.classList.add("mentionMD");
mention.contentEditable="false";
const char=txt[i + 1];
i=j;
switch(char){
case "@":
const user=this.localuser.userMap.get(id);
if(user){
mention.textContent=`@${user.name}`;
let guild:null|Guild=null;
if(this.owner instanceof Channel){
guild=this.owner.guild;
}
if(!keep){
user.bind(mention,guild);
}
if(guild){
Member.resolveMember(user,guild).then(member=>{
if(member){
mention.textContent=`@${member.name}`;
}
})
}
}else{
mention.textContent=`@unknown`;
}
break;
case "#":
const channel=this.localuser.channelids.get(id);
if(channel){
mention.textContent=`#${channel.name}`;
if(!keep){
mention.onclick=_=>{
this.localuser.goToChannel(id);
}
}
}else{
mention.textContent=`#unknown`;
}
break;
}
span.appendChild(mention);
mention.setAttribute("real",`<${char}${id}>`);
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(/^<t:([0-9]{1,16})(:([tTdDfFR]))?>$/) 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"}) + " " +
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";
}
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[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: Boolean(parts[1])},owner);
span.appendChild(emoji.getHTML(isEmojiOnly));
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(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;
}
}
if(partsFound === 2){
appendcurrent();
const parts=build.join("").match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/);
if(parts){
const linkElem=document.createElement("a");
if(URL.canParse(parts[2])){
i=j;
MarkDown.safeLink(linkElem,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;
}
}
}
}
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):string{
if(element.tagName.toLowerCase()==="img"){
return(element as HTMLImageElement).alt;
}
if(element.tagName.toLowerCase()==="br"){
return"\n";
}
if(element.hasAttribute("real")){
return element.getAttribute("real") as string;
}
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;
}
static readonly trustedDomains=new Set([location.host])
static safeLink(elm:HTMLElement,url:string){
if(URL.canParse(url)){
const Url=new URL(url);
if(elm instanceof HTMLAnchorElement&&this.trustedDomains.has(Url.host)){
elm.href=url;
elm.target="_blank";
return;
}
elm.onmouseup=_=>{
if(_.button===2) return;
console.log(":3")
function open(){
const proxy=window.open(url, '_blank')
if(proxy&&_.button===1){
proxy.focus();
}else if(proxy){
window.focus();
}
}
if(this.trustedDomains.has(Url.host)){
open();
}else{
const full=new Dialog([
"vdiv",
["title","You're leaving spacebar"],
["text","You're going to "+Url.host+" are you sure you want to go there?"],
["hdiv",
["button","","Nevermind",_=>full.hide()],
["button","","Go there",_=>{open();full.hide()}],
["button","","Go there and trust in the future",_=>{
open();
full.hide();
this.trustedDomains.add(Url.host);
}]
]
]);
full.show();
}
}
}else{
throw Error(url+" is not a valid URL")
}
}
static replace(base:HTMLElement,newelm:HTMLElement){
const basechildren=base.children;
const newchildren=newelm.children;
for(const thing of newchildren){
}
}
}
//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div
let text="";
function saveCaretPosition (context){
const selection = window.getSelection();
if(!selection)return;
const range = selection.getRangeAt(0);
range.setStart(context, 0);
text=selection.toString();
let len = text.length+1;
for(const str in text.split("\n")){
if(str.length!==0){
len--;
}
}
len+=+(text[text.length-1]==="\n");
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;
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};

View File

@@ -1,221 +0,0 @@
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 extends SnowFlake{
static already={};
owner:Guild;
user:User;
roles:Role[]=[];
nick:string;
private constructor(memberjson:memberjson,owner:Guild){
super(memberjson.id);
this.owner=owner;
if(this.localuser.userMap.has(memberjson.id)){
this.user=this.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){
this.user=new User(memberjson.user,owner.localuser);
}else{
throw new Error("Missing user object of this member");
}
for(const thing of Object.keys(memberjson)){
if(thing==="guild"){
continue;
}
if(thing==="owner"){
continue;
}
if(thing==="roles"){
for(const strrole of memberjson.roles){
const role=this.guild.roleids.get(strrole);
if(!role) continue;
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;
}
this.roles.sort((a,b)=>{return (this.guild.roles.indexOf(a)-this.guild.roles.indexOf(b))});
}
get guild(){
return this.owner;
}
get localuser(){
return this.guild.localuser;
}
get info(){
return this.owner.info;
}
static async new(memberjson:memberjson,owner:Guild):Promise<Member|undefined>{
let user:User;
if(owner.localuser.userMap.has(memberjson.id)){
user=owner.localuser.userMap.get(memberjson.id) as User;
}else if(memberjson.user){
user=new User(memberjson.user,owner.localuser);
}else{
throw new Error("missing user object of this member");
}
if(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<Member|undefined>{
const maybe=user.members.get(guild);
if(!user.members.has(guild)){
const membpromise=guild.localuser.resolvemember(user.id,guild.id);
const promise=new Promise<Member|undefined>(async res=>{
const 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;
}
/*
if(this.error){
}
*/
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,
});
}
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;
}
}
export{Member};

View File

@@ -1,687 +0,0 @@
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 { Dialog } from "./dialog.js";
class Message extends SnowFlake{
static contextmenu=new Contextmenu<Message,undefined>("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[]
message_reference;
type:number;
timestamp:number;
content:MarkDown;
static del:Promise<void>;
static resolve:Function;
/*
weakdiv:WeakRef<HTMLDivElement>;
set div(e:HTMLDivElement){
if(!e){
this.weakdiv=null;
return;
}
this.weakdiv=new WeakRef(e);
}
get div(){
return this.weakdiv?.deref();
}
//*/
div:HTMLDivElement|undefined;
member:Member|undefined;
reactions:messagejson["reactions"];
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.setEdit();
},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();
});
}
setEdit(){
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);
}
constructor(messagejson:messagejson,owner:Channel){
super(messagejson.id);
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"){
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){
this.embeds[thing]=new Embed(messagejson.embeds[thing],this);
}
continue;
}
this[thing]=messagejson[thing];
}
if(messagejson.reactions?.length){
console.log(messagejson.reactions,":3");
}
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===this.localuser.user;
}
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.id+"/messages/"+this.id,{
method: "PATCH",
headers: this.headers,
body: JSON.stringify({content})
});
}
delete(){
fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`,{
headers: this.headers,
method: "DELETE",
});
}
deleteEvent(){
console.log("deleted")
if(this.div){
this.div.remove();
this.div.innerHTML="";
this.div=undefined;
}
const prev=this.channel.idToPrev.get(this.id);
const next=this.channel.idToNext.get(this.id);
this.channel.idToPrev.delete(this.id);
this.channel.idToNext.delete(this.id);
this.channel.messages.delete(this.id);
if(prev&&next){
this.channel.idToPrev.set(next,prev);
this.channel.idToNext.set(prev,next);
}else if(prev){
this.channel.idToNext.delete(prev);
}else if(next){
this.channel.idToPrev.delete(next);
}
if(prev){
const prevmessage=this.channel.messages.get(prev);
if(prevmessage){
prevmessage.generateMessage();
}
}
if(this.channel.lastmessage===this||this.channel.lastmessageid===this.id){
if(prev){
this.channel.lastmessage=this.channel.messages.get(prev);
this.channel.lastmessageid=prev;
}else{
this.channel.lastmessage=undefined;
this.channel.lastmessageid=undefined;
}
}
if(this.channel.lastreadmessageid===this.id){
if(prev){
this.channel.lastreadmessageid=prev;
}else{
this.channel.lastreadmessageid=undefined;
}
}
console.log("deleted done")
}
reactdiv:WeakRef<HTMLDivElement>;
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;
for(const user of this.mentions){
if(user===this.localuser.user){
div.classList.add("mentioned");
}
}
if(this===this.channel.replyingto){
div.classList.add("replying");
}
div.innerHTML="";
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.";
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.scollDiv&&scroll){
this.channel.infinite.scollDiv.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.scollDiv&&scroll){
func();
this.channel.infinite.scollDiv.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,this.guild);
username.textContent=author.username;
author.bind(username,this.guild);
});
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!=this.author)||(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);
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);
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){
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");
}
const reactions=document.createElement("div");
reactions.classList.add("flexltr","reactiondiv");
this.reactdiv=new WeakRef(reactions);
this.updateReactions();
div.append(reactions);
this.bindButtonEvent();
return(div);
}
bindButtonEvent(){
if(this.div){
let buttons:HTMLDivElement|undefined;
this.div.onmouseenter=_=>{
if(buttons){
buttons.remove();
buttons=undefined;
}
if(this.div){
buttons=document.createElement("div");
buttons.classList.add("messageButtons","flexltr");
if(this.channel.hasPermission("SEND_MESSAGES")){
const container=document.createElement("div");
const reply=document.createElement("span");
reply.classList.add("svgtheme", "svg-reply", "svgicon");
container.append(reply);
buttons.append(container);
container.onclick=_=>{
this.channel.setReplying(this);
}
}
if(this.author===this.localuser.user){
const container=document.createElement("div");
const edit=document.createElement("span");
edit.classList.add("svgtheme", "svg-edit", "svgicon");
container.append(edit);
buttons.append(container);
container.onclick=_=>{
this.setEdit();
}
}
if(this.canDelete()){
const container=document.createElement("div");
const reply=document.createElement("span");
reply.classList.add("svgtheme", "svg-delete", "svgicon");
container.append(reply);
buttons.append(container);
container.onclick=_=>{
if(_.shiftKey){
this.delete();
return;
}
const diaolog=new Dialog(["hdiv",["title","are you sure you want to delete this?"],["button","","yes",()=>{this.delete();diaolog.hide()}],["button","","no",()=>{diaolog.hide()}]]);
diaolog.show();
}
}
if(buttons.childNodes.length!==0){
this.div.append(buttons);
}
}
}
this.div.onmouseleave=_=>{
if(buttons){
buttons.remove();
buttons=undefined;
}
}
}
}
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(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.id} 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:string;
let yesterdayStr:string;
function formatTime(date:Date){
updateTimes();
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)}`;
}
}
let tomorrow=0;
updateTimes();
function updateTimes(){
if(tomorrow<Date.now()){
const d = new Date();
tomorrow=d.setHours(24,0,0,0);
now = new Date().toLocaleDateString();
const yesterday = new Date(now);
yesterday.setDate(new Date().getDate() - 1);
yesterdayStr=yesterday.toLocaleDateString();
}
}
Message.setup();
export{ Message };

View File

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

View File

@@ -1,60 +0,0 @@
<body class="Dark-theme">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<link href="/themes.css" rel="stylesheet" id="lightcss">
</head>
<div id="logindiv">
<h1>Create an account</h1><br>
<form id="register" submit="registertry(e)">
<div>
<label for="instance"><b>Instance:</b></label><br>
<p id="verify"></p>
<input type="search" list="instances" placeholder="Instance URL" id="instancein" name="instance" value="" id="instancein" required>
</div>
<div>
<label for="uname"><b>Email:</b></label><br>
<input type="text" placeholder="Enter Email" name="uname" id="uname" required>
</div>
<div>
<label for="uname"><b>Username:</b></label><br>
<input type="text" placeholder="Enter Username" name="username" id="username" required>
</div>
<div>
<label for="psw"><b>Password:</b></label><br>
<input type="password" placeholder="Enter Password" name="psw" id="psw" required>
</div>
<div>
<label for="psw2"><b>Enter password again:</b></label><br>
<input type="password" placeholder="Enter Password Again" name="psw2" id="psw2" required>
</div>
<div>
<label for="date"><b>Date of birth:</b></label><br>
<input type="date" id="date" name="date">
</div>
<div>
<b id="TOSbox">I agree to the <a href="" id="TOSa">Terms of Service</a>:</b>
<input type="checkbox" id="TOS" name="TOS">
</div>
<p class="wrongred" id="wrong"></p>
<div id="h-captcha">
</div>
<button type="submit" class="dontgrow">Create account</button>
</form>
<a href="/login.html" id="switch">Already have an account?</a>
</div>
<datalist id="instances"></datalist>
<script src="/register.js" type="module"></script>
</body>

View File

@@ -1,111 +0,0 @@
import{checkInstance, adduser}from"./login.js";
if(document.getElementById("register")){
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);
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");
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;
}
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 <a href=\"\" id=\"TOSa\">Terms of Service</a>:";
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();
checkInstance["alt"]=tosLogic;

View File

@@ -1,169 +0,0 @@
import{Permissions}from"./permissions.js";
import{Localuser}from"./localuser.js";
import{Guild}from"./guild.js";
import{ SnowFlake }from"./snowflake.js";
import{ rolesjson }from"./jsontypes.js";
class Role extends SnowFlake{
permissions:Permissions;
owner:Guild;
color:number;
name:string;
info:Guild["info"];
hoist:boolean;
icon:string;
mentionable:boolean;
unicode_emoji:string;
headers:Guild["headers"];
constructor(json:rolesjson, owner:Guild){
super(json.id);
this.headers=owner.headers;
this.info=owner.info;
for(const thing of Object.keys(json)){
if(thing==="id"){
continue;
}
this[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";
class PermissionToggle implements OptionsElement<number>{
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);
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(){
}
}
import{ OptionsElement,Buttons }from"./settings.js";
class RoleList extends Buttons{
readonly permissions:[Role,Permissions][];
permission:Permissions;
readonly guild:Guild;
readonly channel:boolean;
readonly declare buttons:[string,string][];
readonly options:Options;
onchange:Function;
curid:string;
constructor(permissions:[Role,Permissions][],guild:Guild,onchange:Function,channel=false){
super("Roles");
this.guild=guild;
this.permissions=permissions;
this.channel=channel;
this.onchange=onchange;
const options=new Options("",this);
if(channel){
this.permission=new Permissions("0","0");
}else{
this.permission=new Permissions("0");
}
for(const thing of Permissions.info){
options.options.push(new PermissionToggle(thing,this.permission,options));
}
for(const i of permissions){
console.log(i);
this.buttons.push([i[0].name,i[0].id]);
}
this.options=options;
}
handleString(str:string):HTMLElement{
this.curid=str;
const arr=this.permissions.find(_=>_[0].id===str);
if(arr){
const perm=arr[1];
this.permission.deny=perm.deny;
this.permission.allow=perm.allow;
const role=this.permissions.find(e=>e[0].id===str);
if(role){
this.options.name=role[0].name;
this.options.haschanged=false;
}
}
return this.options.generateHTML();
}
save(){
this.onchange(this.curid,this.permission);
}
}
export{RoleList};

View File

@@ -1,93 +0,0 @@
function deleteoldcache(){
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("test");
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);
});
}
var checkedrecently=false;
function samedomain(url){
return new URL(url).origin===self.origin;
}
function isindexhtml(url){
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);
}
}
self.addEventListener("fetch", (event:any)=>{
try{
event.respondWith(getfile(event));
}catch(e){
console.error(e);
}
});

View File

@@ -1,945 +0,0 @@
interface OptionsElement<x> {//
generateHTML():HTMLElement;
submit:()=>void;
readonly watchForChange:(func:(arg1:x)=>void)=>void;
value:x;
}
//future me stuff
class Buttons implements OptionsElement<unknown>{
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<string>{
readonly label:string;
readonly owner:Options;
readonly onSubmit:(str:string)=>void;
value:string;
input:WeakRef<HTMLInputElement>;
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<void>{
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<void>{
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<boolean>{
readonly label:string;
readonly owner:Options;
readonly onSubmit:(str:boolean)=>void;
value:boolean;
input:WeakRef<HTMLInputElement>;
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<void>{
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<string>{
readonly label:string;
readonly owner:Options;
readonly onSubmit:(str:string)=>void;
colorContent:string;
input:WeakRef<HTMLInputElement>;
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<number>{
readonly label:string;
readonly owner:Options;
readonly onSubmit:(str:number)=>void;
options:string[];
index:number;
select:WeakRef<HTMLSelectElement>;
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);
}
}
class MDInput implements OptionsElement<string>{
readonly label:string;
readonly owner:Options;
readonly onSubmit:(str:string)=>void;
value:string;
input:WeakRef<HTMLTextAreaElement>;
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<FileList|null>{
readonly label:string;
readonly owner:Options;
readonly onSubmit:(str:FileList|null)=>void;
input:WeakRef<HTMLInputElement>;
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(input){
this.value=input.files;
if(this.onchange){
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<void>{
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<void>{
name:string;
haschanged=false;
readonly options:OptionsElement<any>[];
readonly owner:Buttons|Options|Form;
readonly ltr:boolean;
value:void;
readonly html:WeakMap<OptionsElement<any>,WeakRef<HTMLDivElement>>=new WeakMap();
container:WeakRef<HTMLDivElement>=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;
}
addForm(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.options.push(options);
this.generate(options);
return options;
}
generate(elm:OptionsElement<any>){
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<HTMLElement>=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);
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<any>;
message:string;
constructor(elem:OptionsElement<any>,message:string){
super(message);
this.message=message;
this.elem=elem;
}
}
export{FormError};
class Form implements OptionsElement<object>{
name:string;
readonly options:Options;
readonly owner:Options;
readonly ltr:boolean;
readonly names:Map<string,OptionsElement<any>>=new Map();
readonly required:WeakSet<OptionsElement<any>>=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;
}
readonly fileOptions:Map<FileInput,{files:"one"|"multi"}>=new Map();
addFileInput(label:string,formName:string,{required=false,files="one",clear=false}={}){
const FI=this.options.addFileInput(label,_=>{},{clear});
if(files!=="one"&&files!=="multi") throw new Error("files should equal one or multi");
this.fileOptions.set(FI,{files});
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;
}
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();
}
}
async 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;
}
}
const promises:Promise<void>[]=[];
for(const thing of this.names.keys()){
if(thing==="")continue;
const input=this.names.get(thing) as OptionsElement<any>;
if(input instanceof SelectInput){
build[thing]=input.options[input.value];
continue;
}else if(input instanceof FileInput){
const options=this.fileOptions.get(input);
if(!options){
throw new Error("FileInput without its options is in this form, this should never happen.");
}
if(options.files==="one"){
console.log(input.value);
if(input.value){
const reader=new FileReader();
reader.readAsDataURL(input.value[0]);
const promise=new Promise<void>((res)=>{
reader.onload=()=>{
build[thing]=reader.result;
res();
};
})
promises.push(promise);
}
}else{
console.error(options.files+" is not currently implemented")
}
}
build[thing]=input.value;
}
await Promise.allSettled(promises);
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");
const title=document.createElement("h2");
title.textContent=this.name;
title.classList.add("settingstitle");
background.append(title);
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;
}
}
}
export{Settings,OptionsElement,Buttons,Options};

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,178 +0,0 @@
:root {
--servertd-height: 0px;
/* Default value */
--red: red;
--green: green;
--yellow:yellow;
--accent-color:#242443;
}
.Dark-theme { /* thanks to TomatoCake for the updated CSS vars and such*/
color-scheme: dark;
--primary-text: #FFF;
--primary-bg: color-mix(in srgb, #2f2f2f 70%, var(--accent-color));
--black: #000;
--shadow: #000;
--focus: #8888ff;
--message-bg-hover: color-mix(in srgb, #191919 85%, var(--accent-color));
--typing-bg: #161616;
--timestamp-color: #a2a2a2;
--code-bg: color-mix(in srgb, #121212 95%, var(--accent-color));
--user-info-bg: color-mix(in srgb,#383838 85%, var(--accent-color));
--user-dock-bg: color-mix(in srgb,#111111 90%, var(--accent-color));
--channels-bg: color-mix(in srgb, #2a2a2a 90%, var(--accent-color));
--channel-hover: color-mix(in srgb, #121212 70%, var(--accent-color));
--blank-bg: #323232;
--light-border: #92929B;
--settings-hover: color-mix(in srgb, #000000 95%, var(--accent-color) 5%);
--quote-bg: #7a798e;
--button-bg: color-mix(in srgb, #191919 85%, var(--accent-color));
--button-hover: color-mix(in srgb, #2f2f2f 70%, var(--accent-color));
--textarea-bg: color-mix(in srgb, #484848 80%, var(--accent-color));
--filename: #47bbff;
--mention-bg: #F00;
--mention-md-bg: #3b588b;
--pronouns: #797979;
--profile-bg: color-mix(in srgb, #232323 90%, var(--accent-color));
--profile-info-bg: color-mix(in srgb, #121212 90%, var(--accent-color));
--server-border: color-mix(in srgb, #000000 80%, var(--accent-color));
--channel-name-bg: color-mix(in srgb, #2a2a2a 80%, var(--accent-color));
--server-name-bg: color-mix(in srgb, #232323 90%, var(--accent-color));
--reply-border: #474b76;
--reply-bg: #0b0d20;
--reply-text: #acacac;
--spoiler-hover: #111111;
--spoiler-open-bg: #1e1e1e;
--unknown-file-bg: #141316;
--unknown-file-border: #474555;
--login-border: #131315;
--loading-bg: #22232c;
--dialog-bg: #33363d;
--dialog-border: #1c1b31;
--scrollbar-track: #34313c;
--scrollbar-thumb: #201f29;
--scrollbar-thumb-hover: #16161f;
--markdown-timestamp: #2f2f33;
--embed: color-mix(in srgb, #131313 90%, var(--accent-color));
--link: #8888ff;
--discovery-bg: #37373b;
--message-jump:#7678b0;
--icon-color:white;
--server-list: color-mix(in srgb, #1d1d1d 90%, var(--accent-color));
}
.WHITE-theme {
color-scheme: light;
--primary-text: #000;
--primary-bg: #FFF;
--black: #FFF;
--red: #dd6c6c;
--green: #639d63;
--shadow: #777;
--focus: #47bbff;
--message-bg-hover: #dedee2;
--typing-bg: #dad8d8;
--timestamp-color: #929297;
--code-bg: #cbcbcc;
--user-info-bg: #b0abc2;
--user-dock-bg: #b2b2c4;
--channels-bg: #cbcbd8;
--channel-hover: #b8b5cc;
--blank-bg: #ceccdd;
--light-border: #96959e;
--settings-hover: #b5b1bb;
--quote-bg: #7a798e;
--button-bg: #b7b7cc;
--button-hover: #FFF;
--textarea-bg: #b1b6ce;
--filename: #47bbff;
--mention-bg: #F00;
--mention-md-bg: #3b588b;
--pronouns: #6a6a6d;
--profile-bg: #cacad8;
--profile-info-bg: #bbbbce;
--server-border: #bebed3;
--channel-name-bg: #c0c0d4;
--server-name-bg: #a3a3b5;
--reply-border: #b1b2bd;
--reply-bg: #d4d6e9;
--reply-text: #2e2e30;
--spoiler-hover: #b9b9b9;
--spoiler-open-bg: #dadada;
--unknown-file-bg: #bdbdbd;
--unknown-file-border: #adadad;
--login-border: #c3c0e0;
--loading-bg: #b5b7cc;
--dialog-bg: #c1c8d6;
--dialog-border: #b9b7db;
--scrollbar-track: #d5d1e2;
--scrollbar-thumb: #b0afc0;
--scrollbar-thumb-hover: #a5a5b8;
--markdown-timestamp: #c8c8da;
--embed: #f2f3f5;
--link: #3333ee;
--discovery-bg: #c6c6d8;
--message-jump:#ccceff;
--icon-color:black;
}
.Light-theme {
color-scheme: light;
--primary-text: #000;
--primary-bg: #8e90c3;
--black: #fff;
--shadow: #000;
--focus: #5e50c5;
--message-bg-hover: #5757b5;
--typing-bg: #d4d6e9;
--profile-bg: #8075bf;
--profile-info-bg: #8075bf;
--timestamp-color: #000000;
--code-bg: #a89adf;
--info-bg: #6060a3;
--user-info-bg: #796f9a;
--user-dock-bg: #83839d;
--channels-bg: #c2c2d1;
--channel-hover: #726e88;
--blank-bg: #5e50c5;
--light-border: #000000;
--settings-hover: #b5b1bb;
--quote-bg: #7a798e;
--button-bg: #5757b5;
--button-hover: #8e90c3;
--textarea-bg: #abb1cd;
--filename: #47bbff;
--mention-bg: #F00;
--mention-md-bg: #3b588b;
--pronouns: #202020;
--channel-name-bg: #c0c0d4;
--server-name-bg: #a3a3b5;
--server-border: #aaaac4;
--server-hover: #7f7fa8;
--reply-border: #474b76;
--reply-bg: #d4d6e9;
--reply-text: #38383d;
--spoiler-hover: #34333a;
--spoiler-open-bg: #767587;
--unknown-file-bg: #bdbdbd;
--unknown-file-border: #adadad;
--login-border: #c3c0e0;
--loading-bg: #b5b7cc;
--dialog-bg: #c1c8d6;
--dialog-border: #b9b7db;
--scrollbar-track: #d2cedf;
--scrollbar-thumb: #bdbcca;
--scrollbar-thumb-hover: #a7a7be;
--markdown-timestamp: #c8c8da;
--embed: #cdccd1;
--link: #5566cc;
--discovery-bg: #c6c6d8;
--message-jump:#ccceff;
--icon-color:black;
}

View File

@@ -1,433 +0,0 @@
//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";
class User extends SnowFlake{
owner:Localuser;
hypotheticalpfp:boolean;
avatar:string|null;
username:string;
nickname:string|null=null;
relationshipType:0|1|2|3|4=0;
bio:MarkDown;
discriminator:string;
pronouns:string;
bot:boolean;
public_flags: number;
accent_color: number;
banner: string|undefined;
hypotheticalbanner:boolean;
premium_since: string;
premium_type: number;
theme_colors: string;
badge_ids: string[];
members: WeakMap<Guild, Member|undefined|Promise<Member|undefined>>=new WeakMap();
private status:string;
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";
}
}
static contextmenu=new Contextmenu<User,Member|undefined>("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
}).then(_=>_.json()).then(json=>{
this.localuser.goToChannel(json.id)
});
});
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,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;
}
get name(){
return this.username;
}
constructor(userjson:userjson,owner:Localuser,dontclone=false){
super(userjson.id);
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"){
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<any>=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 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);
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);
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};