update instancejson more
MY HEAD HURTS NOW ;( . . more work Finish rewrite all finished
164
webpage/audio.ts
@@ -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};
|
1204
webpage/channel.ts
@@ -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};
|
@@ -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};
|
@@ -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();
|
391
webpage/embed.ts
@@ -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};
|
230
webpage/emoji.ts
@@ -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};
|
Before Width: | Height: | Size: 906 B |
145
webpage/file.ts
@@ -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};
|
617
webpage/guild.ts
@@ -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 };
|
@@ -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>
|
@@ -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);
|
||||
}
|
||||
});
|
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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>
|
229
webpage/index.ts
@@ -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");
|
||||
};
|
||||
}
|
||||
})();
|
@@ -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};
|
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
@@ -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>
|
@@ -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);
|
||||
})();
|
@@ -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};
|
1477
webpage/localuser.ts
@@ -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>
|
454
webpage/login.ts
@@ -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("");
|
||||
}
|
||||
});
|
@@ -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 |
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "Jank Client",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.svg",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url":"/channels/@me",
|
||||
"display":"standalone",
|
||||
"theme_color":"#05050a"
|
||||
}
|
@@ -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};
|
@@ -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};
|
@@ -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 };
|
@@ -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};
|
@@ -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>
|
@@ -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;
|
169
webpage/role.ts
@@ -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};
|
@@ -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);
|
||||
}
|
||||
});
|
@@ -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};
|
||||
|
@@ -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};
|
2208
webpage/style.css
@@ -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;
|
||||
}
|
433
webpage/user.ts
@@ -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};
|