diff --git a/.dist/channel.js b/.dist/channel.js index 668463a..376dadd 100644 --- a/.dist/channel.js +++ b/.dist/channel.js @@ -58,7 +58,7 @@ class Channel { this.deleteChannel(); }, null, _ => { console.log(_); return _.isAdmin(); }); this.contextmenu.addbutton("Edit channel", function () { - this.editChannel(this); + this.editChannel(); }, null, _ => { return _.isAdmin(); }); this.contextmenu.addbutton("Make invite", function () { this.createInvite(); @@ -189,7 +189,9 @@ class Channel { }.bind(this), async function (id) { const message = SnowFlake.getSnowFlakeFromID(id, Message).getObject(); try { - message.deleteDiv(); + if (message) { + message.deleteDiv(); + } } catch (e) { console.error(e); @@ -344,9 +346,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); @@ -550,8 +552,7 @@ class Channel { }); console.log(full); full.hide(); - }] - ] + }]] ]); full.show(); console.log(full); @@ -798,6 +799,9 @@ class Channel { 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=""; diff --git a/.dist/dialog.js b/.dist/dialog.js index fc2f3ab..11ee249 100644 --- a/.dist/dialog.js +++ b/.dist/dialog.js @@ -238,7 +238,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); diff --git a/.dist/direct.js b/.dist/direct.js index c17c507..56bd414 100644 --- a/.dist/direct.js +++ b/.dist/direct.js @@ -105,6 +105,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"); diff --git a/.dist/guild.js b/.dist/guild.js index 5e97801..9a83b45 100644 --- a/.dist/guild.js +++ b/.dist/guild.js @@ -77,6 +77,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; @@ -125,15 +128,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; @@ -446,8 +446,7 @@ class Guild { console.log(name, category); func(name, category); channelselect.hide(); - }.bind(this)] - ]); + }.bind(this)]]); channelselect.show(); } createcategory() { @@ -458,12 +457,11 @@ class Guild { console.log(this); name = this.value; }], - ["button", "", "submit", function () { + ["button", "", "submit", () => { console.log(name, category); this.createChannel(name, category); channelselect.hide(); - }] - ]); + }]]); channelselect.show(); } delChannel(json) { diff --git a/.dist/index.js b/.dist/index.js index 2c36ff7..d28ad76 100644 --- a/.dist/index.js +++ b/.dist/index.js @@ -85,6 +85,7 @@ function showAccountSwitcher() { } let thisuser; try { + console.log(users.users, users.currentuser); thisuser = new Localuser(users.users[users.currentuser]); thisuser.initwebsocket().then(_ => { thisuser.loaduser(); @@ -94,7 +95,8 @@ try { console.log("done loading"); }); } -catch { +catch (e) { + console.error(e); document.getElementById("load-desc").textContent = "Account unable to start"; thisuser = new Localuser(-1); } @@ -151,11 +153,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]] } } })); +/* +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 = []; const imageshtml = []; import { File } from "./file.js"; diff --git a/.dist/infiniteScroller.js b/.dist/infiniteScroller.js index 9407073..ff39331 100644 --- a/.dist/infiniteScroller.js +++ b/.dist/infiniteScroller.js @@ -164,7 +164,7 @@ class InfiniteScroller { return; } const out = await Promise.allSettled([this.watchForTop(), this.watchForBottom()]); - const changed = (out[0] || out[1]); + const changed = (out[0].value || out[1].value); if (null === this.timeout && changed) { this.timeout = setTimeout(this.updatestuff.bind(this), 300); } @@ -217,7 +217,7 @@ class InfiniteScroller { await this.destroyFromID(thing[1]); } this.HTMLElements = []; - clearInterval(this.timeout); + clearTimeout(this.timeout); if (this.div) { this.div.remove(); } diff --git a/.dist/localuser.js b/.dist/localuser.js index 97ec9f7..9d774a7 100644 --- a/.dist/localuser.js +++ b/.dist/localuser.js @@ -3,7 +3,7 @@ import { Direct } from "./direct.js"; import { Voice } from "./audio.js"; import { User } from "./user.js"; import { Dialog } from "./dialog.js"; -import { getBulkInfo, setTheme } from "./login.js"; +import { getapiurls, getBulkInfo, setTheme } from "./login.js"; import { SnowFlake } from "./snowflake.js"; import { Message } from "./message.js"; import { Member } from "./member.js"; @@ -11,6 +11,7 @@ import { Settings } from "./settings.js"; import { MarkDown } from "./markdown.js"; const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); class Localuser { + badges = new Map(); lastSequence = null; token; userinfo; @@ -205,7 +206,7 @@ class Localuser { res(); } }); - this.ws.addEventListener("close", event => { + this.ws.addEventListener("close", async (event) => { this.ws = undefined; console.log("WebSocket closed with code " + event.code); this.unload(); @@ -221,6 +222,41 @@ class Localuser { this.errorBackoff++; this.connectionSucceed = 0; document.getElementById("load-desc").innerHTML = "Unable to connect to the Spacebar server, retrying in " + Math.round(0.2 + (this.errorBackoff * 2.8)) + " seconds..."; + switch (this.errorBackoff) { //try to recover from bad domain + case 3: + const newurls = await getapiurls(this.info.wellknown); + if (newurls) { + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + break; + } + case 4: + { + const newurls = await getapiurls(new URL(this.info.wellknown).origin); + if (newurls) { + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + break; + } + } + case 5: + { + const breakappart = new URL(this.info.wellknown).origin.split("."); + const url = "https://" + breakappart[breakappart.length - 2] + "." + breakappart[breakappart.length - 1]; + const newurls = await getapiurls(url); + if (newurls) { + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + } + break; + } + } setTimeout(() => { if (this.swapped) return; @@ -618,7 +654,7 @@ class Localuser { } async typingStart(typing) { 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"); @@ -881,8 +917,7 @@ class Localuser { this.mfa_enabled = true; } }); - }] - ]); + }]]); console.log("here :3"); addmodel.show(); }); diff --git a/.dist/login.js b/.dist/login.js index 7091388..aa10fc8 100644 --- a/.dist/login.js +++ b/.dist/login.js @@ -17,6 +17,41 @@ 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")); } @@ -141,6 +176,7 @@ async function getapiurls(str) { gateway: info.gateway, cdn: info.cdn, wellknown: str, + login: url.toString() }; } catch { @@ -201,7 +237,7 @@ async function login(username, password, captcha) { try { const info = JSON.parse(localStorage.getItem("instanceinfo")); const api = info.login + (info.login.startsWith("/") ? "/" : ""); - return await fetch(api + 'auth/login', options).then(response => response.json()) + return await fetch(api + '/auth/login', options).then(response => response.json()) .then((response) => { console.log(response, response.message); if ("Invalid Form Body" === response.message) { @@ -246,6 +282,8 @@ async function login(username, password, captcha) { } 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) { @@ -260,6 +298,8 @@ async function login(username, password, captcha) { } 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) { @@ -347,3 +387,4 @@ if (switchurl) { } } export { checkInstance }; +trimswitcher(); diff --git a/.dist/member.js b/.dist/member.js index 83683e8..68bd5a7 100644 --- a/.dist/member.js +++ b/.dist/member.js @@ -17,7 +17,7 @@ class Member { this.contextmenu.addbutton("Message user", function () { fetch(this.info.api + "/users/@me/channels", { method: "POST", body: JSON.stringify({ "recipients": [this.id] }), - headers: this.headers + headers: this.localuser.headers }); }); } diff --git a/.dist/message.js b/.dist/message.js index d61fa81..96ab4e7 100644 --- a/.dist/message.js +++ b/.dist/message.js @@ -70,7 +70,7 @@ class Message { Message.contextmenu.addbutton("Edit", function () { this.channel.editing = this; const markdown = (document.getElementById("typebox"))["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 () { diff --git a/.dist/user.js b/.dist/user.js index 46ef539..86d13d2 100644 --- a/.dist/user.js +++ b/.dist/user.js @@ -121,6 +121,32 @@ class User { async resolvemember(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; + async getBadge(id) { + 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(); @@ -235,6 +261,28 @@ 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); @@ -246,6 +294,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; diff --git a/tsconfig.json b/tsconfig.json index b4b481e..9dba0e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "strict": false, "esModuleInterop": true, "outDir": "./.dist", - "removeComments": false + "removeComments": false, + "noImplicitThis":true }, "include": [ "./webpage/*.ts" diff --git a/webpage/channel.ts b/webpage/channel.ts index c8c7958..0142a62 100644 --- a/webpage/channel.ts +++ b/webpage/channel.ts @@ -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=""; diff --git a/webpage/dialog.ts b/webpage/dialog.ts index 985031a..def7c25 100644 --- a/webpage/dialog.ts +++ b/webpage/dialog.ts @@ -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); diff --git a/webpage/direct.ts b/webpage/direct.ts index a00361e..af92dae 100644 --- a/webpage/direct.ts +++ b/webpage/direct.ts @@ -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") diff --git a/webpage/guild.ts b/webpage/guild.ts index 8bef811..b662a64 100644 --- a/webpage/guild.ts +++ b/webpage/guild.ts @@ -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(); diff --git a/webpage/index.ts b/webpage/index.ts index e84ca69..972190e 100644 --- a/webpage/index.ts +++ b/webpage/index.ts @@ -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=[]; diff --git a/webpage/infiniteScroller.ts b/webpage/infiniteScroller.ts index 951d902..3d47d4f 100644 --- a/webpage/infiniteScroller.ts +++ b/webpage/infiniteScroller.ts @@ -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(); } diff --git a/webpage/jsontypes.ts b/webpage/jsontypes.ts index 0d1a312..83d5609 100644 --- a/webpage/jsontypes.ts +++ b/webpage/jsontypes.ts @@ -137,7 +137,7 @@ type userjson={ premium_type: number, theme_colors: string, pronouns: string, - badge_ids: string, + badge_ids: string[], } type memberjson= { index?:number, diff --git a/webpage/localuser.ts b/webpage/localuser.ts index 6ab9684..b3da697 100644 --- a/webpage/localuser.ts +++ b/webpage/localuser.ts @@ -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=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 " + Math.round(0.2 + (this.errorBackoff*2.8)) + " seconds..."; + switch(this.errorBackoff){//try to recover from bad domain + case 3: + const newurls=await getapiurls(this.info.wellknown); + if(newurls){ + this.info=newurls; + this.serverurls=newurls; + this.userinfo.json.serverurls=this.info; + this.userinfo.updateLocal(); + break + } + case 4: + { + const newurls=await getapiurls(new URL(this.info.wellknown).origin); + if(newurls){ + this.info=newurls; + this.serverurls=newurls; + this.userinfo.json.serverurls=this.info; + this.userinfo.updateLocal(); + break + } + + } + case 5: + { + const breakappart=new URL(this.info.wellknown).origin.split("."); + const url="https://"+breakappart[breakappart.length-2]+"."+breakappart[breakappart.length-1] + const newurls=await getapiurls(url); + if(newurls){ + this.info=newurls; + this.serverurls=newurls; + this.userinfo.json.serverurls=this.info; + this.userinfo.updateLocal(); + } + break + } + } setTimeout(() => { if(this.swapped) return; document.getElementById("load-desc").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{ 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", diff --git a/webpage/login.ts b/webpage/login.ts index f01575b..45d3d56 100644 --- a/webpage/login.ts +++ b/webpage/login.ts @@ -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(); diff --git a/webpage/member.ts b/webpage/member.ts index dbbd865..8578bfb 100644 --- a/webpage/member.ts +++ b/webpage/member.ts @@ -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 }); }); } diff --git a/webpage/message.ts b/webpage/message.ts index c36e1f8..ce8a79d 100644 --- a/webpage/message.ts +++ b/webpage/message.ts @@ -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()}) } diff --git a/webpage/style.css b/webpage/style.css index 53e7fec..7b2dd09 100644 --- a/webpage/style.css +++ b/webpage/style.css @@ -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; +} diff --git a/webpage/user.ts b/webpage/user.ts index 6415ad7..99ec8b2 100644 --- a/webpage/user.ts +++ b/webpage/user.ts @@ -25,7 +25,7 @@ class User{ premium_since: string; premium_type: number; theme_colors: string; - badge_ids: string; + badge_ids: string[]; members: WeakMap>=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=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;