Merge branch 'main' of https://github.com/DEVTomatoCake/JankClient into jank/channel-topic

This commit is contained in:
TomatoCake 2024-08-20 15:54:02 +02:00
commit 4b745ded3f
25 changed files with 383 additions and 94 deletions

View file

@ -52,30 +52,30 @@ class Channel{
return this.snowflake.id;
}
static setupcontextmenu(){
this.contextmenu.addbutton("Copy channel id",function(){
this.contextmenu.addbutton("Copy channel id",function(this:Channel){
console.log(this)
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Mark as read",function(){
this.contextmenu.addbutton("Mark as read",function(this:Channel){
console.log(this)
this.readbottom();
});
this.contextmenu.addbutton("Settings[temp]",function(){
this.contextmenu.addbutton("Settings[temp]",function(this:Channel){
this.generateSettings();
});
this.contextmenu.addbutton("Delete channel",function(){
this.contextmenu.addbutton("Delete channel",function(this:Channel){
console.log(this)
this.deleteChannel();
},null,_=>{console.log(_);return _.isAdmin()});
this.contextmenu.addbutton("Edit channel",function(){
this.editChannel(this);
this.contextmenu.addbutton("Edit channel",function(this:Channel){
this.editChannel();
},null,_=>{return _.isAdmin()});
this.contextmenu.addbutton("Make invite",function(){
this.contextmenu.addbutton("Make invite",function(this:Channel){
this.createInvite();
},null,(_:Channel)=>{
return _.hasPermission("CREATE_INSTANT_INVITE")&&_.type!==4
@ -203,7 +203,9 @@ class Channel{
async function(this:Channel,id:string){
const message=SnowFlake.getSnowFlakeFromID(id,Message).getObject();
try{
message.deleteDiv();
if(message){
message.deleteDiv();
}
}catch(e){console.error(e)}finally{}
}.bind(this),
this.readbottom.bind(this)
@ -355,9 +357,9 @@ class Channel{
addchannel.textContent="+";
addchannel.classList.add("addchannel");
caps.appendChild(addchannel);
addchannel.onclick=function(){
addchannel.onclick=_=>{
this.guild.createchannels(this.createChannel.bind(this));
}.bind(this);
}
this.coatDropDiv(decdiv,childrendiv);
}
div.appendChild(caps)
@ -536,9 +538,9 @@ class Channel{
const full=new Dialog(
["hdiv",
["vdiv",
["textbox","Channel name:",this.name,function(){name=this.value}],
["mdbox","Channel topic:",this.topic,function(){topic=this.value}],
["checkbox","NSFW Channel",this.nsfw,function(){nsfw=this.checked}],
["textbox","Channel name:",this.name,function(this:HTMLInputElement){name=this.value}],
["mdbox","Channel topic:",this.topic,function(this:HTMLTextAreaElement){topic=this.value}],
["checkbox","NSFW Channel",this.nsfw,function(this:HTMLInputElement){nsfw=this.checked}],
["button","","submit",()=>{
fetch(this.info.api+"/channels/"+thisid,{
method:"PATCH",
@ -809,6 +811,9 @@ class Channel{
if(this.infinitefocus) return;
this.infinitefocus=true;
const messages=document.getElementById("channelw");
for(const thing of messages.getElementsByClassName("messagecontainer")){
thing.remove();
}
const loading=document.getElementById("loadingdiv");
const removetitle=document.getElementById("removetitle");
//messages.innerHTML="";

View file

@ -241,7 +241,7 @@ class Dialog{
this.background.classList.add("background");
document.body.appendChild(this.background);
document.body.appendChild(this.html);
this.background.onclick = function(){this.hide();}.bind(this);
this.background.onclick = _=>{this.hide()};
}
hide(){
document.body.removeChild(this.background);

View file

@ -111,6 +111,7 @@ class Group extends Channel{
this.lastmessageid??=new SnowFlake("0",undefined);
this.mentions=0;
this.setUpInfiniteScroller();
this.position=Math.max(this.lastmessageid.getUnixTime(),this.snowflake.getUnixTime());
}
createguildHTML(){
const div=document.createElement("div")

View file

@ -31,33 +31,33 @@ class Guild{
}
static contextmenu=new Contextmenu("guild menu");
static setupcontextmenu(){
Guild.contextmenu.addbutton("Copy Guild id",function(){
Guild.contextmenu.addbutton("Copy Guild id",function(this:Guild){
console.log(this)
navigator.clipboard.writeText(this.id);
});
Guild.contextmenu.addbutton("Mark as read",function(){
Guild.contextmenu.addbutton("Mark as read",function(this:Guild){
console.log(this)
this.markAsRead();
});
Guild.contextmenu.addbutton("Notifications",function(){
Guild.contextmenu.addbutton("Notifications",function(this:Guild){
console.log(this)
this.setnotifcation();
});
Guild.contextmenu.addbutton("Leave guild",function(){
Guild.contextmenu.addbutton("Leave guild",function(this:Guild){
this.confirmleave();
},null,function(_){return _.properties.owner_id!==_.member.user.id});
Guild.contextmenu.addbutton("Delete guild",function(){
Guild.contextmenu.addbutton("Delete guild",function(this:Guild){
this.confirmDelete();
},null,function(_){return _.properties.owner_id===_.member.user.id});
Guild.contextmenu.addbutton("Create invite",function(){
Guild.contextmenu.addbutton("Create invite",function(this:Guild){
console.log(this);
},null,_=>true,_=>false);
Guild.contextmenu.addbutton("Settings[temp]",function(){
Guild.contextmenu.addbutton("Settings[temp]",function(this:Guild){
this.generateSettings();
});
/* -----things left for later-----
@ -86,6 +86,9 @@ class Guild{
if(json===-1){
return;
}
if(json.stickers.length){
console.log(json.stickers,":3")
}
this.emojis = json.emojis
this.owner=owner;
this.headers=this.owner.headers;
@ -136,15 +139,12 @@ class Guild{
noti
],
["button","","submit",_=>{
fetch(this.info.api+"/users/@me/guilds/settings",{
//
fetch(this.info.api+`/users/@me/guilds/${this.id}/settings/`,{
method:"PATCH",
headers:this.headers,
body:JSON.stringify({
"guilds":{
[this.id]:{
"message_notifications": noti
}
}
"message_notifications": noti
})
})
this.message_notifications=noti;
@ -296,7 +296,7 @@ class Guild{
["textbox",
"Name of server:",
"",
function(){
function(this:HTMLInputElement){
confirmname=this.value;
}
]
@ -447,7 +447,7 @@ class Guild{
},
1
],
["textbox","Name of channel","",function(){
["textbox","Name of channel","",function(this:HTMLInputElement){
console.log(this)
name=this.value
}],
@ -464,11 +464,11 @@ class Guild{
let category=4;
const channelselect=new Dialog(
["vdiv",
["textbox","Name of category","",function(){
["textbox","Name of category","",function(this:HTMLInputElement){
console.log(this);
name=this.value;
}],
["button","","submit",function(){
["button","","submit",()=>{
console.log(name,category)
this.createChannel(name,category);
channelselect.hide();

View file

@ -95,6 +95,7 @@ function showAccountSwitcher(){
}
let thisuser:Localuser;
try{
console.log(users.users,users.currentuser)
thisuser=new Localuser(users.users[users.currentuser]);
thisuser.initwebsocket().then(_=>{
thisuser.loaduser();
@ -103,7 +104,8 @@ try{
document.getElementById("loading").classList.remove("loading");
console.log("done loading")
});
}catch{
}catch(e){
console.error(e);
document.getElementById("load-desc").textContent="Account unable to start";
thisuser=new Localuser(-1);
}
@ -164,13 +166,13 @@ typebox.addEventListener("keydown",event=>{
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=[];

View file

@ -163,8 +163,8 @@ class InfiniteScroller{
this.currrunning=true;
}
if(!this.div){this.currrunning=false;return}
const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()])
const changed=(out[0]||out[1]);
const out=await Promise.allSettled([this.watchForTop(),this.watchForBottom()]) as {value:boolean}[];
const changed=(out[0].value||out[1].value);
if(null===this.timeout&&changed){
this.timeout=setTimeout(this.updatestuff.bind(this),300);
}
@ -213,7 +213,7 @@ class InfiniteScroller{
await this.destroyFromID(thing[1]);
}
this.HTMLElements=[];
clearInterval(this.timeout);
clearTimeout(this.timeout);
if(this.div){
this.div.remove();
}

View file

@ -137,7 +137,7 @@ type userjson={
premium_type: number,
theme_colors: string,
pronouns: string,
badge_ids: string,
badge_ids: string[],
}
type memberjson= {
index?:number,

View file

@ -4,7 +4,7 @@ import {Direct} from "./direct.js";
import {Voice} from "./audio.js";
import {User} from "./user.js";
import {Dialog} from "./dialog.js";
import {getBulkInfo, setTheme, Specialuser} from "./login.js";
import {getapiurls, getBulkInfo, setTheme, Specialuser} from "./login.js";
import { SnowFlake } from "./snowflake.js";
import { Message } from "./message.js";
import { channeljson, guildjson, memberjson, presencejson, readyjson } from "./jsontypes.js";
@ -15,6 +15,7 @@ import { MarkDown } from "./markdown.js";
const wsCodesRetry=new Set([4000,4003,4005,4007,4008,4009]);
class Localuser{
badges:Map<string,{id:string,description:string,icon:string,link:string}>=new Map();
lastSequence:number|null=null;
token:string;
userinfo:Specialuser;
@ -214,7 +215,7 @@ class Localuser{
}
});
this.ws.addEventListener("close", event => {
this.ws.addEventListener("close",async event => {
this.ws=undefined;
console.log("WebSocket closed with code " + event.code);
@ -230,7 +231,43 @@ class Localuser{
this.connectionSucceed=0;
document.getElementById("load-desc").innerHTML="Unable to connect to the Spacebar server, retrying in <b>" + Math.round(0.2 + (this.errorBackoff*2.8)) + "</b> seconds...";
switch(this.errorBackoff){//try to recover from bad domain
case 3:
const newurls=await getapiurls(this.info.wellknown);
if(newurls){
this.info=newurls;
this.serverurls=newurls;
this.userinfo.json.serverurls=this.info;
this.userinfo.updateLocal();
break
}
case 4:
{
const newurls=await getapiurls(new URL(this.info.wellknown).origin);
if(newurls){
this.info=newurls;
this.serverurls=newurls;
this.userinfo.json.serverurls=this.info;
this.userinfo.updateLocal();
break
}
}
case 5:
{
const breakappart=new URL(this.info.wellknown).origin.split(".");
const url="https://"+breakappart[breakappart.length-2]+"."+breakappart[breakappart.length-1]
const newurls=await getapiurls(url);
if(newurls){
this.info=newurls;
this.serverurls=newurls;
this.userinfo.json.serverurls=this.info;
this.userinfo.updateLocal();
}
break
}
}
setTimeout(() => {
if(this.swapped) return;
document.getElementById("load-desc").textContent="Retrying...";
@ -497,7 +534,7 @@ class Localuser{
["textbox",
"Invite Link/Code",
"",
function(){
function(this:HTMLInputElement){
inviteurl=this.value;
}
],
@ -635,7 +672,8 @@ class Localuser{
}
async typingStart(typing):Promise<void>{
if(this.channelfocus.id===typing.d.channel_id){
const guild=SnowFlake.getSnowFlakeFromID(typing.d.guild_id,Guild).getObject()
const guild=this.guildids.get(typing.d.guild_id);
const memb=await Member.new(typing.d.member,guild);
if(memb.id===this.user.id){
console.log("you is typing")
@ -878,8 +916,8 @@ class Localuser{
["title","2FA set up"],
["text","Copy this secret into your totp(time-based one time password) app"],
["text",`Your secret is: ${secret} and it's 6 digits, with a 30 second token period`],
["textbox","Account password:","",function(){password=this.value}],
["textbox","Code:","",function(){code=this.value}],
["textbox","Account password:","",function(this:HTMLInputElement){password=this.value}],
["textbox","Code:","",function(this:HTMLInputElement){code=this.value}],
["button","","Submit",()=>{
fetch(this.info.api+"/users/@me/mfa/totp/enable/",{
method:"POST",

View file

@ -18,6 +18,39 @@ function getBulkUsers(){
}
return json;
}
function trimswitcher(){
const json=getBulkInfo()
const map=new Map();
for(const thing in json.users){
const user=json.users[thing];
console.log(user,json.users);
let wellknown=user.serverurls.wellknown;
if(wellknown[wellknown.length-1]!=="/"){
wellknown+="/";
}
wellknown+=user.username;
if(map.has(wellknown)){
const otheruser=map.get(wellknown);
if(otheruser[1].serverurls.wellknown[otheruser[1].serverurls.wellknown.length-1]==="/"){
delete json.users[otheruser[0]];
map.set(wellknown,[thing,user]);
}else{
delete json.users[thing];
}
}else{
map.set(wellknown,[thing,user]);
}
}
for(const thing in json.users){
if(thing[thing.length-1]==="/"){
const user=json.users[thing];
delete json.users[thing];
json.users[thing.slice(0, -1)]=user;
}
}
localStorage.setItem("userinfos",JSON.stringify(json));
console.log(json);
}
function getBulkInfo(){
return JSON.parse(localStorage.getItem("userinfos"));
@ -121,7 +154,7 @@ function adduser(user){
const instancein=document.getElementById("instancein") as HTMLInputElement;
let timeout;
let instanceinfo;
async function getapiurls(str:string):Promise<{api:string,cdn:string,gateway:string,wellknown:string}|false>{
async function getapiurls(str:string):Promise<{api:string,cdn:string,gateway:string,wellknown:string,login:string}|false>{
if(str[str.length-1]!=="/"){
str+="/"
}
@ -141,6 +174,7 @@ async function getapiurls(str:string):Promise<{api:string,cdn:string,gateway:str
gateway: info.gateway,
cdn: info.cdn,
wellknown: str,
login:url.toString()
};
}catch{
return false;
@ -199,7 +233,7 @@ async function login(username:string, password:string, captcha:string){
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())
return await fetch(api+'/auth/login',options).then(response=>response.json())
.then((response) => {
console.log(response,response.message)
if("Invalid Form Body"===response.message){
@ -229,7 +263,7 @@ async function login(username:string, password:string, captcha:string){
console.log(response);
if(response.ticket){
let onetimecode="";
new Dialog(["vdiv",["title","2FA code:"],["textbox","","",function(){onetimecode=this.value}],["button","","Submit",function(){
new Dialog(["vdiv",["title","2FA code:"],["textbox","","",function(this:HTMLInputElement){onetimecode=this.value}],["button","","Submit",function(){
fetch(api+"/auth/mfa/totp",{
method:"POST",
headers:{
@ -244,6 +278,7 @@ async function login(username:string, password:string, captcha:string){
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){
@ -256,6 +291,7 @@ async function login(username:string, password:string, captcha:string){
}]]).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){
@ -343,3 +379,4 @@ if(switchurl){
}
}
export {checkInstance};
trimswitcher();

View file

@ -14,14 +14,14 @@ class Member{
nick:string;
static contextmenu:Contextmenu=new Contextmenu("User Menu");
static setUpContextMenu(){
this.contextmenu.addbutton("Copy user id",function(){
this.contextmenu.addbutton("Copy user id",function(this:Member){
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Message user",function(){
this.contextmenu.addbutton("Message user",function(this:Member){
fetch(this.info.api+"/users/@me/channels",
{method:"POST",
body:JSON.stringify({"recipients":[this.id]}),
headers: this.headers
headers: this.localuser.headers
});
});
}

View file

@ -57,13 +57,13 @@ class Message{
this.del=new Promise(_=>{this.resolve=_})
}
static setupcmenu(){
Message.contextmenu.addbutton("Copy raw text",function(){
Message.contextmenu.addbutton("Copy raw text",function(this:Message){
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(){
Message.contextmenu.addbutton("Copy message id",function(this:Message){
navigator.clipboard.writeText(this.id);
});
Message.contextmenu.addsubmenu("Add reaction",function(this:Message,e){
@ -72,13 +72,13 @@ class Message{
this.reactionToggle(_);
});
});
Message.contextmenu.addbutton("Edit",function(){
Message.contextmenu.addbutton("Edit",function(this:Message){
this.channel.editing=this;
const markdown=(document.getElementById("typebox"))["markdown"] as MarkDown;
markdown.txt=this.content.rawString;
markdown.txt=this.content.rawString.split('');
markdown.boxupdate(document.getElementById("typebox"));
},null,_=>{return _.author.id===_.localuser.user.id});
Message.contextmenu.addbutton("Delete message",function(){
Message.contextmenu.addbutton("Delete message",function(this:Message){
this.delete();
},null,_=>{return _.canDelete()})
}

View file

@ -1782,7 +1782,7 @@ form div{
justify-content: center;
padding: .5in .2in;
gap: .1in;
width: 5.in;
width: 5in;
}
#AcceptInvite{
padding: .1in .2in;
@ -1929,3 +1929,28 @@ form div{
#channelTopic {
margin-left: 10px;
}
.badge{
display:flex;
color:white;
width:fit-content;
img{
width: .1in;
height: .1in;
}
background: var(--profile-bg);
padding: .04in;
border-radius: .07in;
font-size: .12in;
align-items: center;
border: solid .01in var(--black);
box-sizing: border-box;
}
.badges{
width:fit-content;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
}

View file

@ -25,7 +25,7 @@ class User{
premium_since: string;
premium_type: number;
theme_colors: string;
badge_ids: string;
badge_ids: string[];
members: WeakMap<Guild, Member|undefined|Promise<Member|undefined>>=new WeakMap();
private status:string;
clone(){
@ -71,7 +71,7 @@ class User{
this.contextmenu.addbutton("Copy user id",function(this:User){
navigator.clipboard.writeText(this.id);
});
this.contextmenu.addbutton("Message user",function(){
this.contextmenu.addbutton("Message user",function(this:User){
fetch(this.info.api+"/users/@me/channels",
{method:"POST",
body:JSON.stringify({"recipients":[this.id]}),
@ -121,6 +121,34 @@ class User{
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){
console.log(id,":3")
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.src=this.getpfpsrc();
@ -236,7 +264,27 @@ class User{
this.setstatus("online");
div.classList.add("hypoprofile","flexttb");
}
const badgediv=document.createElement("div");
badgediv.classList.add("badges");
(async ()=>{
console.log(this.badge_ids,":3")
if(!this.badge_ids) return;
for(const id of this.badge_ids){
const badgejson=await this.getBadge(id);
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);
@ -248,7 +296,7 @@ class User{
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;