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

@ -58,7 +58,7 @@ class Channel {
this.deleteChannel(); this.deleteChannel();
}, null, _ => { console.log(_); return _.isAdmin(); }); }, null, _ => { console.log(_); return _.isAdmin(); });
this.contextmenu.addbutton("Edit channel", function () { this.contextmenu.addbutton("Edit channel", function () {
this.editChannel(this); this.editChannel();
}, null, _ => { return _.isAdmin(); }); }, null, _ => { return _.isAdmin(); });
this.contextmenu.addbutton("Make invite", function () { this.contextmenu.addbutton("Make invite", function () {
this.createInvite(); this.createInvite();
@ -189,7 +189,9 @@ class Channel {
}.bind(this), async function (id) { }.bind(this), async function (id) {
const message = SnowFlake.getSnowFlakeFromID(id, Message).getObject(); const message = SnowFlake.getSnowFlakeFromID(id, Message).getObject();
try { try {
message.deleteDiv(); if (message) {
message.deleteDiv();
}
} }
catch (e) { catch (e) {
console.error(e); console.error(e);
@ -344,9 +346,9 @@ class Channel {
addchannel.textContent = "+"; addchannel.textContent = "+";
addchannel.classList.add("addchannel"); addchannel.classList.add("addchannel");
caps.appendChild(addchannel); caps.appendChild(addchannel);
addchannel.onclick = function () { addchannel.onclick = _ => {
this.guild.createchannels(this.createChannel.bind(this)); this.guild.createchannels(this.createChannel.bind(this));
}.bind(this); };
this.coatDropDiv(decdiv, childrendiv); this.coatDropDiv(decdiv, childrendiv);
} }
div.appendChild(caps); div.appendChild(caps);
@ -550,8 +552,7 @@ class Channel {
}); });
console.log(full); console.log(full);
full.hide(); full.hide();
}] }]]
]
]); ]);
full.show(); full.show();
console.log(full); console.log(full);
@ -798,6 +799,9 @@ class Channel {
return; return;
this.infinitefocus = true; this.infinitefocus = true;
const messages = document.getElementById("channelw"); const messages = document.getElementById("channelw");
for (const thing of messages.getElementsByClassName("messagecontainer")) {
thing.remove();
}
const loading = document.getElementById("loadingdiv"); const loading = document.getElementById("loadingdiv");
const removetitle = document.getElementById("removetitle"); const removetitle = document.getElementById("removetitle");
//messages.innerHTML=""; //messages.innerHTML="";

View file

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

View file

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

View file

@ -77,6 +77,9 @@ class Guild {
if (json === -1) { if (json === -1) {
return; return;
} }
if (json.stickers.length) {
console.log(json.stickers, ":3");
}
this.emojis = json.emojis; this.emojis = json.emojis;
this.owner = owner; this.owner = owner;
this.headers = this.owner.headers; this.headers = this.owner.headers;
@ -125,15 +128,12 @@ class Guild {
noti noti
], ],
["button", "", "submit", _ => { ["button", "", "submit", _ => {
fetch(this.info.api + "/users/@me/guilds/settings", { //
fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, {
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
body: JSON.stringify({ body: JSON.stringify({
"guilds": { "message_notifications": noti
[this.id]: {
"message_notifications": noti
}
}
}) })
}); });
this.message_notifications = noti; this.message_notifications = noti;
@ -446,8 +446,7 @@ class Guild {
console.log(name, category); console.log(name, category);
func(name, category); func(name, category);
channelselect.hide(); channelselect.hide();
}.bind(this)] }.bind(this)]]);
]);
channelselect.show(); channelselect.show();
} }
createcategory() { createcategory() {
@ -458,12 +457,11 @@ class Guild {
console.log(this); console.log(this);
name = this.value; name = this.value;
}], }],
["button", "", "submit", function () { ["button", "", "submit", () => {
console.log(name, category); console.log(name, category);
this.createChannel(name, category); this.createChannel(name, category);
channelselect.hide(); channelselect.hide();
}] }]]);
]);
channelselect.show(); channelselect.show();
} }
delChannel(json) { delChannel(json) {

View file

@ -85,6 +85,7 @@ function showAccountSwitcher() {
} }
let thisuser; let thisuser;
try { try {
console.log(users.users, users.currentuser);
thisuser = new Localuser(users.users[users.currentuser]); thisuser = new Localuser(users.users[users.currentuser]);
thisuser.initwebsocket().then(_ => { thisuser.initwebsocket().then(_ => {
thisuser.loaduser(); thisuser.loaduser();
@ -94,7 +95,8 @@ try {
console.log("done loading"); console.log("done loading");
}); });
} }
catch { catch (e) {
console.error(e);
document.getElementById("load-desc").textContent = "Account unable to start"; document.getElementById("load-desc").textContent = "Account unable to start";
thisuser = new Localuser(-1); thisuser = new Localuser(-1);
} }
@ -151,11 +153,13 @@ typebox.addEventListener("keydown", event => {
}); });
console.log(typebox); console.log(typebox);
typebox.onclick = console.log; typebox.onclick = console.log;
function getguildinfo() { /*
const path = window.location.pathname.split("/"); function getguildinfo(){
const channel = path[3]; const path=window.location.pathname.split("/");
this.ws.send(JSON.stringify({ op: 14, d: { guild_id: path[2], channels: { [channel]: [[0, 99]] } } })); const channel=path[3];
this.ws.send(JSON.stringify({op: 14, d: {guild_id: path[2], channels: {[channel]: [[0, 99]]}}}));
} }
*/
const images = []; const images = [];
const imageshtml = []; const imageshtml = [];
import { File } from "./file.js"; import { File } from "./file.js";

View file

@ -164,7 +164,7 @@ class InfiniteScroller {
return; return;
} }
const out = await Promise.allSettled([this.watchForTop(), this.watchForBottom()]); 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) { if (null === this.timeout && changed) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
@ -217,7 +217,7 @@ class InfiniteScroller {
await this.destroyFromID(thing[1]); await this.destroyFromID(thing[1]);
} }
this.HTMLElements = []; this.HTMLElements = [];
clearInterval(this.timeout); clearTimeout(this.timeout);
if (this.div) { if (this.div) {
this.div.remove(); this.div.remove();
} }

View file

@ -3,7 +3,7 @@ import { Direct } from "./direct.js";
import { Voice } from "./audio.js"; import { Voice } from "./audio.js";
import { User } from "./user.js"; import { User } from "./user.js";
import { Dialog } from "./dialog.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 { SnowFlake } from "./snowflake.js";
import { Message } from "./message.js"; import { Message } from "./message.js";
import { Member } from "./member.js"; import { Member } from "./member.js";
@ -11,6 +11,7 @@ import { Settings } from "./settings.js";
import { MarkDown } from "./markdown.js"; import { MarkDown } from "./markdown.js";
const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]);
class Localuser { class Localuser {
badges = new Map();
lastSequence = null; lastSequence = null;
token; token;
userinfo; userinfo;
@ -205,7 +206,7 @@ class Localuser {
res(); res();
} }
}); });
this.ws.addEventListener("close", event => { this.ws.addEventListener("close", async (event) => {
this.ws = undefined; this.ws = undefined;
console.log("WebSocket closed with code " + event.code); console.log("WebSocket closed with code " + event.code);
this.unload(); this.unload();
@ -221,6 +222,41 @@ class Localuser {
this.errorBackoff++; this.errorBackoff++;
this.connectionSucceed = 0; 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..."; 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(() => { setTimeout(() => {
if (this.swapped) if (this.swapped)
return; return;
@ -618,7 +654,7 @@ class Localuser {
} }
async typingStart(typing) { async typingStart(typing) {
if (this.channelfocus.id === typing.d.channel_id) { 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); const memb = await Member.new(typing.d.member, guild);
if (memb.id === this.user.id) { if (memb.id === this.user.id) {
console.log("you is typing"); console.log("you is typing");
@ -881,8 +917,7 @@ class Localuser {
this.mfa_enabled = true; this.mfa_enabled = true;
} }
}); });
}] }]]);
]);
console.log("here :3"); console.log("here :3");
addmodel.show(); addmodel.show();
}); });

View file

@ -17,6 +17,41 @@ function getBulkUsers() {
} }
return json; 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() { function getBulkInfo() {
return JSON.parse(localStorage.getItem("userinfos")); return JSON.parse(localStorage.getItem("userinfos"));
} }
@ -141,6 +176,7 @@ async function getapiurls(str) {
gateway: info.gateway, gateway: info.gateway,
cdn: info.cdn, cdn: info.cdn,
wellknown: str, wellknown: str,
login: url.toString()
}; };
} }
catch { catch {
@ -201,7 +237,7 @@ async function login(username, password, captcha) {
try { try {
const info = JSON.parse(localStorage.getItem("instanceinfo")); const info = JSON.parse(localStorage.getItem("instanceinfo"));
const api = info.login + (info.login.startsWith("/") ? "/" : ""); 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) => { .then((response) => {
console.log(response, response.message); console.log(response, response.message);
if ("Invalid Form Body" === response.message) { if ("Invalid Form Body" === response.message) {
@ -246,6 +282,8 @@ async function login(username, password, captcha) {
} }
else { else {
console.warn(response); console.warn(response);
if (!response.token)
return;
adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token }).username = username; adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token }).username = username;
const redir = new URLSearchParams(window.location.search).get("goback"); const redir = new URLSearchParams(window.location.search).get("goback");
if (redir) { if (redir) {
@ -260,6 +298,8 @@ async function login(username, password, captcha) {
} }
else { else {
console.warn(response); console.warn(response);
if (!response.token)
return;
adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token }).username = username; adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token }).username = username;
const redir = new URLSearchParams(window.location.search).get("goback"); const redir = new URLSearchParams(window.location.search).get("goback");
if (redir) { if (redir) {
@ -347,3 +387,4 @@ if (switchurl) {
} }
} }
export { checkInstance }; export { checkInstance };
trimswitcher();

View file

@ -17,7 +17,7 @@ class Member {
this.contextmenu.addbutton("Message user", function () { this.contextmenu.addbutton("Message user", function () {
fetch(this.info.api + "/users/@me/channels", { method: "POST", fetch(this.info.api + "/users/@me/channels", { method: "POST",
body: JSON.stringify({ "recipients": [this.id] }), body: JSON.stringify({ "recipients": [this.id] }),
headers: this.headers headers: this.localuser.headers
}); });
}); });
} }

View file

@ -70,7 +70,7 @@ class Message {
Message.contextmenu.addbutton("Edit", function () { Message.contextmenu.addbutton("Edit", function () {
this.channel.editing = this; this.channel.editing = this;
const markdown = (document.getElementById("typebox"))["markdown"]; const markdown = (document.getElementById("typebox"))["markdown"];
markdown.txt = this.content.rawString; markdown.txt = this.content.rawString.split('');
markdown.boxupdate(document.getElementById("typebox")); markdown.boxupdate(document.getElementById("typebox"));
}, null, _ => { return _.author.id === _.localuser.user.id; }); }, null, _ => { return _.author.id === _.localuser.user.id; });
Message.contextmenu.addbutton("Delete message", function () { Message.contextmenu.addbutton("Delete message", function () {

View file

@ -121,6 +121,32 @@ class User {
async resolvemember(guild) { async resolvemember(guild) {
return await Member.resolveMember(this, 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() { buildpfp() {
const pfp = document.createElement('img'); const pfp = document.createElement('img');
pfp.src = this.getpfpsrc(); pfp.src = this.getpfpsrc();
@ -235,6 +261,28 @@ class User {
this.setstatus("online"); this.setstatus("online");
div.classList.add("hypoprofile", "flexttb"); 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(); const pfp = await this.buildstatuspfp();
div.appendChild(pfp); div.appendChild(pfp);
@ -246,6 +294,7 @@ class User {
const usernamehtml = document.createElement("h2"); const usernamehtml = document.createElement("h2");
usernamehtml.textContent = this.username; usernamehtml.textContent = this.username;
userbody.appendChild(usernamehtml); userbody.appendChild(usernamehtml);
userbody.appendChild(badgediv);
const discrimatorhtml = document.createElement("h3"); const discrimatorhtml = document.createElement("h3");
discrimatorhtml.classList.add("tag"); discrimatorhtml.classList.add("tag");
discrimatorhtml.textContent = this.username + "#" + this.discriminator; discrimatorhtml.textContent = this.username + "#" + this.discriminator;

View file

@ -6,7 +6,8 @@
"strict": false, "strict": false,
"esModuleInterop": true, "esModuleInterop": true,
"outDir": "./.dist", "outDir": "./.dist",
"removeComments": false "removeComments": false,
"noImplicitThis":true
}, },
"include": [ "include": [
"./webpage/*.ts" "./webpage/*.ts"

View file

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

View file

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

View file

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

View file

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

View file

@ -95,6 +95,7 @@ function showAccountSwitcher(){
} }
let thisuser:Localuser; let thisuser:Localuser;
try{ try{
console.log(users.users,users.currentuser)
thisuser=new Localuser(users.users[users.currentuser]); thisuser=new Localuser(users.users[users.currentuser]);
thisuser.initwebsocket().then(_=>{ thisuser.initwebsocket().then(_=>{
thisuser.loaduser(); thisuser.loaduser();
@ -103,7 +104,8 @@ try{
document.getElementById("loading").classList.remove("loading"); document.getElementById("loading").classList.remove("loading");
console.log("done loading") console.log("done loading")
}); });
}catch{ }catch(e){
console.error(e);
document.getElementById("load-desc").textContent="Account unable to start"; document.getElementById("load-desc").textContent="Account unable to start";
thisuser=new Localuser(-1); thisuser=new Localuser(-1);
} }
@ -164,13 +166,13 @@ typebox.addEventListener("keydown",event=>{
console.log(typebox) console.log(typebox)
typebox.onclick=console.log; typebox.onclick=console.log;
/*
function getguildinfo(){ function getguildinfo(){
const path=window.location.pathname.split("/"); const path=window.location.pathname.split("/");
const channel=path[3]; const channel=path[3];
this.ws.send(JSON.stringify({op: 14, d: {guild_id: path[2], channels: {[channel]: [[0, 99]]}}})); this.ws.send(JSON.stringify({op: 14, d: {guild_id: path[2], channels: {[channel]: [[0, 99]]}}}));
} }
*/
const images:Blob[]=[]; const images:Blob[]=[];
const imageshtml=[]; const imageshtml=[];

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import {Direct} from "./direct.js";
import {Voice} from "./audio.js"; import {Voice} from "./audio.js";
import {User} from "./user.js"; import {User} from "./user.js";
import {Dialog} from "./dialog.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 { SnowFlake } from "./snowflake.js";
import { Message } from "./message.js"; import { Message } from "./message.js";
import { channeljson, guildjson, memberjson, presencejson, readyjson } from "./jsontypes.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]); const wsCodesRetry=new Set([4000,4003,4005,4007,4008,4009]);
class Localuser{ class Localuser{
badges:Map<string,{id:string,description:string,icon:string,link:string}>=new Map();
lastSequence:number|null=null; lastSequence:number|null=null;
token:string; token:string;
userinfo:Specialuser; userinfo:Specialuser;
@ -214,7 +215,7 @@ class Localuser{
} }
}); });
this.ws.addEventListener("close", event => { this.ws.addEventListener("close",async event => {
this.ws=undefined; this.ws=undefined;
console.log("WebSocket closed with code " + event.code); console.log("WebSocket closed with code " + event.code);
@ -230,7 +231,43 @@ class Localuser{
this.connectionSucceed=0; 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..."; 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(() => { setTimeout(() => {
if(this.swapped) return; if(this.swapped) return;
document.getElementById("load-desc").textContent="Retrying..."; document.getElementById("load-desc").textContent="Retrying...";
@ -497,7 +534,7 @@ class Localuser{
["textbox", ["textbox",
"Invite Link/Code", "Invite Link/Code",
"", "",
function(){ function(this:HTMLInputElement){
inviteurl=this.value; inviteurl=this.value;
} }
], ],
@ -635,7 +672,8 @@ class Localuser{
} }
async typingStart(typing):Promise<void>{ async typingStart(typing):Promise<void>{
if(this.channelfocus.id===typing.d.channel_id){ 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); const memb=await Member.new(typing.d.member,guild);
if(memb.id===this.user.id){ if(memb.id===this.user.id){
console.log("you is typing") console.log("you is typing")
@ -878,8 +916,8 @@ class Localuser{
["title","2FA set up"], ["title","2FA set up"],
["text","Copy this secret into your totp(time-based one time password) app"], ["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`], ["text",`Your secret is: ${secret} and it's 6 digits, with a 30 second token period`],
["textbox","Account password:","",function(){password=this.value}], ["textbox","Account password:","",function(this:HTMLInputElement){password=this.value}],
["textbox","Code:","",function(){code=this.value}], ["textbox","Code:","",function(this:HTMLInputElement){code=this.value}],
["button","","Submit",()=>{ ["button","","Submit",()=>{
fetch(this.info.api+"/users/@me/mfa/totp/enable/",{ fetch(this.info.api+"/users/@me/mfa/totp/enable/",{
method:"POST", method:"POST",

View file

@ -18,6 +18,39 @@ function getBulkUsers(){
} }
return json; 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(){ function getBulkInfo(){
return JSON.parse(localStorage.getItem("userinfos")); return JSON.parse(localStorage.getItem("userinfos"));
@ -121,7 +154,7 @@ function adduser(user){
const instancein=document.getElementById("instancein") as HTMLInputElement; const instancein=document.getElementById("instancein") as HTMLInputElement;
let timeout; let timeout;
let instanceinfo; 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]!=="/"){ if(str[str.length-1]!=="/"){
str+="/" str+="/"
} }
@ -141,6 +174,7 @@ async function getapiurls(str:string):Promise<{api:string,cdn:string,gateway:str
gateway: info.gateway, gateway: info.gateway,
cdn: info.cdn, cdn: info.cdn,
wellknown: str, wellknown: str,
login:url.toString()
}; };
}catch{ }catch{
return false; return false;
@ -199,7 +233,7 @@ async function login(username:string, password:string, captcha:string){
try{ try{
const info=JSON.parse(localStorage.getItem("instanceinfo")); const info=JSON.parse(localStorage.getItem("instanceinfo"));
const api=info.login+(info.login.startsWith("/")?"/":""); 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) => { .then((response) => {
console.log(response,response.message) console.log(response,response.message)
if("Invalid Form Body"===response.message){ if("Invalid Form Body"===response.message){
@ -229,7 +263,7 @@ async function login(username:string, password:string, captcha:string){
console.log(response); console.log(response);
if(response.ticket){ if(response.ticket){
let onetimecode=""; 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",{ fetch(api+"/auth/mfa/totp",{
method:"POST", method:"POST",
headers:{ headers:{
@ -244,6 +278,7 @@ async function login(username:string, password:string, captcha:string){
alert(response.message) alert(response.message)
}else{ }else{
console.warn(response); console.warn(response);
if(!response.token) return;
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token}).username=username; adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token}).username=username;
const redir=new URLSearchParams(window.location.search).get("goback"); const redir=new URLSearchParams(window.location.search).get("goback");
if(redir){ if(redir){
@ -256,6 +291,7 @@ async function login(username:string, password:string, captcha:string){
}]]).show(); }]]).show();
}else{ }else{
console.warn(response); console.warn(response);
if(!response.token) return;
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token}).username=username; adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token}).username=username;
const redir=new URLSearchParams(window.location.search).get("goback"); const redir=new URLSearchParams(window.location.search).get("goback");
if(redir){ if(redir){
@ -343,3 +379,4 @@ if(switchurl){
} }
} }
export {checkInstance}; export {checkInstance};
trimswitcher();

View file

@ -14,14 +14,14 @@ class Member{
nick:string; nick:string;
static contextmenu:Contextmenu=new Contextmenu("User Menu"); static contextmenu:Contextmenu=new Contextmenu("User Menu");
static setUpContextMenu(){ static setUpContextMenu(){
this.contextmenu.addbutton("Copy user id",function(){ this.contextmenu.addbutton("Copy user id",function(this:Member){
navigator.clipboard.writeText(this.id); 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", fetch(this.info.api+"/users/@me/channels",
{method:"POST", {method:"POST",
body:JSON.stringify({"recipients":[this.id]}), 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=_}) this.del=new Promise(_=>{this.resolve=_})
} }
static setupcmenu(){ static setupcmenu(){
Message.contextmenu.addbutton("Copy raw text",function(){ Message.contextmenu.addbutton("Copy raw text",function(this:Message){
navigator.clipboard.writeText(this.content.rawString); navigator.clipboard.writeText(this.content.rawString);
}); });
Message.contextmenu.addbutton("Reply",function(this:Message,div:HTMLDivElement){ Message.contextmenu.addbutton("Reply",function(this:Message,div:HTMLDivElement){
this.channel.setReplying(this); 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); navigator.clipboard.writeText(this.id);
}); });
Message.contextmenu.addsubmenu("Add reaction",function(this:Message,e){ Message.contextmenu.addsubmenu("Add reaction",function(this:Message,e){
@ -72,13 +72,13 @@ class Message{
this.reactionToggle(_); this.reactionToggle(_);
}); });
}); });
Message.contextmenu.addbutton("Edit",function(){ Message.contextmenu.addbutton("Edit",function(this:Message){
this.channel.editing=this; this.channel.editing=this;
const markdown=(document.getElementById("typebox"))["markdown"] as MarkDown; const markdown=(document.getElementById("typebox"))["markdown"] as MarkDown;
markdown.txt=this.content.rawString; markdown.txt=this.content.rawString.split('');
markdown.boxupdate(document.getElementById("typebox")); markdown.boxupdate(document.getElementById("typebox"));
},null,_=>{return _.author.id===_.localuser.user.id}); },null,_=>{return _.author.id===_.localuser.user.id});
Message.contextmenu.addbutton("Delete message",function(){ Message.contextmenu.addbutton("Delete message",function(this:Message){
this.delete(); this.delete();
},null,_=>{return _.canDelete()}) },null,_=>{return _.canDelete()})
} }

View file

@ -1782,7 +1782,7 @@ form div{
justify-content: center; justify-content: center;
padding: .5in .2in; padding: .5in .2in;
gap: .1in; gap: .1in;
width: 5.in; width: 5in;
} }
#AcceptInvite{ #AcceptInvite{
padding: .1in .2in; padding: .1in .2in;
@ -1929,3 +1929,28 @@ form div{
#channelTopic { #channelTopic {
margin-left: 10px; 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_since: string;
premium_type: number; premium_type: number;
theme_colors: string; theme_colors: string;
badge_ids: string; badge_ids: string[];
members: WeakMap<Guild, Member|undefined|Promise<Member|undefined>>=new WeakMap(); members: WeakMap<Guild, Member|undefined|Promise<Member|undefined>>=new WeakMap();
private status:string; private status:string;
clone(){ clone(){
@ -71,7 +71,7 @@ class User{
this.contextmenu.addbutton("Copy user id",function(this:User){ this.contextmenu.addbutton("Copy user id",function(this:User){
navigator.clipboard.writeText(this.id); 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", fetch(this.info.api+"/users/@me/channels",
{method:"POST", {method:"POST",
body:JSON.stringify({"recipients":[this.id]}), body:JSON.stringify({"recipients":[this.id]}),
@ -121,6 +121,34 @@ class User{
async resolvemember(guild:Guild){ async resolvemember(guild:Guild){
return await Member.resolveMember(this,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(){ buildpfp(){
const pfp=document.createElement('img'); const pfp=document.createElement('img');
pfp.src=this.getpfpsrc(); pfp.src=this.getpfpsrc();
@ -236,7 +264,27 @@ class User{
this.setstatus("online"); this.setstatus("online");
div.classList.add("hypoprofile","flexttb"); 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(); const pfp=await this.buildstatuspfp();
div.appendChild(pfp); div.appendChild(pfp);
@ -248,7 +296,7 @@ class User{
const usernamehtml=document.createElement("h2"); const usernamehtml=document.createElement("h2");
usernamehtml.textContent=this.username; usernamehtml.textContent=this.username;
userbody.appendChild(usernamehtml); userbody.appendChild(usernamehtml);
userbody.appendChild(badgediv);
const discrimatorhtml=document.createElement("h3"); const discrimatorhtml=document.createElement("h3");
discrimatorhtml.classList.add("tag"); discrimatorhtml.classList.add("tag");
discrimatorhtml.textContent=this.username+"#"+this.discriminator; discrimatorhtml.textContent=this.username+"#"+this.discriminator;