Merge remote-tracking branch 'upstream/main'

This commit is contained in:
ygg2 2024-07-24 08:43:31 -04:00
commit 9005d2554f
28 changed files with 2304 additions and 1254 deletions

View file

@ -3,14 +3,14 @@ import { Message } from "./message.js";
import {Voice} from "./audio.js";
import {Contextmenu} from "./contextmenu.js";
import {Fullscreen} from "./fullscreen.js";
import {markdown} from "./markdown.js";
import {Guild} from "./guild.js";
import { Localuser } from "./localuser.js";
import { Permissions } from "./permissions.js";
import { Settings, RoleList } from "./settings.js";
import { Role } from "./role.js";
import {InfiniteScroller} from "./infiniteScroller.js"
Settings;
import {InfiniteScroller} from "./infiniteScroller.js";
import { SnowFlake } from "./snowflake.js";
declare global {
interface NotificationOptions {
image?: string
@ -22,30 +22,30 @@ class Channel{
owner:Guild;
headers:Localuser["headers"];
name:string;
id:string;
parent_id:string;
id:SnowFlake<Channel>;
parent_id:SnowFlake<Channel>;
parent:Channel;
children:Channel[];
guild_id:string;
messageids:{[key : string]:Message};
permission_overwrites:{[key:string]:Permissions};
permission_overwritesar:[string,Permissions][]
messageids:Map<SnowFlake<Message>,Message>;
permission_overwrites:Map<string,Permissions>;
permission_overwritesar:[SnowFlake<Role>,Permissions][]
topic:string;
nsfw:boolean;
position:number;
lastreadmessageid:string;
lastmessageid:string;
lastreadmessageid:SnowFlake<Message>;
lastmessageid:SnowFlake<Message>;
mentions:number;
lastpin:string;
move_id:string;
move_id:SnowFlake<Channel>;
typing:number;
message_notifications:number;
allthewayup:boolean;
static contextmenu=new Contextmenu("channel menu");
replyingto:Message;
infinite:InfiniteScroller;
idToPrev:{[key:string]:string}={};
idToNext:{[key:string]:string}={};
idToPrev:Map<SnowFlake<Message>,SnowFlake<Message>>=new Map();
idToNext:Map<SnowFlake<Message>,SnowFlake<Message>>=new Map();
static setupcontextmenu(){
this.contextmenu.addbutton("Copy channel id",function(){
console.log(this)
@ -87,26 +87,28 @@ class Channel{
}
setUpInfiniteScroller(){
const ids:{[key:string]:Function}={};
this.infinite=new InfiniteScroller(async function(id:string,offset:number){
this.infinite=new InfiniteScroller(async function(this:Channel,id:string,offset:number):Promise<string>{
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
if(offset===1){
if(this.idToPrev[id]){
return this.idToPrev[id];
if(this.idToPrev.get(snowflake)){
return this.idToPrev.get(snowflake)?.id;
}else{
await this.grabmoremessages(id);
return this.idToPrev[id];
await this.grabBefore(id);
return this.idToPrev.get(snowflake)?.id;
}
}else{
return this.idToNext[id];
return this.idToNext.get(snowflake)?.id;
}
}.bind(this),
function(this:Channel,id:string){
let res:Function;
const promise=new Promise(_=>{res=_;}) as Promise<void>;
const html=this.messageids[id].buildhtml(this.messageids[this.idToPrev[id]],promise);
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
const html=this.messageids.get(snowflake).buildhtml(this.messageids.get(this.idToPrev.get(snowflake)),promise);
ids[id]=res;
return html;
}.bind(this),
async function(id:string){
async function(this:Channel,id:string){
ids[id]();
delete ids[id];
return true;
@ -125,26 +127,25 @@ class Channel{
this.owner=owner;
this.headers=this.owner.headers;
this.name=JSON.name;
this.id=JSON.id;
this.parent_id=JSON.parent_id;
this.id=new SnowFlake(JSON.id,this);
this.parent_id=new SnowFlake(JSON.parent_id,undefined);
this.parent=null;
this.children=[];
this.guild_id=JSON.guild_id;
this.messageids={};
this.permission_overwrites={};
this.messageids=new Map();
this.permission_overwrites=new Map();
this.permission_overwritesar=[];
for(const thing of JSON.permission_overwrites){
console.log(thing);
if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){continue;};
this.permission_overwrites[thing.id]=new Permissions(thing.allow,thing.deny);
this.permission_overwritesar.push([thing.id,this.permission_overwrites[thing.id]]);
this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny));
this.permission_overwritesar.push([thing.id,this.permission_overwrites.get(thing.id)]);
}
this.topic=JSON.topic;
this.nsfw=JSON.nsfw;
this.position=JSON.position;
this.lastreadmessageid=null;
this.lastmessageid=JSON.last_message_id;
this.lastmessageid=SnowFlake.getSnowFlakeFromID(JSON.last_message_id,Message);
this.setUpInfiniteScroller();
}
isAdmin(){
@ -160,22 +161,22 @@ class Channel{
return this.owner.info;
}
readStateInfo(json){
this.lastreadmessageid=json.last_message_id;
this.lastreadmessageid=SnowFlake.getSnowFlakeFromID(json.last_message_id,Message);
this.mentions=json.mention_count;
this.mentions??=0;
this.lastpin=json.last_pin_timestamp;
}
get hasunreads():boolean{
if(!this.hasPermission("VIEW_CHANNEL")){return false;}
return this.lastmessageid!==this.lastreadmessageid&&this.type!==4;
return this.lastmessageid!==this.lastreadmessageid&&this.type!==4&&!!this.lastmessageid.id;
}
hasPermission(name:string,member=this.guild.member):boolean{
if(member.isAdmin()){
return true;
}
for(const thing of member.roles){
if(this.permission_overwrites[thing.id]){
let perm=this.permission_overwrites[thing.id].getPermission(name);
if(this.permission_overwrites.get(thing.id.id)){
let perm=this.permission_overwrites.get(thing.id.id).getPermission(name);
if(perm){
return perm===1;
}
@ -196,7 +197,7 @@ class Channel{
this.children.sort((a,b)=>{return a.position-b.position});
}
resolveparent(guild:Guild){
this.parent=guild.channelids[this.parent_id];
this.parent=guild.channelids[this.parent_id?.id];
this.parent??=null;
if(this.parent!==null){
this.parent.children.push(this);
@ -354,7 +355,7 @@ class Channel{
if(!this.hasunreads){
return;
}
fetch(this.info.api.toString()+"/v9/channels/"+this.id+"/messages/"+this.lastmessageid+"/ack",{
fetch(this.info.api.toString()+"/channels/"+this.id+"/messages/"+this.lastmessageid+"/ack",{
method:"POST",
headers:this.headers,
body:JSON.stringify({})
@ -445,8 +446,8 @@ class Channel{
["textbox","Channel name:",this.name,function(){name=this.value}],
["mdbox","Channel topic:",this.topic,function(){topic=this.value}],
["checkbox","NSFW Channel",this.nsfw,function(){nsfw=this.checked}],
["button","","submit",function(){
fetch(this.info.api.toString()+"/v9/channels/"+thisid,{
["button","","submit",()=>{
fetch(this.info.api.toString()+"/channels/"+thisid,{
method:"PATCH",
headers:this.headers,
body:JSON.stringify({
@ -470,7 +471,7 @@ class Channel{
console.log(full)
}
deleteChannel(){
fetch(this.info.api.toString()+"/v9/channels/"+this.id,{
fetch(this.info.api.toString()+"/channels/"+this.id,{
method:"DELETE",
headers:this.headers
})
@ -508,10 +509,11 @@ class Channel{
}
}
async getmessage(id:string):Promise<Message>{
if(this.messageids[id]){
return this.messageids[id];
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
if(snowflake.getObject()){
return snowflake.getObject();
}else{
const gety=await fetch(this.info.api.toString()+"/v9/channels/"+this.id+"/messages?limit=1&around="+id,{headers:this.headers})
const gety=await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=1&around="+id,{headers:this.headers})
const json=await gety.json();
return new Message(json[0],this);
}
@ -539,13 +541,15 @@ class Channel{
history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.id);
document.getElementById("channelname").textContent="#"+this.name;
console.log(this);
(document.getElementById("typebox") as HTMLInputElement).disabled=!this.canMessage;
(document.getElementById("typebox") as HTMLInputElement).contentEditable=""+this.canMessage;
}
lastmessage:Message;
async putmessages(){
if(this.allthewayup){return};
const j=await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=100",{
headers: this.headers,
})
});
const response=await j.json();
if(response.length!==100){
this.allthewayup=true;
@ -554,12 +558,14 @@ class Channel{
for(const thing of response){
const message=new Message(thing,this);
if(prev){
this.idToNext[message.id]=prev.id;
this.idToPrev[prev.id]=message.id;
this.idToNext.set(message.id,prev.id);
this.idToPrev.set(prev.id,message.id);
}else{
this.lastmessage=message;
}
prev=message;
if(this.messageids[message.id]===undefined){
this.messageids[message.id]=message;
if(this.messageids.get(message.id)===undefined){
this.messageids.set(message.id,message);
}
}
}
@ -572,7 +578,7 @@ class Channel{
}
this.children=build;
}
async grabmoremessages(id:string){
async grabBefore(id:string){
if(this.allthewayup){
return;
}
@ -584,7 +590,7 @@ class Channel{
if(response.length===0){
this.allthewayup=true;
}
let previd=id;
let previd=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
for(const i in response){
let messager:Message;
if(!next){
@ -598,11 +604,11 @@ class Channel{
next=undefined;
console.log("ohno",+i+1);
}
if(this.messageids[messager.id]===undefined){
this.idToNext[messager.id]=previd;
this.idToPrev[previd]=messager.id;
if(this.messageids.get(messager.id)===undefined){
this.idToNext.set(messager.id,previd);
this.idToPrev.set(previd,messager.id);
previd=messager.id;
this.messageids[messager.id]=messager;
this.messageids.set(messager.id,messager);
}else{
console.log("How???")
}
@ -618,17 +624,42 @@ class Channel{
buildmessages(){
const messages=document.getElementById("channelw");
messages.innerHTML="";
messages.append(this.infinite.getDiv(this.lastmessageid));
let id:SnowFlake<Message>;
if(this.messageids.get(this.lastreadmessageid)){
id=this.lastreadmessageid;
}else if(this.lastmessage.id){
id=this.goBackIds(this.lastmessage.id,50);
console.log("shouldn't")
}
messages.append(this.infinite.getDiv(id.id));
}
private goBackIds(id:SnowFlake<Message>,back:number):SnowFlake<Message>{
while(back!==0){
const nextid=this.idToPrev.get(id);
if(nextid){
id=nextid;
console.log(id);
back--;
}else{
break;
}
}
return id;
}
updateChannel(JSON){
this.type=JSON.type;
this.name=JSON.name;
this.parent_id=JSON.parent_id;
this.parent_id=new SnowFlake(JSON.parent_id,undefined);
this.parent=null;
this.children=[];
this.guild_id=JSON.guild_id;
this.messageids={};
this.permission_overwrites=JSON.permission_overwrites;
this.messageids=new Map();
this.permission_overwrites=new Map();
for(const thing of JSON.permission_overwrites){
if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){continue;};
this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny));
this.permission_overwritesar.push([thing.id,this.permission_overwrites.get(thing.id)]);
}
this.topic=JSON.topic;
this.nsfw=JSON.nsfw;
}
@ -662,9 +693,9 @@ class Channel{
if(replyingto){
replyjson=
{
"guild_id":replyingto.guild.id,
"channel_id": replyingto.channel.id,
"message_id": replyingto.id,
"guild_id":replyingto.guild.id.id,
"channel_id": replyingto.channel.id.id,
"message_id": replyingto.id.id,
};
};
if(attachments.length===0){
@ -707,10 +738,11 @@ class Channel{
messageCreate(messagep:any):void{
if(!this.hasPermission("VIEW_CHANNEL")){return}
const messagez=new Message(messagep.d,this);
this.idToNext[this.lastmessageid]=messagez.id;
this.idToPrev[messagez.id]=this.lastmessageid;
console.log(this.lastmessageid,messagez.id,":3");
this.idToNext.set(this.lastmessageid,messagez.id);
this.idToPrev.set(messagez.id,this.lastmessageid);
this.lastmessageid=messagez.id;
this.messageids[messagez.id]=messagez;
this.messageids.set(messagez.id,messagez);
if(messagez.author===this.localuser.user){
this.lastreadmessageid=messagez.id;
if(this.myhtml){
@ -744,10 +776,10 @@ class Channel{
if (!("Notification" in window)) {
} else if (Notification.permission === "granted") {
let noticontent=markdown(message.content).textContent;
let noticontent=message.content.textContent;
if(message.embeds[0]){
noticontent||=message.embeds[0].json.title;
noticontent||=markdown(message.embeds[0].json.description).textContent;
noticontent||=message.content.textContent;
}
noticontent||="Blank Message";
let imgurl=null;
@ -785,11 +817,11 @@ class Channel{
})
})
const perm=new Permissions("0","0");
this.permission_overwrites[role.id]=perm;
this.permission_overwrites.set(role.id.id,perm);
this.permission_overwritesar.push([role.id,perm]);
}
async updateRolePermissions(id:string,perms:Permissions){
const permission=this.permission_overwrites[id];
const permission=this.permission_overwrites.get(id);
permission.allow=perms.allow;
permission.deny=perms.deny;
await fetch(this.info.api.toString()+"/channels/"+this.id+"/permissions/"+id,{

View file

@ -4,6 +4,7 @@ import { Message } from "./message.js";
import { Localuser } from "./localuser.js";
import {User} from "./user.js";
import { Member } from "./member.js";
import { SnowFlake } from "./snowflake.js";
class Direct extends Guild{
constructor(JSON,owner:Localuser){
@ -17,16 +18,16 @@ class Direct extends Guild{
this.headers=this.localuser.headers;
this.channels=[];
this.channelids={};
this.id="@me";
this.id=new SnowFlake("@me",this);
this.properties={};
this.roles=[];
this.roleids={};
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.channelids[temp.id.id]=temp;
}
this.headchannels=this.channels;
}
@ -39,7 +40,7 @@ class Direct extends Guild{
}
sortchannels(){
this.headchannels.sort((a,b)=>{
const result=(BigInt(a.lastmessageid)-BigInt(b.lastmessageid));
const result=(a.lastmessageid.getUnixTime()-b.lastmessageid.getUnixTime());
return Number(-result);
});
}
@ -74,15 +75,15 @@ class Group extends Channel{
this.user=this.localuser.user;
}
this.name??=this.localuser.user.username;
this.id=JSON.id;
this.id=new SnowFlake(JSON.id,this);
this.parent_id=null;
this.parent=null;
this.children=[];
this.guild_id="@me";
this.messageids={};
this.permission_overwrites={};
this.lastmessageid=JSON.last_message_id;
this.lastmessageid??="0";
this.messageids=new Map();
this.permission_overwrites=new Map();
this.lastmessageid=SnowFlake.getSnowFlakeFromID(JSON.last_message_id,Message);
this.lastmessageid??=new SnowFlake("0",undefined);
this.mentions=0;
this.setUpInfiniteScroller();
}
@ -118,10 +119,10 @@ class Group extends Channel{
}
messageCreate(messagep){
const messagez=new Message(messagep.d,this);
this.idToNext[this.lastmessageid]=messagez.id;
this.idToPrev[messagez.id]=this.lastmessageid;
this.idToNext.set(this.lastmessageid,messagez.id);
this.idToPrev.set(messagez.id,this.lastmessageid);
this.lastmessageid=messagez.id;
this.messageids[messagez.id]=messagez;
this.messageids.set(messagez.id,messagez);
if(messagez.author===this.localuser.user){
this.lastreadmessageid=messagez.id;
if(this.myhtml){

View file

@ -1,6 +1,6 @@
import {Fullscreen} from "./fullscreen.js";
import {Message} from "./message.js";
import {markdown} from "./markdown.js";
import {MarkDown} from "./markdown.js";
class Embed{
type:string;
@ -29,6 +29,18 @@ class Embed{
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(){
console.log(this.json)
const div=document.createElement("div");
@ -59,7 +71,7 @@ class Embed{
embed.append(authorline);
}
const title=document.createElement("a");
title.append(markdown(this.json.title));
title.append(new MarkDown(this.json.title,this.channel).makeHTML());
if(this.json.url){
title.href=this.json.url;
}
@ -68,7 +80,7 @@ class Embed{
if(this.json.description){
const p=document.createElement("p");
p.append(markdown(this.json.description));
p.append(new MarkDown(this.json.description,this.channel).makeHTML());
embed.append(p);
}
@ -80,7 +92,7 @@ class Embed{
b.textContent=thing.name;
div.append(b);
const p=document.createElement("p")
p.append(markdown(thing.value));
p.append(new MarkDown(thing.value,this.channel).makeHTML());
p.classList.add("embedp");
div.append(p);

View file

@ -6,15 +6,16 @@ import {Fullscreen} from "./fullscreen.js";
import {Member} from "./member.js";
import {Settings,RoleList} from "./settings.js";
import {Permissions} from "./permissions.js";
import { SnowFlake } from "./snowflake.js";
class Guild{
owner:Localuser;
headers:Localuser["headers"];
channels:Channel[];
channelids:{[key:string]:Channel};
id:string;
id:SnowFlake<Guild>;
properties
roles:Role[];
roleids:{[key:string]:Role};
roleids:Map<SnowFlake<Role>,Role>;
prevchannel:Channel;
message_notifications:number;
headchannels:Channel[];
@ -75,30 +76,30 @@ class Guild{
s1.options.push(new RoleList(permlist,this,this.updateRolePermissions.bind(this)));
settings.show();
}
constructor(JSON,owner:Localuser,member){
if(JSON===-1){
constructor(json,owner:Localuser,member){
if(json===-1){
return;
}
this.owner=owner;
this.headers=this.owner.headers;
this.channels=[];
this.channelids={};
this.id=JSON.id;
this.properties=JSON.properties;
this.id=new SnowFlake(json.id,this);
this.properties=json.properties;
this.roles=[];
this.roleids={};
this.roleids=new Map();
this.prevchannel=undefined;
this.message_notifications=0;
for(const roley of JSON.roles){
for(const roley of json.roles){
const roleh=new Role(roley,this);
this.roles.push(roleh)
this.roleids[roleh.id]=roleh;
this.roleids.set(roleh.id,roleh);
}
Member.resolve(member,this).then(_=>this.member=_);
for(const thing of JSON.channels){
for(const thing of json.channels){
const temp=new Channel(thing,this);
this.channels.push(temp);
this.channelids[temp.id]=temp;
this.channelids[temp.id.id]=temp;
}
this.headchannels=[];
for(const thing of this.channels){
@ -128,7 +129,7 @@ class Guild{
headers:this.headers,
body:JSON.stringify({
"guilds":{
[this.id]:{
[this.id.id]:{
"message_notifications": noti
}
}
@ -247,7 +248,7 @@ class Guild{
const noti=document.createElement("div");
noti.classList.add("unread");
divy.append(noti);
this.localuser.guildhtml[this.id]=divy;
this.localuser.guildhtml[this.id.id]=divy;
if(this.properties.icon!=null){
const img=document.createElement("img");
img.classList.add("pfp","servericon");
@ -373,16 +374,12 @@ class Guild{
body:JSON.stringify(build)
})
}
getRole(ID:string):Role{
if(!this.roleids[ID]){console.error(`role id ${ID} does not exist`,this.roleids)}
return this.roleids[ID];
}
hasRole(r:Role|string){
console.log("this should run");
if((typeof r)!==(typeof "")){
r=(r as Role).id;
if(r instanceof Role){
r=r.id.id;
}
return this.member.hasRole(r as string);
return this.member.hasRole(r);
}
loadChannel(ID:string=undefined){
if(ID&&this.channelids[ID]){
@ -402,10 +399,10 @@ class Guild{
}
}
loadGuild(){
this.localuser.loadGuild(this.id);
this.localuser.loadGuild(this.id.id);
}
updateChannel(JSON){
this.channelids[JSON.id].updateChannel(JSON);
SnowFlake.getSnowFlakeFromID(JSON.id,Channel).getObject().updateChannel(JSON);
this.headchannels=[];
for(const thing of this.channels){
thing.children=[];
@ -516,7 +513,7 @@ class Guild{
})
const json=await fetched.json();
const role=new Role(json,this);
this.roleids[role.id]=role;
this.roleids[role.id.id]=role;
this.roles.push(role);
return role;
}

View file

@ -37,7 +37,12 @@
<p id="status">STATUS</p>
</div>
</div>
<h2 id="settings"></h2>
<div id="user-actions">
<h2 id="settings"></h2>
<h2 id="connections">🔗</h2>
<h2 id="dev-portal">🤖</h2>
</div>
</div>
</div>
<div class="flexttb messageflex">
@ -53,7 +58,7 @@
<div id="pasteimage"></div>
<div id="replybox" class="hideReplyBox"></div>
<div id="typediv">
<textarea id="typebox"></textarea>
<div id="typebox" contentEditable="true"></div>
<div id="typing" class="hidden">
<p id="typingtext">typing</p>
<div class="loading-indicator">

View file

@ -122,7 +122,7 @@ async function enter(event){
if(event.key === "Enter"&&!event.shiftKey){
event.preventDefault();
if(channel.editing){
channel.editing.edit((typebox).value);
channel.editing.edit(markdown.rawString);
channel.editing=null;
}else{
replyingto= thisuser.channelfocus.replyingto;
@ -131,7 +131,7 @@ async function enter(event){
replyingto.div.classList.remove("replying");
}
thisuser.channelfocus.replyingto=null;
channel.sendMessage(typebox.value,{
channel.sendMessage(markdown.rawString,{
attachments:images,
replyingto:replying,
})
@ -141,12 +141,15 @@ async function enter(event){
images.pop();
pasteimage.removeChild(imageshtml.pop());
}
typebox.value="";
typebox.innerHTML="";
return;
}
}
const typebox=document.getElementById("typebox") as HTMLInputElement;
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();
@ -166,6 +169,7 @@ const images:Blob[]=[];
const imageshtml=[];
import { File } from "./file.js";
import { MarkDown } from "./markdown.js";
document.addEventListener('paste', async (e) => {
Array.from(e.clipboardData.files).forEach(async (f) => {
const file=File.initFromBlob(f);
@ -183,6 +187,14 @@ function userSettings(){
thisuser.usersettings.show();
}
document.getElementById("settings").onclick=userSettings;
function userConnections(){
thisuser.userConnections.show();
}
document.getElementById("connections").onclick=userConnections;
function devPortal(){
thisuser.devPortal.show();
}
document.getElementById("dev-portal").onclick=devPortal;
if(mobile){
document.getElementById("channelw").onclick=function(){

View file

@ -4,9 +4,11 @@ import {Direct} from "./direct.js";
import {Voice} from "./audio.js";
import {User} from "./user.js";
import {Member} from "./member.js";
import {markdown} from "./markdown.js";
import {MarkDown} from "./markdown.js";
import {Fullscreen} from "./fullscreen.js";
import {setTheme, Specialuser} from "./login.js";
import { SnowFlake } from "./snowflake.js";
import { Message } from "./message.js";
const wsCodesRetry=new Set([4000,4003,4005,4007,4008,4009]);
@ -19,14 +21,16 @@ class Localuser{
info;
headers:{"Content-type":string,Authorization:string};
usersettings:Fullscreen;
userConnections:Fullscreen;
devPortal:Fullscreen;
ready;
guilds:Guild[];
guildids:{ [key: string]: Guild };
guildids:Map<string,Guild>;
user:User;
status:string;
channelfocus:Channel;
lookingguild:Guild;
guildhtml:Record<string, HTMLDivElement>;
guildhtml:Map<string, HTMLDivElement>;
ws:WebSocket;
typing:[string,number][];
wsinterval:NodeJS.Timeout;
@ -46,14 +50,14 @@ class Localuser{
this.initialized=true;
this.ready=ready;
this.guilds=[];
this.guildids={};
this.guildids=new Map();
this.user=new User(ready.d.user,this);
this.userinfo.username=this.user.username;
this.userinfo.pfpsrc=this.user.getpfpsrc();
this.status=this.ready.d.user_settings.status;
this.channelfocus=null;
this.lookingguild=null;
this.guildhtml={};
this.guildhtml=new Map();
const members={};
for(const thing of ready.d.merged_members){
members[thing[0].guild_id]=thing[0];
@ -62,12 +66,12 @@ class Localuser{
for(const thing of ready.d.guilds){
const temp=new Guild(thing,this,members[thing.id]);
this.guilds.push(temp);
this.guildids[temp.id]=temp;
this.guildids[temp.id.id]=temp;
}
{
const temp=new Direct(ready.d.private_channels,this);
this.guilds.push(temp);
this.guildids[temp.id]=temp;
this.guildids[temp.id.id]=temp;
}
console.log(ready.d.user_guild_settings.entries);
@ -84,14 +88,16 @@ class Localuser{
continue
}
const guildid=guild.id;
this.guildids[guildid].channelids[thing.channel_id].readStateInfo(thing);
this.guildids[guildid.id].channelids[thing.channel_id].readStateInfo(thing);
}
this.typing=[];
}
outoffocus():void{
document.getElementById("servers").textContent="";
document.getElementById("channels").textContent="";
this.channelfocus.infinite.delete();
if(this.channelfocus){
this.channelfocus.infinite.delete();
}
this.lookingguild=null;
this.channelfocus=null;
}
@ -100,7 +106,7 @@ class Localuser{
clearInterval(this.wsinterval);
this.outoffocus();
this.guilds=[];
this.guildids={};
this.guildids=new Map();
this.ws.close(4000)
}
async initwebsocket():Promise<void>{
@ -134,7 +140,7 @@ class Localuser{
this.ws.addEventListener('message', (event) => {
try{
const temp=JSON.parse(event.data);
console.log(temp)
if(temp.op==0){
@ -146,7 +152,7 @@ class Localuser{
break;
case "MESSAGE_DELETE":
console.log(temp.d);
this.guildids[temp.d.guild_id].channelids[temp.d.channel_id].messageids[temp.d.id].deleteEvent();
SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent();
break;
case "READY":
this.gottenReady(temp);
@ -154,7 +160,7 @@ class Localuser{
returny();
break;
case "MESSAGE_UPDATE":
const message=this.resolveChannelFromID(temp.d.channel_id).messageids[temp.d.id];
const message=SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject();
message.giveData(temp.d);
break;
case "TYPING_START":
@ -164,7 +170,7 @@ class Localuser{
break;
case "USER_UPDATE":
if(this.initialized){
const users=User.userids[temp.d.id];
const users=SnowFlake.getSnowFlakeFromID(temp.d.id,User).getObject() as User;
console.log(users,temp.d.id)
if(users){
users.userupdate(temp.d);
@ -198,7 +204,7 @@ class Localuser{
{
const guildy=new Guild(temp.d,this,this.user);
this.guilds.push(guildy);
this.guildids[guildy.id]=guildy;
this.guildids[guildy.id.id]=guildy;
document.getElementById("servers").insertBefore(guildy.generateGuildIcon(),document.getElementById("bottomseparator"));
}
}
@ -214,9 +220,7 @@ class Localuser{
}else if(temp.op!=11){
this.packets++
}
}catch(error){
console.error(error)
}
});
@ -260,15 +264,15 @@ class Localuser{
return undefined;
}
updateChannel(JSON):void{
this.guildids[JSON.guild_id].updateChannel(JSON);
if(JSON.guild_id===this.lookingguild.id){
SnowFlake.getSnowFlakeFromID(JSON.guild_id,Guild).getObject().updateChannel(JSON);
if(JSON.guild_id===this.lookingguild.id.id){
this.loadGuild(JSON.guild_id);
}
}
createChannel(JSON):void{
JSON.guild_id??="@me";
this.guildids[JSON.guild_id].createChannelpac(JSON);
if(JSON.guild_id===this.lookingguild.id){
SnowFlake.getSnowFlakeFromID(JSON.guild_id,Guild).getObject().createChannelpac(JSON);
if(JSON.guild_id===this.lookingguild.id.id){
this.loadGuild(JSON.guild_id);
}
}
@ -493,8 +497,8 @@ class Localuser{
unreads():void{
console.log(this.guildhtml)
for(const thing of this.guilds){
if(thing.id==="@me"){continue;}
thing.unreads(this.guildhtml[thing.id]);
if(thing.id.id==="@me"){continue;}
thing.unreads(this.guildhtml[thing.id.id]);
}
}
typingStart(typing):void{
@ -531,7 +535,7 @@ class Localuser{
reader.readAsDataURL(file);
console.log(this.headers);
reader.onload = ()=>{
fetch(this.info.api.toString()+"/v9/users/@me",{
fetch(this.info.api.toString()+"/users/@me",{
method:"PATCH",
headers:this.headers,
body:JSON.stringify({
@ -543,7 +547,7 @@ class Localuser{
}
updatepronouns(pronouns:string):void{
fetch(this.info.api.toString()+"/v9/users/@me/profile",{
fetch(this.info.api.toString()+"/users/@me/profile",{
method:"PATCH",
headers:this.headers,
body:JSON.stringify({
@ -620,13 +624,13 @@ class Localuser{
newprouns=this.value;
regen();
}],
["mdbox","Bio:",this.user.bio,function(e){
["mdbox","Bio:",this.user.bio.rawString,function(e){
console.log(this.value);
hypouser.bio=this.value;
newbio=this.value;
regen();
}],
["button","update user content:","submit",function(){
["button","update user content:","submit",()=>{
if(file!==null){
this.updatepfp(file);
}
@ -657,6 +661,262 @@ class Localuser{
newprouns=null;
newbio=null;
}.bind(this))
const connectionContainer=document.createElement("div");
connectionContainer.id="connection-container";
this.userConnections=new Fullscreen(
["html",
connectionContainer
], () => {}, async () => {
connectionContainer.innerHTML="";
const res=await fetch(this.info.api.toString()+"/v9/connections", {
headers: this.headers
});
const json=await res.json();
Object.keys(json).sort(key => json[key].enabled ? -1 : 1).forEach(key => {
const connection=json[key];
const container=document.createElement("div");
container.textContent=key.charAt(0).toUpperCase() + key.slice(1);
if (connection.enabled) {
container.addEventListener("click", async () => {
const connectionRes=await fetch(this.info.api.toString()+"/v9/connections/" + key + "/authorize", {
headers: this.headers
});
const connectionJSON=await connectionRes.json();
window.open(connectionJSON.url, "_blank", "noopener noreferrer");
})
} else {
container.classList.add("disabled")
container.title="This connection has been disabled server-side."
}
connectionContainer.appendChild(container);
})
}
);
let appName="";
const appListContainer=document.createElement("div");
appListContainer.id="app-list-container";
this.devPortal=new Fullscreen(
["vdiv",
["hdiv",
["textbox", "Name:", appName, event => {
appName=event.target.value;
}],
["button",
"",
"Create application",
async () => {
if (appName.trim().length == 0) return alert("Please enter a name for the application.");
const res=await fetch(this.info.api.toString()+"/v9/applications", {
method: "POST",
headers: this.headers,
body: JSON.stringify({
name: appName
})
});
const json=await res.json();
this.manageApplication(json.id);
this.devPortal.hide();
}
]
],
["html",
appListContainer
]
], () => {}, async () => {
appListContainer.innerHTML="";
const res=await fetch(this.info.api.toString()+"/v9/applications", {
headers: this.headers
});
const json=await res.json();
json.forEach(application => {
const container=document.createElement("div");
if (application.cover_image) {
const cover=document.createElement("img");
cover.crossOrigin="anonymous";
cover.src=this.info.cdn.toString()+"/app-icons/" + application.id + "/" + application.cover_image + ".png?size=256";
cover.alt="";
cover.loading="lazy";
container.appendChild(cover);
}
const name=document.createElement("h2");
name.textContent=application.name + (application.bot ? " (Bot)" : "");
container.appendChild(name);
container.addEventListener("click", async () => {
this.devPortal.hide();
this.manageApplication(application.id);
});
appListContainer.appendChild(container);
})
}
)
}
async manageApplication(appId="") {
const res=await fetch(this.info.api.toString()+"/v9/applications/" + appId, {
headers: this.headers
});
const json=await res.json();
const fields: any={};
const appDialog=new Fullscreen(
["vdiv",
["title",
"Editing " + json.name
],
["vdiv",
["textbox", "Application name:", json.name, event => {
fields.name=event.target.value;
}],
["mdbox", "Description:", json.description, event => {
fields.description=event.target.value;
}],
["vdiv",
json.icon ? ["img", this.info.cdn.toString()+"/app-icons/" + appId + "/" + json.icon + ".png?size=128", [128, 128]] : ["text", "No icon"],
["fileupload", "Application icon:", event => {
const reader=new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload=() => {
fields.icon=reader.result;
}
}]
]
],
["hdiv",
["textbox", "Privacy policy URL:", json.privacy_policy_url || "", event => {
fields.privacy_policy_url=event.target.value;
}],
["textbox", "Terms of Service URL:", json.terms_of_service_url || "", event => {
fields.terms_of_service_url=event.target.value;
}]
],
["hdiv",
["checkbox", "Make bot publicly inviteable?", json.bot_public, event => {
fields.bot_public=event.target.checked;
}],
["checkbox", "Require code grant to invite the bot?", json.bot_require_code_grant, event => {
fields.bot_require_code_grant=event.target.checked;
}]
],
["hdiv",
["button",
"",
"Save changes",
async () => {
const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId, {
method: "PATCH",
headers: this.headers,
body: JSON.stringify(fields)
});
if (updateRes.ok) appDialog.hide();
else {
const updateJSON=await updateRes.json();
alert("An error occurred: " + updateJSON.message);
}
}
],
["button",
"",
(json.bot ? "Manage" : "Add") + " bot",
async () => {
if (!json.bot) {
if (!confirm("Are you sure you want to add a bot to this application? There's no going back.")) return;
const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId + "/bot", {
method: "POST",
headers: this.headers
});
const updateJSON=await updateRes.json();
alert("Bot token:\n" + updateJSON.token);
}
appDialog.hide();
this.manageBot(appId);
}
]
]
]
)
appDialog.show();
}
async manageBot(appId="") {
const res=await fetch(this.info.api.toString()+"/v9/applications/" + appId, {
headers: this.headers
});
const json=await res.json();
if (!json.bot) return alert("For some reason, this application doesn't have a bot (yet).");
const fields: any={
username: json.bot.username,
avatar: json.bot.avatar ? (this.info.cdn.toString()+"/app-icons/" + appId + "/" + json.bot.avatar + ".png?size=256") : ""
};
const botDialog=new Fullscreen(
["vdiv",
["title",
"Editing bot: " + json.bot.username
],
["hdiv",
["textbox", "Bot username:", json.bot.username, event => {
fields.username=event.target.value
}],
["vdiv",
fields.avatar ? ["img", fields.avatar, [128, 128]] : ["text", "No avatar"],
["fileupload", "Bot avatar:", event => {
const reader=new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload=() => {
fields.avatar=reader.result;
}
}]
]
],
["hdiv",
["button",
"",
"Save changes",
async () => {
const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId + "/bot", {
method: "PATCH",
headers: this.headers,
body: JSON.stringify(fields)
});
if (updateRes.ok) botDialog.hide();
else {
const updateJSON=await updateRes.json();
alert("An error occurred: " + updateJSON.message);
}
}
],
["button",
"",
"Reset token",
async () => {
if (!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")) return;
const updateRes=await fetch(this.info.api.toString()+"/v9/applications/" + appId + "/bot/reset", {
method: "POST",
headers: this.headers
});
const updateJSON=await updateRes.json();
alert("New token:\n" + updateJSON.token);
botDialog.hide();
}
]
]
]
);
botDialog.show();
}
}
export {Localuser};

View file

@ -1,413 +1,531 @@
export {markdown};
function 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");
import { Channel } from "./channel";
import { Localuser } from "./localuser";
export {MarkDown};
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;
}
for(let i=0;i<txt.length;i++){
if(txt[i]==="\n"||i===0){
const first=i===0;
if(first){
i--;
get rawString(){
return this.txt.join("");
}
get textContent(){
return this.makeHTML().textContent;
}
makeHTML({keep=this.keep,stdsize=this.stdsize}={}){
return this.markdown(this.txt,{keep:keep,stdsize:stdsize});
}
markdown(text : string|string[],{keep=false,stdsize=false} = {}){
let txt : string[];
if((typeof text)===(typeof "")){
txt=(text as string).split("");
}else{
txt=(text as string[]);
}
if(txt===undefined){
txt=[];
}
const span=document.createElement("span");
let current=document.createElement("span");
function appendcurrent(){
if(current.innerHTML!==""){
span.append(current);
current=document.createElement("span");
}
let element=null;
let keepys="";
if(txt[i+1]==="#"){
console.log("test");
if(txt[i+2]==="#"){
if(txt[i+3]==="#"&&txt[i+4]===" "){
element=document.createElement("h3");
keepys="### ";
i+=5;
}else if(txt[i+3]===" "){
element=document.createElement("h2");
element.classList.add("h2md");
keepys="## ";
i+=4;
}
for(let i=0;i<txt.length;i++){
if(txt[i]==="\n"||i===0){
const first=i===0;
if(first){
i--;
}
let element=null;
let keepys="";
if(txt[i+1]==="#"){
console.log("test");
if(txt[i+2]==="#"){
if(txt[i+3]==="#"&&txt[i+4]===" "){
element=document.createElement("h3");
keepys="### ";
i+=5;
}else if(txt[i+3]===" "){
element=document.createElement("h2");
element.classList.add("h2md");
keepys="## ";
i+=4;
}
}else if(txt[i+2]===" "){
element=document.createElement("h1");
keepys="# ";
i+=3;
}
}else if(txt[i+2]===" "){
element=document.createElement("h1");
keepys="# ";
}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;
}
}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"));
if(keepys){
appendcurrent();
if(!first&&!stdsize){
span.appendChild(document.createElement("br"));
}
const build=[];
for(;txt[i]!=="\n"&&txt[i]!==undefined;i++){
build.push(txt[i]);
}
if(stdsize){
element=document.createElement("span");
}
if(keep){
element.append(keepys);
}
element.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
span.append(element);
i-=1;
console.log(txt[i]);
continue;
}
const build=[];
for(;txt[i]!=="\n"&&txt[i]!==undefined;i++){
build.push(txt[i]);
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){
element=document.createElement("span");
console.log(build);
build=build.replaceAll("\n","");
console.log(build,JSON.stringify(build));
}
if(keep){
element.append(keepys);
}
element.appendChild(markdown(build,{keep:keep,stdsize:stdsize}));
span.append(element);
i--;
continue;
}
if(first){
i++;
}
}
if(txt[i]==="\n"){
if(!stdsize){
appendcurrent();
span.append(document.createElement("br"));
}
continue;
}
if(txt[i]==="`"){
let count=1;
if(txt[i+1]==="`"){
count++;
if(txt[i+2]==="`"){
count++;
}
}
let build="";
if(keep){
build+="`".repeat(count);
}
let find=0;
let j=i+count;
let init=true;
for(;txt[j]!==undefined&&(txt[j]!=="\n"||count===3)&&find!==count;j++){
if(txt[j]==="`"){
find++;
}else{
if(find!==0){
build+="`".repeat(find);
find=0;
}
if(init&&count===3){
if(txt[j]===" "||txt[j]==="\n"){
init=false;
}
if(keep){
build+=txt[j];
}
continue;
}
build+=txt[j];
}
}
if(stdsize){
console.log(build);
build=build.replaceAll("\n","");
console.log(build,JSON.stringify(build));
}
if(find===count){
appendcurrent();
i=j;
if(keep){
build+="`".repeat(find);
}
if(count!==3&&!stdsize){
const samp=document.createElement("samp");
samp.textContent=build;
span.appendChild(samp);
}else{
const pre=document.createElement("pre");
if(build[build.length-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=[];
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(markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){i.append(stars)}
span.appendChild(i);
}else if(count===2){
const b=document.createElement("b");
if(keep){b.append(stars)}
b.appendChild(markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){b.append(stars)}
span.appendChild(b);
}else{
const b=document.createElement("b");
const i=document.createElement("i");
if(keep){b.append(stars)}
b.appendChild(markdown(build,{keep:keep,stdsize: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=[];
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(markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){i.append(underscores)}
span.appendChild(i);
}else if(count===2){
const u=document.createElement("u");
if(keep){u.append(underscores)}
u.appendChild(markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){u.append(underscores)}
span.appendChild(u);
}else{
const u=document.createElement("u");
const i=document.createElement("i");
if(keep){i.append(underscores)}
i.appendChild(markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){i.append(underscores)}
u.appendChild(i)
span.appendChild(u);
}
i--;
continue;
}
}
if(txt[i]==="~"&&txt[i+1]==="~"){
let count=2;
let build=[];
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;
const tildes="~~";
if(count===2){
const s=document.createElement("s");
if(keep){s.append(tildes)}
s.appendChild(markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){s.append(tildes)}
span.appendChild(s);
}
continue;
}
}
if(txt[i]==="|"&&txt[i+1]==="|"){
let count=2;
let build=[];
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;
const pipes="||";
if(count===2){
const j=document.createElement("j");
if(keep){j.append(pipes)}
j.appendChild(markdown(build,{keep:keep,stdsize:stdsize}));
j.classList.add("spoiler");
j.onclick=markdown.unspoil;
if(keep){j.append(pipes)}
span.appendChild(j);
}
continue;
}
}
if (txt[i]==="<" && txt[i + 1]==="t" && txt[i + 2]===":") {
let found=false;
const build=["<","t",":"];
let j = i+3;
for (; txt[j] !== void 0; j++) {
build.push(txt[j]);
if (txt[j]===">") {
found=true;
break;
}
}
if (found) {
appendcurrent();
i=j;
const parts=build.join("").match(/^<t:([0-9]{1,16})(:([tTdDfFR]))?>$/);
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 parts=build.join("").match(/^<(a)?:\w+:(\d{10,30})>$/);
if (parts && parts[2]) {
if(find===count){
appendcurrent();
i=j;
console.log(typeof txt,txt);
const isEmojiOnly = txt.join("").trim()===build.join("").trim();
const emojiElem=document.createElement("img");
emojiElem.classList.add("md-emoji");
emojiElem.width=isEmojiOnly ? 48 : 22;
emojiElem.height=isEmojiOnly ? 48 : 22;
emojiElem.crossOrigin="anonymous";
//emojiElem.src=this.info.cdn.toString() + "/emojis/" + parts[2] + "." + (parts[1] ? "gif" : "png") + "?size=32";
//must uncomment later
emojiElem.alt="";
emojiElem.loading="lazy";
span.appendChild(emojiElem);
if(keep){
build+="`".repeat(find);
}
if(count!==3&&!stdsize){
const samp=document.createElement("samp");
samp.textContent=build;
span.appendChild(samp);
}else{
const pre=document.createElement("pre");
if(build[build.length-1]==="\n"){
build=build.substring(0,build.length-1);
}
if(txt[i]==="\n"){
i++
}
pre.textContent=build;
span.appendChild(pre);
}
i--;
continue;
}
}
if(txt[i]==="*"){
let count=1;
if(txt[i+1]==="*"){
count++;
if(txt[i+2]==="*"){
count++;
}
}
let build=[];
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:keep,stdsize:stdsize}));
if(keep){i.append(stars)}
span.appendChild(i);
}else if(count===2){
const b=document.createElement("b");
if(keep){b.append(stars)}
b.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){b.append(stars)}
span.appendChild(b);
}else{
const b=document.createElement("b");
const i=document.createElement("i");
if(keep){b.append(stars)}
b.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){b.append(stars)}
i.appendChild(b);
span.appendChild(i);
}
i--
continue;
}
}
if(txt[i]==="_"){
let count=1;
if(txt[i+1]==="_"){
count++;
if(txt[i+2]==="_"){
count++;
}
}
let build=[];
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:keep,stdsize:stdsize}));
if(keep){i.append(underscores)}
span.appendChild(i);
}else if(count===2){
const u=document.createElement("u");
if(keep){u.append(underscores)}
u.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){u.append(underscores)}
span.appendChild(u);
}else{
const u=document.createElement("u");
const i=document.createElement("i");
if(keep){i.append(underscores)}
i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){i.append(underscores)}
u.appendChild(i)
span.appendChild(u);
}
i--;
continue;
}
}
if(txt[i]==="~"&&txt[i+1]==="~"){
let count=2;
let build=[];
let find=0;
let j=i+2;
for(;txt[j]!==undefined&&find!==count;j++){
if(txt[j]==="~"){
find++;
}else{
build.push(txt[j]);
if(find!==0){
build=build.concat(new Array(find).fill("~"));
find=0;
}
}
}
if(find===count){
appendcurrent();
i=j-1;
const tildes="~~";
if(count===2){
const s=document.createElement("s");
if(keep){s.append(tildes)}
s.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
if(keep){s.append(tildes)}
span.appendChild(s);
}
continue;
}
}
if(txt[i]==="|"&&txt[i+1]==="|"){
let count=2;
let build=[];
let find=0;
let j=i+2;
for(;txt[j]!==undefined&&find!==count;j++){
if(txt[j]==="|"){
find++;
}else{
build.push(txt[j]);
if(find!==0){
build=build.concat(new Array(find).fill("~"));
find=0;
}
}
}
if(find===count){
appendcurrent();
i=j-1;
const pipes="||";
if(count===2){
const j=document.createElement("j");
if(keep){j.append(pipes)}
j.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize}));
j.classList.add("spoiler");
j.onclick=MarkDown.unspoil;
if(keep){j.append(pipes)}
span.appendChild(j);
}
continue;
}
}
if (txt[i]==="<" && txt[i + 1]==="t" && txt[i + 2]===":") {
let found=false;
const build=["<","t",":"];
let j = i+3;
for (; txt[j] !== void 0; j++) {
build.push(txt[j]);
if (txt[j]===">") {
found=true;
break;
}
}
if (found) {
appendcurrent();
i=j;
const parts=build.join("").match(/^<t:([0-9]{1,16})(:([tTdDfFR]))?>$/);
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 emojiElem=document.createElement("img");
emojiElem.classList.add("md-emoji");
emojiElem.classList.add(isEmojiOnly ? "bigemoji" : "smallemoji");
emojiElem.crossOrigin="anonymous";
emojiElem.src=this.info.cdn.toString() + "emojis/" + parts[2] + "." + (parts[1] ? "gif" : "png") + "?size=32";
emojiElem.alt=buildjoin;
emojiElem.loading="lazy";
span.appendChild(emojiElem);
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=_=>{
console.log(_.clipboardData.types)
const data=_.clipboardData.getData("text");
document.execCommand('insertHTML', false, data);
_.preventDefault();
box.onkeyup(new KeyboardEvent("_"))
}
}
boxupdate(box:HTMLElement){
var restore = saveCaretPosition(box);
box.innerHTML="";
box.append(this.makeHTML({keep:true}))
restore();
}
static gatherBoxText(element:HTMLElement){
const children=element.childNodes;
if(element.tagName.toLowerCase()==="img"){
return (element as HTMLImageElement).alt;
}
if(element.tagName.toLowerCase()==="br"){
return "\n";
}
if(children.length!==0){
let build="";
for(const thing of children){
if(thing instanceof Text){
const text=thing.textContent;
build+=text;
continue;
}
const text=this.gatherBoxText(thing as HTMLElement);
if(text){
build+=text;
}
}
return build;
}
current.textContent+=txt[i];
}
appendcurrent();
return span;
}
markdown.unspoil=function(e:any) : void{
//console.log("undone")
e.target.classList.remove("spoiler")
e.target.classList.add("unspoiled")
}
//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div
function saveCaretPosition(context){
var selection = window.getSelection();
var range = selection.getRangeAt(0);
range.setStart( context, 0 );
var len = range.toString().length;
return function restore(){
var pos = getTextNodeAtPosition(context, len);
selection.removeAllRanges();
var range = new Range();
range.setStart(pos.node ,pos.position);
selection.addRange(range);
}
}
function getTextNodeAtPosition(root, index){
const NODE_TYPE = NodeFilter.SHOW_TEXT;
var treeWalker = document.createTreeWalker(root, NODE_TYPE, function next(elem) {
if(index > elem.textContent.length){
index -= elem.textContent.length;
return NodeFilter.FILTER_REJECT
}
return NodeFilter.FILTER_ACCEPT;
});
var c = treeWalker.nextNode();
return {
node: c? c: root,
position: index
};
}

View file

@ -2,6 +2,7 @@ import {User} from "./user.js";
import {Role} from "./role.js";
import {Guild} from "./guild.js";
import { Contextmenu } from "./contextmenu.js";
import { SnowFlake } from "./snowflake.js";
class Member{
static already={};
@ -38,7 +39,7 @@ class Member{
if(thing==="owner"){continue}
if(thing==="roles"){
for(const strrole of membery["roles"]){
const role=this.guild.getRole(strrole);
const role=SnowFlake.getSnowFlakeFromID(strrole,Role).getObject();
this.roles.push(role);
}
continue;
@ -65,44 +66,45 @@ class Member{
console.error(guild)
}
let user:User;
let id="";
let id:SnowFlake<User>;
if(unkown instanceof User){
user=unkown as User;
id=user.id;
}else if(typeof unkown===typeof ""){
id=unkown as string;
id=new SnowFlake(unkown as string,undefined);
}else{
return new Member(unkown,guild);
}
if(guild.id==="@me"){return null}
if(!Member.already[guild.id]){
Member.already[guild.id]={};
}else if(Member.already[guild.id][id]){
const memb=Member.already[guild.id][id]
if(guild.id.id==="@me"){return null}
if(!Member.already[guild.id.id]){
Member.already[guild.id.id]={};
}else if(Member.already[guild.id.id][id]){
const memb=Member.already[guild.id.id][id]
if(memb instanceof Promise){
return await memb;
}
return memb;
}
const promoise= fetch(guild.info.api.toString()+"/v9/users/"+id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.id,{headers:guild.headers}).then(_=>_.json()).then(json=>{
const promoise= fetch(guild.info.api.toString()+"/users/"+id+"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id="+guild.id,{headers:guild.headers}).then(_=>_.json()).then(json=>{
const memb=new Member(json,guild);
Member.already[guild.id][id]=memb;
Member.already[guild.id.id][id]=memb;
console.log("resolved")
return memb
})
Member.already[guild.id][id]=promoise;
Member.already[guild.id.id][id]=promoise;
try{
return await promoise
}catch(_){
const memb=new Member(user,guild,true);
Member.already[guild.id][id]=memb;
Member.already[guild.id.id][id]=memb;
return memb;
}
}
hasRole(ID:string){
console.log(this.roles,ID);
for(const thing of this.roles){
if(thing.id===ID){
if(thing.id.id===ID){
return true;
}
}
@ -123,7 +125,7 @@ class Member{
return true;
}
}
return this.guild.properties.owner_id===this.user.id;
return this.guild.properties.owner_id===this.user.id.id;
}
bind(html:HTMLElement){
if(html.tagName==="SPAN"){

View file

@ -1,12 +1,13 @@
import {Contextmenu} from "./contextmenu.js";
import {User} from "./user.js";
import {Member} from "./member.js";
import {markdown} from "./markdown.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";
class Message{
static contextmenu=new Contextmenu("message menu");
@ -17,11 +18,11 @@ class Message{
mentions:User[];
mention_roles:Role[];
attachments:File[];//probably should be its own class tbh, should be Attachments[]
id:string;
id:SnowFlake<Message>;
message_reference;
type:number;
timestamp:number;
content:string;
content:MarkDown;
static del:Promise<void>;
static resolve:Function;
div:HTMLDivElement;
@ -37,17 +38,19 @@ class Message{
}
static setupcmenu(){
Message.contextmenu.addbutton("Copy raw text",function(){
navigator.clipboard.writeText(this.content);
navigator.clipboard.writeText(this.content.rawString);
});
Message.contextmenu.addbutton("Reply",function(this:Message,div:HTMLDivElement){
this.channel.setReplying(this);
});
Message.contextmenu.addbutton("Copy message id",function(){
navigator.clipboard.writeText(this.id);
navigator.clipboard.writeText(this.id.id);
});
Message.contextmenu.addbutton("Edit",function(){
this.channel.editing=this;
(document.getElementById("typebox") as HTMLInputElement).value=this.content;
const markdown=(document.getElementById("typebox"))["markdown"] as MarkDown;
markdown.txt=this.content.rawString;
markdown.boxupdate(document.getElementById("typebox"));
},null,_=>{return _.author.id===_.localuser.user.id});
Message.contextmenu.addbutton("Delete message",function(){
this.delete();
@ -67,6 +70,12 @@ class Message{
this.attachments.push(new File(thing,this));
}
continue;
}else if(thing==="content"){
this.content=new MarkDown(messagejson[thing],this.channel);
continue;
}else if(thing ==="id"){
this.id=new SnowFlake(messagejson.id,this);
continue;
}
this[thing]=messagejson[thing];
}
@ -130,14 +139,14 @@ class Message{
return build;
}
async edit(content){
return await fetch(this.info.api.toString()+"/channels/"+this.channel.id+"/messages/"+this.id,{
return await fetch(this.info.api.toString()+"/channels/"+this.channel.id+"/messages/"+this.id.id,{
method: "PATCH",
headers: this.headers,
body:JSON.stringify({content:content})
});
}
delete(){
fetch(`${this.info.api.toString()}/channels/${this.channel.id}/messages/${this.id}`,{
fetch(`${this.info.api.toString()}/channels/${this.channel.id}/messages/${this.id.id}`,{
headers:this.headers,
method:"DELETE",
})
@ -147,19 +156,22 @@ class Message{
this.div.innerHTML="";
this.div=null;
}
const prev=this.channel.idToPrev[this.id];
const next=this.channel.idToNext[this.id];
const prev=this.channel.idToPrev[this.id.id];
const next=this.channel.idToNext[this.id.id];
this.channel.idToNext[prev]=next;
this.channel.idToPrev[next]=prev;
delete this.channel.messageids[this.id];
delete this.channel.messageids[this.id.id];
const regen=this.channel.messageids[prev]
if(regen){
regen.generateMessage();
}
if(this.channel.lastmessage===this){
this.channel.lastmessage=this.channel.messageids[prev];
}
}
generateMessage(premessage:Message=null){
if(!premessage){
premessage=this.channel.messageids[this.channel.idToNext[this.id]];
premessage=this.channel.messageids[this.channel.idToNext[this.id.id]];
}
const div=this.div;
if(this===this.channel.replyingto){
@ -210,7 +222,7 @@ class Message{
replyline.classList.add("replyflex")
this.channel.getmessage(this.message_reference.message_id).then(message=>{
const author=message.author;
reply.appendChild(markdown(message.content,{stdsize:true}));
reply.appendChild(message.content.makeHTML({stdsize:true}));
minipfp.src=author.getpfpsrc()
author.bind(minipfp);
username.textContent=author.username;
@ -271,7 +283,7 @@ class Message{
}else{
div.classList.remove("topMessage");
}
const messaged=markdown(this.content);
const messaged=this.content.makeHTML();
div["txt"]=messaged;
const messagedwrap=document.createElement("div");
messagedwrap.classList.add("flexttb")

View file

@ -2,11 +2,12 @@ export {Role};
import {Permissions} from "./permissions.js";
import {Localuser} from "./localuser.js";
import {Guild} from "./guild.js";
import { SnowFlake } from "./snowflake.js";
class Role{
permissions:Permissions;
owner:Guild;
color:number;
id:string;
readonly id:SnowFlake<Role>;
name:string;
info:Guild["info"];
hoist:boolean;
@ -18,6 +19,10 @@ class Role{
this.headers=owner.headers;
this.info=owner.info;
for(const thing of Object.keys(JSON)){
if(thing==="id"){
this.id=new SnowFlake(JSON.id,this);
continue;
}
this[thing]=JSON[thing];
}
this.permissions=new Permissions(JSON.permissions);

View file

@ -1,5 +1,7 @@
import { Permissions } from "./permissions.js";
import { Guild } from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { Role } from "./role.js";
class Buttons{
readonly name:string;
@ -126,7 +128,7 @@ class PermissionToggle{
}
}
class RoleList extends Buttons{
readonly permissions:[string,Permissions][];
readonly permissions:[SnowFlake<Role>,Permissions][];
permission:Permissions;
readonly guild:Guild;
readonly channel:boolean;
@ -134,7 +136,7 @@ class RoleList extends Buttons{
readonly options:Options;
onchange:Function;
curid:string;
constructor(permissions:[string,Permissions][],guild:Guild,onchange:Function,channel=false){
constructor(permissions:[SnowFlake<Role>,Permissions][],guild:Guild,onchange:Function,channel=false){
super("Roles");
this.guild=guild;
this.permissions=permissions;
@ -150,16 +152,17 @@ class RoleList extends Buttons{
options.addPermissionToggle(thing,this.permission);//
}
for(const i of permissions){
this.buttons.push([guild.getRole(i[0]).name,i[0]])//
console.log(i);
this.buttons.push([i[0].getObject().name,i[0].id])//
}
this.options=options;
}
handleString(str:string):HTMLElement{
this.curid=str;
const perm=this.permissions.find(_=>_[0]===str)[1];
const perm=this.permissions.find(_=>_[0].id===str)[1];
this.permission.deny=perm.deny;
this.permission.allow=perm.allow;
this.options.name=this.guild.getRole(str).name;
this.options.name=SnowFlake.getSnowFlakeFromID(str,Role).getObject().name;
this.options.haschanged=false;
return this.options.generateHTML();
}

53
webpage/snowflake.ts Normal file
View file

@ -0,0 +1,53 @@
class SnowFlake<x>{
public readonly id:string;
private static readonly SnowFlakes:Map<any,Map<string,WeakRef<SnowFlake<any>>>>=new Map();
private static readonly FinalizationRegistry=new FinalizationRegistry((id:string)=>{
SnowFlake.SnowFlakes.delete(id);
});
private obj:x;
constructor(id:string,obj:x){
if(!obj){
this.id=id;
return;
}
if(!SnowFlake.SnowFlakes.get(obj.constructor)){
SnowFlake.SnowFlakes.set(obj.constructor,new Map());
}
if(SnowFlake.SnowFlakes.get(obj.constructor).get(id)){
const snowflake=SnowFlake.SnowFlakes.get(obj.constructor).get(id).deref();
snowflake.obj=obj;
return snowflake;
}
this.id=id;
SnowFlake.SnowFlakes.get(obj.constructor).set(id,new WeakRef(this));
SnowFlake.FinalizationRegistry.register(this,id);
this.obj=obj;
}
static getSnowFlakeFromID(id:string,type:any):SnowFlake<any>{
if(!SnowFlake.SnowFlakes.get(type)){
SnowFlake.SnowFlakes.set(type,new Map());
}
const snowflake=SnowFlake.SnowFlakes.get(type).get(id);
if(snowflake){
return snowflake.deref();
}
{
const snowflake=new SnowFlake(id,undefined);
SnowFlake.SnowFlakes.get(type).set(id,new WeakRef(snowflake));
SnowFlake.FinalizationRegistry.register(snowflake,id);
return snowflake;
}
}
getUnixTime():number{
return Number((BigInt(this.id)>>22n)+1420070400000n);
}
toString(){
return this.id;
}
getObject():x{
return this.obj;
}
}
export {SnowFlake};

View file

@ -119,14 +119,17 @@ samp {
}
.infosection {
hr{
width:100%;
}
display: inline-block;
background: var(--profile-info-bg);
border-radius: 10%;
padding: .3cm;
width: 100%;
height: calc(100% - .5in - .2cm);
margin-top: .1cm;
box-sizing: border-box;
width: calc(100% - .6cm);
height: calc(100% - .75in);
display: flex;
flex-direction: column;
}
.profile {
@ -152,6 +155,7 @@ h1,
h2,
h3 {
margin: 0;
display: inline-block;
}
h2 {
@ -342,15 +346,16 @@ div {
#typebox {
font-family: "acumin-pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
/* font-size: 16px; */
padding: 3px;
border-radius: .25cm;
width: 100%;
height: .6in;
min-height: .5in;
z-index: -100;
display: flex;
max-height: fit-content;
box-sizing: border-box;
max-height: 1.5in;
overflow-y: scroll;
}
p {
@ -630,7 +635,11 @@ textarea {
textarea:focus-visible {
outline: 2px solid var(--focus);
}
#typebox{
color: var(--primary-text);
background: var(--textarea-bg);
border: 1px solid;
}
.channels {
overflow: auto;
transition: height .2s ease-in-out;
@ -731,22 +740,20 @@ textarea:focus-visible {
flex-shrink: 1;
}
#settings {
#user-actions {
display: flex;
flex-wrap: wrap;
}
#user-actions h2 {
cursor: pointer;
user-select: none;
border-radius: .3in;
transition: background 1s;
border-radius: .1in;
transition: color .5s;
text-align: center;
font-size: .25in;
width: .3in;
height: .3in;
overflow: visible;
}
#settings:hover {
background: var(--settings-hover);
cursor: pointer;
user-select: none;
#user-actions h2:hover, #user-actions h2:focus {
color: var(--timestamp-color);
}
#userinfo {
@ -1402,6 +1409,41 @@ span {
flex-direction: column;
max-height:100in;
}
#connection-container, #app-list-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
max-width: 700px;
}
#connection-container div, #app-list-container div {
padding: 5px 10px;
border-radius: 5px;
background-color: var(--textarea-bg);
cursor: pointer;
}
#connection-container .disabled {
background-color: var(--embed-fallback);
cursor: not-allowed;
}
.sizeupdown{
height:4in;
}
.bigemoji{
width:48px;
height:48px;
}
.smallemoji{
width:22px;
height:22px;
}
#typebox[contenteditable=false]{
cursor:not-allowed;
}
#typebox[contenteditable=false]:before{
content:'You can\'t chat here';
color:color-mix(in hsl, var(--primary-bg),var(--primary-text));
}

View file

@ -1,31 +1,32 @@
//const usercache={};
import {Member} from "./member.js";
import {markdown} from "./markdown.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";
class User{
static userids={};
owner:Localuser;
hypotheticalpfp:boolean;
id:string;
id:SnowFlake<User>;
avatar:string;
username:string;
bio:string;
bio:MarkDown;
discriminator:string;
pronouns:string;
bot:boolean;
static contextmenu:Contextmenu=new Contextmenu("User Menu");
static setUpContextMenu(){
this.contextmenu.addbutton("Copy user id",function(){
navigator.clipboard.writeText(this.id);
navigator.clipboard.writeText(this.id.id);
});
this.contextmenu.addbutton("Message user",function(){
fetch(this.info.api.toString()+"/v9/users/@me/channels",
{method:"POST",
body:JSON.stringify({"recipients":[this.id]}),
headers: this.headers
body:JSON.stringify({"recipients":[this.id.id]}),
headers: this.localuser.headers
});
})
}
@ -49,6 +50,14 @@ class User{
if(!owner){console.error("missing localuser")}
if(dontclone){
for(const thing of Object.keys(userjson)){
if(thing==="bio"){
this.bio=new MarkDown(userjson[thing],this.localuser);
continue;
}
if(thing === "id"){
this.id=new SnowFlake(userjson[thing],this);
continue;
}
this[thing]=userjson[thing];
}
this.hypotheticalpfp=false;
@ -63,7 +72,7 @@ class User{
const pfp=document.createElement('img');
pfp.src=this.getpfpsrc();
pfp.classList.add("pfp");
pfp.classList.add("userid:"+this.id);
pfp.classList.add("userid:"+this.id.id);
return pfp;
}
userupdate(json){
@ -73,7 +82,7 @@ class User{
}
}
bind(html:HTMLElement,guild:Guild=null){
if(guild&&guild.id!=="@me"){
if(guild&&guild.id.id!=="@me"){
Member.resolve(this,guild).then(_=>{
_.bind(html);
}).catch(_=>{
@ -94,7 +103,7 @@ class User{
this.hypotheticalpfp=false;
const src=this.getpfpsrc();
console.log(src)
for(const thing of document.getElementsByClassName("userid:"+this.id)){
for(const thing of document.getElementsByClassName("userid:"+this.id.id)){
(thing as HTMLImageElement).src=src;
}
}
@ -103,7 +112,7 @@ class User{
return this.avatar;
}
if(this.avatar!=null){
return this.info.cdn.toString()+"avatars/"+this.id+"/"+this.avatar+".png";
return this.info.cdn.toString()+"avatars/"+this.id.id+"/"+this.avatar+".png";
}else{
return this.info.cdn.toString()+"embed/avatars/3.png";
}
@ -152,7 +161,7 @@ class User{
const rule=document.createElement("hr");
userbody.appendChild(rule);
const biohtml=markdown(this.bio);
const biohtml=this.bio.makeHTML();
userbody.appendChild(biohtml);
}
console.log(div);