Merge remote-tracking branch 'upstream/main'

This commit is contained in:
ygg2 2024-07-29 17:18:04 -04:00
commit 8de112febb
32 changed files with 1109 additions and 285 deletions

View file

@ -5,6 +5,7 @@ import { Contextmenu } from "./contextmenu.js";
import { Fullscreen } from "./fullscreen.js";
import { Permissions } from "./permissions.js";
import { Settings, RoleList } from "./settings.js";
import { Role } from "./role.js";
import { InfiniteScroller } from "./infiniteScroller.js";
import { SnowFlake } from "./snowflake.js";
class Channel {
@ -78,7 +79,7 @@ class Channel {
this.infinite = new InfiniteScroller(async function (id, offset) {
const snowflake = SnowFlake.getSnowFlakeFromID(id, Message);
if (offset === 1) {
if (this.idToPrev.get(snowflake)) {
if (this.idToPrev.has(snowflake)) {
return this.idToPrev.get(snowflake)?.id;
}
else {
@ -87,13 +88,25 @@ class Channel {
}
}
else {
if (this.idToNext.has(snowflake)) {
return this.idToNext.get(snowflake)?.id;
}
}.bind(this), function (id) {
else if (this.lastmessage.id !== id) {
await this.grabAfter(id);
return this.idToNext.get(snowflake)?.id;
}
else {
console.log("at bottom");
}
}
}.bind(this), async function (id) {
let res;
const promise = new Promise(_ => { res = _; });
const snowflake = SnowFlake.getSnowFlakeFromID(id, Message);
const html = this.messageids.get(snowflake).buildhtml(this.messageids.get(this.idToPrev.get(snowflake)), promise);
if (!snowflake.getObject()) {
await this.grabArround(id);
}
const html = snowflake.getObject().buildhtml(this.messageids.get(this.idToPrev.get(snowflake)), promise);
ids[id] = res;
return html;
}.bind(this), async function (id) {
@ -125,7 +138,7 @@ class Channel {
}
;
this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny));
this.permission_overwritesar.push([thing.id, this.permission_overwrites.get(thing.id)]);
this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id, Role), this.permission_overwrites.get(thing.id)]);
}
this.topic = json.topic;
this.nsfw = json.nsfw;
@ -528,7 +541,7 @@ class Channel {
return;
}
this.makereplybox();
this.buildmessages();
await this.buildmessages();
history.pushState(null, null, "/channels/" + this.guild_id + "/" + this.snowflake);
document.getElementById("channelname").textContent = "#" + this.name;
console.log(this);
@ -566,69 +579,111 @@ class Channel {
delChannel(json) {
const build = [];
for (const thing of this.children) {
if (thing.snowflake !== json.id) {
if (thing.id !== json.id) {
build.push(thing);
}
}
this.children = build;
}
async grabBefore(id) {
if (this.allthewayup) {
async grabAfter(id) {
console.log(id, this.lastmessage.id);
if (id === this.lastmessage.id) {
return;
}
await fetch(this.info.api.toString() + "/channels/" + this.snowflake + "/messages?before=" + id + "&limit=100", {
await fetch(this.info.api.toString() + "/channels/" + this.id + "/messages?limit=100&after=" + id, {
headers: this.headers
}).then((j) => { return j.json(); }).then(response => {
let next;
if (response.length === 0) {
this.allthewayup = true;
}
let previd = SnowFlake.getSnowFlakeFromID(id, Message);
for (const i in response) {
let messager;
if (!next) {
let willbreak = false;
if (!SnowFlake.hasSnowFlakeFromID(response[i].id, Message)) {
messager = new Message(response[i], this);
}
else {
messager = next;
messager = SnowFlake.getSnowFlakeFromID(response[i].id, Message).getObject();
willbreak = true;
}
if (response[+i + 1] !== undefined) {
next = new Message(response[+i + 1], this);
}
else {
next = undefined;
console.log("ohno", +i + 1);
}
if (this.messageids.get(messager.snowflake) === undefined) {
this.idToNext.set(messager.snowflake, previd);
this.idToPrev.set(previd, messager.snowflake);
this.idToPrev.set(messager.snowflake, previd);
this.idToNext.set(previd, messager.snowflake);
previd = messager.snowflake;
this.messageids.set(messager.snowflake, messager);
}
else {
console.log("How???");
if (willbreak) {
break;
}
}
//out.buildmessages();
});
return;
}
topid;
async grabBefore(id) {
if (this.topid && id === this.topid.id) {
return;
}
await fetch(this.info.api.toString() + "/channels/" + this.snowflake + "/messages?before=" + id + "&limit=100", {
headers: this.headers
}).then((j) => { return j.json(); }).then((response) => {
if (response.length < 100) {
this.allthewayup = true;
if (response.length === 0) {
this.topid = SnowFlake.getSnowFlakeFromID(id, Message);
}
}
let previd = SnowFlake.getSnowFlakeFromID(id, Message);
for (const i in response) {
let messager;
let willbreak = false;
if (!SnowFlake.hasSnowFlakeFromID(response[i].id, Message)) {
messager = new Message(response[i], this);
}
else {
console.log("flaky");
messager = SnowFlake.getSnowFlakeFromID(response[i].id, Message).getObject();
willbreak = true;
}
this.idToNext.set(messager.snowflake, previd);
this.idToPrev.set(previd, messager.snowflake);
previd = messager.snowflake;
this.messageids.set(messager.snowflake, messager);
if (+i === response.length - 1 && response.length < 100) {
this.topid = previd;
}
if (willbreak) {
break;
}
}
});
return;
}
/**
* Please dont use this, its not implemented.
**/
async grabArround(id) {
throw new Error("please don't call this, no one has implmented it :P");
}
buildmessage(message, next) {
const built = message.buildhtml(next);
document.getElementById("messages").prepend(built);
}
buildmessages() {
async buildmessages() {
const messages = document.getElementById("channelw");
messages.innerHTML = "";
let id;
if (this.messageids.get(this.lastreadmessageid)) {
if (this.lastreadmessageid && this.lastreadmessageid.getObject()) {
id = this.lastreadmessageid;
}
else if (this.lastmessage.snowflake) {
id = this.goBackIds(this.lastmessage.snowflake, 50);
console.log("shouldn't");
}
messages.append(this.infinite.getDiv(id.id));
console.log(this.lastreadmessageid, id.id);
messages.append(await this.infinite.getDiv(id.id));
this.infinite.updatestuff();
this.infinite.watchForChange().then(async (_) => {
await new Promise(resolve => setTimeout(resolve, 100));
this.infinite.focus(id.id, false); //if someone could figure out how to make this work correctly without this, that's be great :P
});
}
goBackIds(id, back) {
while (back !== 0) {
@ -659,7 +714,7 @@ class Channel {
}
;
this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny));
this.permission_overwritesar.push([thing.id, this.permission_overwrites.get(thing.id)]);
this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id, Role), this.permission_overwrites.get(thing.id)]);
}
this.topic = json.topic;
this.nsfw = json.nsfw;
@ -745,6 +800,7 @@ class Channel {
return;
}
const messagez = new Message(messagep.d, this);
this.lastmessage = messagez;
console.log(this.lastmessageid, messagez.snowflake, ":3");
this.idToNext.set(this.lastmessageid, messagez.snowflake);
this.idToPrev.set(messagez.snowflake, this.lastmessageid);
@ -762,7 +818,9 @@ class Channel {
}
}
this.guild.unreads();
if (this === this.localuser.channelfocus) {
this.infinite.addedBottom();
}
if (messagez.author === this.localuser.user) {
return;
}

View file

@ -7,7 +7,6 @@ class Direct extends Guild {
constructor(json, owner) {
super(-1, owner, null);
this.message_notifications = 0;
console.log(json);
this.owner = owner;
if (!this.localuser) {
console.error("Owner was not included, please fix");
@ -41,7 +40,7 @@ class Direct extends Guild {
return Number(-result);
});
}
giveMember(member) {
giveMember(_member) {
console.error("not a real guild, can't give member object");
}
getRole(ID) {
@ -169,13 +168,11 @@ class Group extends Channel {
const noti = document.createElement("div");
noti.classList.add("unread", "notiunread", "pinged");
noti.textContent = "" + this.mentions;
console.log(this.mentions);
div["noti"] = noti;
div.append(noti);
const buildpfp = this.user.buildpfp();
div["all"] = this;
buildpfp.classList.add("mentioned");
console.log(this);
div.append(buildpfp);
sentdms.append(div);
div.onclick = _ => {

View file

@ -40,7 +40,6 @@ class Embed {
return this.guild.localuser;
}
generateRich() {
console.log(this.json);
const div = document.createElement("div");
if (this.json.color) {
div.style.backgroundColor = "#" + this.json.color.toString(16);

View file

@ -10,7 +10,6 @@ class File {
url;
size;
constructor(fileJSON, owner) {
console.log(fileJSON);
this.owner = owner;
this.id = fileJSON.id;
this.filename = fileJSON.filename;

View file

@ -15,7 +15,7 @@ class InfiniteScroller {
this.reachesBottom = reachesBottom;
}
interval;
getDiv(initialId, bottom = true) {
async getDiv(initialId, bottom = true) {
const div = document.createElement("div");
div.classList.add("messagecontainer");
//div.classList.add("flexttb")
@ -28,10 +28,10 @@ class InfiniteScroller {
this.scroll.addEventListener("scroll", this.watchForChange.bind(this));
new ResizeObserver(this.watchForChange.bind(this)).observe(div);
new ResizeObserver(this.watchForChange.bind(this)).observe(scroll);
this.firstElement(initialId);
await this.firstElement(initialId);
this.updatestuff();
await this.watchForChange().then(_ => {
this.updatestuff();
this.watchForChange().then(_ => {
this.scroll.scrollTop = this.scroll.scrollHeight;
});
return div;
}
@ -45,8 +45,8 @@ class InfiniteScroller {
}
//this.watchForChange();
}
firstElement(id) {
const html = this.getHTMLFromID(id);
async firstElement(id) {
const html = await this.getHTMLFromID(id);
this.scroll.append(html);
this.HTMLElements.push([html, id]);
}
@ -59,23 +59,8 @@ class InfiniteScroller {
this.scroll.scrollTop = this.scroll.scrollHeight;
}
}
async watchForChange() {
if (this.currrunning) {
return;
}
else {
this.currrunning = true;
}
async watchForTop() {
let again = false;
if (!this.div) {
this.currrunning = false;
return;
}
/*
if(this.scrollTop===0){
this.scrollTop=10;
}
*/
if (this.scrollTop === 0) {
this.scrollTop = 1;
this.scroll.scrollTop = 1;
@ -87,7 +72,7 @@ class InfiniteScroller {
}
else {
again = true;
const html = this.getHTMLFromID(nextid);
const html = await this.getHTMLFromID(nextid);
this.scroll.prepend(html);
this.HTMLElements.unshift([html, nextid]);
this.scrollTop += 60;
@ -100,6 +85,12 @@ class InfiniteScroller {
await this.destroyFromID(html[1]);
this.scrollTop -= 60;
}
if (again) {
await this.watchForTop();
}
}
async watchForBottom() {
let again = false;
const scrollBottom = this.scrollBottom;
if (scrollBottom < this.minDist) {
const previd = this.HTMLElements.at(-1)[1];
@ -108,7 +99,7 @@ class InfiniteScroller {
}
else {
again = true;
const html = this.getHTMLFromID(nextid);
const html = await this.getHTMLFromID(nextid);
this.scroll.append(html);
this.HTMLElements.push([html, nextid]);
this.scrollBottom += 60;
@ -124,11 +115,51 @@ class InfiniteScroller {
await this.destroyFromID(html[1]);
this.scrollBottom -= 60;
}
this.currrunning = false;
if (again) {
await this.watchForChange();
await this.watchForBottom();
}
}
async watchForChange() {
if (this.currrunning) {
return;
}
else {
this.currrunning = true;
}
if (!this.div) {
this.currrunning = false;
return;
}
await Promise.allSettled([this.watchForBottom(), this.watchForTop()]);
this.currrunning = false;
}
async focus(id, flash = true) {
let element;
for (const thing of this.HTMLElements) {
if (thing[1] === id) {
element = thing[0];
}
}
console.log(element, id, ":3");
if (element) {
element.scrollIntoView();
if (flash) {
element.classList.remove("jumped");
await new Promise(resolve => setTimeout(resolve, 100));
element.classList.add("jumped");
}
}
else {
for (const thing of this.HTMLElements) {
await this.destroyFromID(thing[1]);
}
this.HTMLElements = [];
await this.firstElement(id);
this.updatestuff();
await this.watchForChange();
await new Promise(resolve => setTimeout(resolve, 100));
await this.focus(id, true);
}
}
async delete() {
for (const thing of this.HTMLElements) {

1
.dist/jsontypes.js Normal file
View file

@ -0,0 +1 @@
export {};

View file

@ -85,8 +85,8 @@ class Localuser {
this.typing = [];
}
outoffocus() {
document.getElementById("servers").textContent = "";
document.getElementById("channels").textContent = "";
document.getElementById("servers").innerHTML = "";
document.getElementById("channels").innerHTML = "";
if (this.channelfocus) {
this.channelfocus.infinite.delete();
}
@ -99,7 +99,7 @@ class Localuser {
this.outoffocus();
this.guilds = [];
this.guildids = new Map();
this.ws.close(4000);
this.ws.close(4001);
}
async initwebsocket() {
let returny = null;
@ -114,7 +114,7 @@ class Localuser {
"capabilities": 16381,
"properties": {
"browser": "Jank Client",
"client_build_number": 0,
"client_build_number": 0, //might update this eventually lol
"release_channel": "Custom",
"browser_user_agent": navigator.userAgent
},
@ -262,7 +262,7 @@ class Localuser {
delChannel(json) {
json.guild_id ??= "@me";
this.guildids.get(json.guild_id).delChannel(json);
if (json.guild_id === this.lookingguild.snowflake) {
if (json.guild_id === this.lookingguild.id) {
this.loadGuild(json.guild_id);
}
}
@ -288,7 +288,6 @@ class Localuser {
if (!guild) {
guild = this.guildids.get("@me");
}
console.log(this.guildids, id, guild);
if (this.lookingguild) {
this.lookingguild.html.classList.remove("serveropen");
}
@ -345,7 +344,6 @@ class Localuser {
div.classList.add("home", "servericon");
serverlist.appendChild(div);
div.onclick = _ => {
console.log("clicked :3");
this.createGuild();
};
const guildDiscoveryContainer = document.createElement("div");
@ -356,7 +354,6 @@ class Localuser {
this.guildDiscovery();
});
}
console.log("test");
this.unreads();
}
createGuild() {
@ -369,7 +366,6 @@ class Localuser {
"Invite Link/Code",
"",
function () {
console.log(this);
inviteurl = this.value;
}
],
@ -389,7 +385,6 @@ class Localuser {
method: "POST",
headers: this.headers,
}).then(r => r.json()).then(_ => {
console.log(_);
if (_.message) {
error.textContent = _.message;
}
@ -463,13 +458,11 @@ class Localuser {
this.unreads();
}
unreads() {
console.log(this.guildhtml);
for (const thing of this.guilds) {
if (thing.id === "@me") {
continue;
}
const html = this.guildhtml.get(thing.id);
console.log(html);
thing.unreads(html);
}
}
@ -506,7 +499,6 @@ class Localuser {
updatepfp(file) {
var reader = new FileReader();
reader.readAsDataURL(file);
console.log(this.headers);
reader.onload = () => {
fetch(this.info.api.toString() + "/users/@me", {
method: "PATCH",
@ -515,7 +507,6 @@ class Localuser {
avatar: reader.result,
})
});
console.log(reader.result);
};
}
updatepronouns(pronouns) {
@ -559,7 +550,6 @@ class Localuser {
else {
build += " is typing";
}
console.log(typingtext.classList);
if (showing) {
typingtext.classList.remove("hidden");
document.getElementById("typingtext").textContent = build;
@ -573,7 +563,7 @@ class Localuser {
let file = null;
let newprouns = null;
let newbio = null;
let hypouser = new User(this.user, this, true);
let hypouser = this.user.clone();
function regen() {
hypotheticalProfile.textContent = "";
const hypoprofile = hypouser.buildprofile(-1, -1);
@ -627,7 +617,7 @@ class Localuser {
]
], _ => { }, function () {
console.log(this);
hypouser = new User(this.user, this);
hypouser = this.user.clone();
regen();
file = null;
newprouns = null;

View file

@ -175,6 +175,7 @@ async function login(username, password, captcha) {
console.log(response);
if (response.captcha_sitekey) {
const capt = document.getElementById("h-captcha");
if (!capt.children.length) {
const capty = document.createElement("div");
capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", response.captcha_sitekey);
@ -183,6 +184,11 @@ async function login(username, password, captcha) {
capt.append(script);
capt.append(capty);
}
else {
eval("hcaptcha.reset()");
}
return;
}
else {
adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token });
window.location.href = '/channels/@me';

View file

@ -9,6 +9,7 @@ class Member {
user;
roles;
error;
id;
static contextmenu = new Contextmenu("User Menu");
static setUpContextMenu() {
this.contextmenu.addbutton("Copy user id", function () {
@ -27,11 +28,12 @@ class Member {
let membery = memberjson;
this.roles = [];
if (!error) {
if (memberjson.guild_member) {
if (memberjson["guild_member"]) {
memberjson = memberjson;
membery = memberjson.guild_member;
this.user = memberjson.user;
}
}
membery = membery;
for (const thing of Object.keys(membery)) {
if (thing === "guild") {
continue;
@ -52,7 +54,11 @@ class Member {
this.user = memberjson;
}
else {
this.user = new User(this.user, owner.localuser);
if (SnowFlake.getSnowFlakeFromID(this?.id, User)) {
this.user = SnowFlake.getSnowFlakeFromID(this.id, User).getObject();
return;
}
this.user = new User(membery.user, owner.localuser);
}
}
get guild() {

View file

@ -22,6 +22,7 @@ class Message {
static del;
static resolve;
div;
member;
get id() {
return this.snowflake.id;
}
@ -43,7 +44,7 @@ class Message {
this.channel.setReplying(this);
});
Message.contextmenu.addbutton("Copy message id", function () {
navigator.clipboard.writeText(this.id.id);
navigator.clipboard.writeText(this.id);
});
Message.contextmenu.addbutton("Edit", function () {
this.channel.editing = this;
@ -77,15 +78,28 @@ class Message {
this.snowflake = new SnowFlake(messagejson.id, this);
continue;
}
else if (thing === "member") {
this.member = new Member(messagejson.member, this.guild);
continue;
}
else if (thing === "embeds") {
this.embeds = [];
for (const thing in messagejson.embeds) {
console.log(thing, messagejson.embeds);
this.embeds[thing] = new Embed(messagejson.embeds[thing], this);
}
continue;
}
this[thing] = messagejson[thing];
}
for (const thing in this.embeds) {
console.log(thing, this.embeds);
this.embeds[thing] = new Embed(this.embeds[thing], this);
this.author = new User(messagejson.author, this.localuser);
for (const thing in messagejson.mentions) {
this.mentions[thing] = new User(messagejson.mentions[thing], this.localuser);
}
this.author = new User(this.author, this.localuser);
for (const thing in this.mentions) {
this.mentions[thing] = new User(this.mentions[thing], this.localuser);
if (!this.member && this.guild.id !== "@me") {
this.author.resolvemember(this.guild).then(_ => {
this.member = _;
});
}
if (this.mentions.length || this.mention_roles.length) { //currently mention_roles isn't implemented on the spacebar servers
console.log(this.mentions, this.mention_roles);
@ -227,6 +241,10 @@ class Message {
username.textContent = author.username;
author.bind(username);
});
reply.onclick = _ => {
console.log("this got clicked :3");
this.channel.infinite.focus(this.message_reference.message_id);
};
div.appendChild(replyline);
}
build.classList.add("message");
@ -300,6 +318,7 @@ class Message {
messagedwrap.appendChild(attach);
}
if (this.embeds.length) {
console.log(this.embeds);
const embeds = document.createElement("div");
embeds.classList.add("flexltr");
for (const thing of this.embeds) {

View file

@ -14,13 +14,14 @@ async function registertry(e) {
const password = elements[3].value;
const dateofbirth = elements[5].value;
const apiurl = new URL(JSON.parse(localStorage.getItem("instanceinfo")).api);
fetch(apiurl + "/auth/register", {
await fetch(apiurl + "/auth/register", {
body: JSON.stringify({
date_of_birth: dateofbirth,
email: email,
username: username,
password: password,
consent: elements[6].checked,
captcha_key: elements[7]?.value
}),
headers: {
"content-type": "application/json"
@ -28,10 +29,43 @@ async function registertry(e) {
method: "POST"
}).then(e => {
e.json().then(e => {
if (e.captcha_sitekey) {
const capt = document.getElementById("h-captcha");
if (!capt.children.length) {
const capty = document.createElement("div");
capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", e.captcha_sitekey);
const script = document.createElement("script");
script.src = "https://js.hcaptcha.com/1/api.js";
capt.append(script);
capt.append(capty);
}
else {
eval("hcaptcha.reset()");
}
return;
}
if (!e.token) {
console.log(e);
if (e.errors.consent) {
error(elements[6], e.errors.consent._errors[0].message);
}
else if (e.errors.password) {
error(elements[3], "Password: " + e.errors.password._errors[0].message);
}
else if (e.errors.username) {
error(elements[2], "Username: " + e.errors.username._errors[0].message);
}
else if (e.errors.email) {
error(elements[1], "Email: " + e.errors.email._errors[0].message);
}
else if (e.errors.date_of_birth) {
error(elements[5], "Date of Birth: " + e.errors.date_of_birth._errors[0].message);
}
else {
document.getElementById("wrong").textContent = e.errors[Object.keys(e.errors)[0]]._errors[0].message;
}
}
else {
localStorage.setItem("token", e.token);
window.location.href = '/channels/@me';
@ -41,6 +75,21 @@ async function registertry(e) {
//document.getElementById("wrong").textContent=h;
// console.log(h);
}
function error(e, message) {
const p = e.parentElement;
let element = p.getElementsByClassName("suberror")[0];
if (!element) {
const div = document.createElement("div");
div.classList.add("suberror", "suberrora");
p.append(div);
element = div;
}
else {
element.classList.remove("suberror");
setTimeout(_ => { element.classList.add("suberror"); }, 100);
}
element.textContent = message;
}
let TOSa = document.getElementById("TOSa");
async function tosLogic() {
const apiurl = new URL(JSON.parse(localStorage.getItem("instanceinfo")).api);

View file

@ -15,9 +15,14 @@ class SnowFlake {
}
if (SnowFlake.SnowFlakes.get(obj.constructor).get(id)) {
const snowflake = SnowFlake.SnowFlakes.get(obj.constructor).get(id).deref();
if (snowflake) {
snowflake.obj = obj;
return snowflake;
}
else {
SnowFlake.SnowFlakes.get(obj.constructor).delete(id);
}
}
this.id = id;
SnowFlake.SnowFlakes.get(obj.constructor).set(id, new WeakRef(this));
SnowFlake.FinalizationRegistry.register(this, [id, obj.constructor]);
@ -33,7 +38,13 @@ class SnowFlake {
}
const snowflake = SnowFlake.SnowFlakes.get(type).get(id);
if (snowflake) {
return snowflake.deref();
const obj = snowflake.deref();
if (obj) {
return obj;
}
else {
SnowFlake.SnowFlakes.get(type).delete(id);
}
}
{
const snowflake = new SnowFlake(id, undefined);
@ -42,6 +53,24 @@ class SnowFlake {
return snowflake;
}
}
static hasSnowFlakeFromID(id, type) {
if (!SnowFlake.SnowFlakes.get(type)) {
return false;
}
const flake = SnowFlake.SnowFlakes.get(type).get(id);
if (flake) {
const flake2 = flake.deref()?.getObject();
if (flake2) {
return true;
}
else {
return false;
}
}
else {
return false;
}
}
getUnixTime() {
return Number((BigInt(this.id) >> 22n) + 1420070400000n);
}

View file

@ -14,6 +14,31 @@ class User {
discriminator;
pronouns;
bot;
public_flags;
accent_color;
banner;
premium_since;
premium_type;
theme_colors;
badge_ids;
clone() {
return new User({
username: this.username,
id: this.id + "#clone",
public_flags: this.public_flags,
discriminator: this.discriminator,
avatar: this.avatar,
accent_color: this.accent_color,
banner: this.banner,
bio: this.bio.rawString,
premium_since: this.premium_since,
premium_type: this.premium_type,
bot: this.bot,
theme_colors: this.theme_colors,
pronouns: this.pronouns,
badge_ids: this.badge_ids
}, this.owner);
}
get id() {
return this.snowflake.id;
}
@ -29,13 +54,13 @@ class User {
});
});
}
static checkuser(userjson, owner) {
if (User.userids[userjson.id]) {
return User.userids[userjson.id];
static checkuser(user, owner) {
if (User.userids[user.id]) {
return User.userids[user.id];
}
else {
const tempuser = new User(userjson, owner, true);
User.userids[userjson.id] = tempuser;
const tempuser = new User(user, owner, true);
User.userids[user.id] = tempuser;
return tempuser;
}
}
@ -69,7 +94,7 @@ class User {
}
}
async resolvemember(guild) {
await Member.resolve(this, guild);
return await Member.resolve(this, guild);
}
buildpfp() {
const pfp = document.createElement('img');

2
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "0.1.0",
"license": "GPL-3.0",
"dependencies": {
"express": "latest"
"express": "^4.19.2"
},
"devDependencies": {
"@eslint/js": "^9.7.0",

View file

@ -10,6 +10,7 @@ import { Settings, RoleList } from "./settings.js";
import { Role } from "./role.js";
import {InfiniteScroller} from "./infiniteScroller.js";
import { SnowFlake } from "./snowflake.js";
import { channeljson, messagejson, readyjson } from "./jsontypes.js";
declare global {
interface NotificationOptions {
@ -93,21 +94,31 @@ class Channel{
this.infinite=new InfiniteScroller(async function(this:Channel,id:string,offset:number):Promise<string>{
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
if(offset===1){
if(this.idToPrev.get(snowflake)){
if(this.idToPrev.has(snowflake)){
return this.idToPrev.get(snowflake)?.id;
}else{
await this.grabBefore(id);
return this.idToPrev.get(snowflake)?.id;
}
}else{
if(this.idToNext.has(snowflake)){
return this.idToNext.get(snowflake)?.id;
}else if(this.lastmessage.id!==id){
await this.grabAfter(id);
return this.idToNext.get(snowflake)?.id;
}else{
console.log("at bottom")
}
}
}.bind(this),
function(this:Channel,id:string){
async function(this:Channel,id:string){
let res:Function;
const promise=new Promise(_=>{res=_;}) as Promise<void>;
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
const html=this.messageids.get(snowflake).buildhtml(this.messageids.get(this.idToPrev.get(snowflake)),promise);
const snowflake=SnowFlake.getSnowFlakeFromID(id,Message);
if(!snowflake.getObject()){
await this.grabArround(id);
}
const html=snowflake.getObject().buildhtml(this.messageids.get(this.idToPrev.get(snowflake)),promise);
ids[id]=res;
return html;
}.bind(this),
@ -119,12 +130,11 @@ class Channel{
this.readbottom.bind(this)
);
}
constructor(json,owner:Guild){
constructor(json:channeljson|-1,owner:Guild){
if(json===-1){
return;
}
this.editing;
this.type=json.type;
this.owner=owner;
@ -141,7 +151,7 @@ class Channel{
for(const thing of json.permission_overwrites){
if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){continue;};
this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny));
this.permission_overwritesar.push([thing.id,this.permission_overwrites.get(thing.id)]);
this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id,Role),this.permission_overwrites.get(thing.id)]);
}
this.topic=json.topic;
@ -163,7 +173,7 @@ class Channel{
get info(){
return this.owner.info;
}
readStateInfo(json){
readStateInfo(json:readyjson["d"]["read_state"]["entries"][0]){
this.lastreadmessageid=SnowFlake.getSnowFlakeFromID(json.last_message_id,Message);
this.mentions=json.mention_count;
this.mentions??=0;
@ -540,7 +550,7 @@ class Channel{
return;
}
this.makereplybox();
this.buildmessages();
await this.buildmessages();
history.pushState(null, null,"/channels/"+this.guild_id+"/"+this.snowflake);
document.getElementById("channelname").textContent="#"+this.name;
console.log(this);
@ -572,69 +582,114 @@ class Channel{
}
}
}
delChannel(json){
delChannel(json:channeljson){
const build=[];
for(const thing of this.children){
if(thing.snowflake!==json.id){
if(thing.id!==json.id){
build.push(thing)
}
}
this.children=build;
}
async grabBefore(id:string){
if(this.allthewayup){
async grabAfter(id:string){
console.log(id,this.lastmessage.id)
if(id===this.lastmessage.id){
return;
}
await fetch(this.info.api.toString()+"/channels/"+this.snowflake+"/messages?before="+id+"&limit=100",{
await fetch(this.info.api.toString()+"/channels/"+this.id+"/messages?limit=100&after="+id,{
headers:this.headers
}).then((j)=>{return j.json()}).then(response=>{
let next:Message;
if(response.length===0){
this.allthewayup=true;
}
let previd=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
let previd:SnowFlake<Message>=SnowFlake.getSnowFlakeFromID(id,Message);
for(const i in response){
let messager:Message;
if(!next){
let willbreak=false
if(!SnowFlake.hasSnowFlakeFromID(response[i].id,Message)){
messager=new Message(response[i],this);
}else{
messager=next;
messager=SnowFlake.getSnowFlakeFromID(response[i].id,Message).getObject();
willbreak=true;
}
if(response[+i+1]!==undefined){
next=new Message(response[+i+1],this);
}else{
next=undefined;
console.log("ohno",+i+1);
}
if(this.messageids.get(messager.snowflake)===undefined){
this.idToNext.set(messager.snowflake,previd);
this.idToPrev.set(previd,messager.snowflake);
this.idToPrev.set(messager.snowflake,previd);
this.idToNext.set(previd,messager.snowflake);
previd=messager.snowflake;
this.messageids.set(messager.snowflake,messager);
}else{
console.log("How???")
if(willbreak){
break;
}
}
//out.buildmessages();
})
return;
}
topid:SnowFlake<Message>;
async grabBefore(id:string){
if(this.topid&&id===this.topid.id){
return;
}
await fetch(this.info.api.toString()+"/channels/"+this.snowflake+"/messages?before="+id+"&limit=100",{
headers:this.headers
}).then((j)=>{return j.json()}).then((response:messagejson[])=>{
if(response.length<100){
this.allthewayup=true;
if(response.length===0){
this.topid=SnowFlake.getSnowFlakeFromID(id,Message);
}
}
let previd=SnowFlake.getSnowFlakeFromID(id,Message) as SnowFlake<Message>;
for(const i in response){
let messager:Message;
let willbreak=false;
if(!SnowFlake.hasSnowFlakeFromID(response[i].id,Message)){
messager=new Message(response[i],this);
}else{
console.log("flaky")
messager=SnowFlake.getSnowFlakeFromID(response[i].id,Message).getObject();
willbreak=true;
}
this.idToNext.set(messager.snowflake,previd);
this.idToPrev.set(previd,messager.snowflake);
previd=messager.snowflake;
this.messageids.set(messager.snowflake,messager);
if(+i===response.length-1&&response.length<100){
this.topid=previd;
}
if(willbreak){
break;
}
}
})
return;
}
/**
* Please dont use this, its not implemented.
**/
async grabArround(id:string){//currently unused and no plans to use it yet
throw new Error("please don't call this, no one has implmented it :P")
}
buildmessage(message:Message,next:Message){
const built=message.buildhtml(next);
document.getElementById("messages").prepend(built);
}
buildmessages(){
async buildmessages(){
const messages=document.getElementById("channelw");
messages.innerHTML="";
let id:SnowFlake<Message>;
if(this.messageids.get(this.lastreadmessageid)){
if(this.lastreadmessageid&&this.lastreadmessageid.getObject()){
id=this.lastreadmessageid;
}else if(this.lastmessage.snowflake){
id=this.goBackIds(this.lastmessage.snowflake,50);
console.log("shouldn't")
}
messages.append(this.infinite.getDiv(id.id));
console.log(this.lastreadmessageid,id.id);
messages.append(await this.infinite.getDiv(id.id));
this.infinite.updatestuff();
this.infinite.watchForChange().then(async _=>{
await new Promise(resolve => setTimeout(resolve, 100));
this.infinite.focus(id.id,false);//if someone could figure out how to make this work correctly without this, that's be great :P
})
}
private goBackIds(id:SnowFlake<Message>,back:number):SnowFlake<Message>{
while(back!==0){
@ -649,7 +704,7 @@ class Channel{
}
return id;
}
updateChannel(json){
updateChannel(json:channeljson){
this.type=json.type;
this.name=json.name;
this.parent_id=new SnowFlake(json.parent_id,undefined);
@ -661,7 +716,7 @@ class Channel{
for(const thing of json.permission_overwrites){
if(thing.id==="1182819038095799904"||thing.id==="1182820803700625444"){continue;};
this.permission_overwrites.set(thing.id,new Permissions(thing.allow,thing.deny));
this.permission_overwritesar.push([thing.id,this.permission_overwrites.get(thing.id)]);
this.permission_overwritesar.push([SnowFlake.getSnowFlakeFromID(thing.id,Role),this.permission_overwrites.get(thing.id)]);
}
this.topic=json.topic;
this.nsfw=json.nsfw;
@ -741,6 +796,7 @@ class Channel{
messageCreate(messagep:any):void{
if(!this.hasPermission("VIEW_CHANNEL")){return}
const messagez=new Message(messagep.d,this);
this.lastmessage=messagez;
console.log(this.lastmessageid,messagez.snowflake,":3");
this.idToNext.set(this.lastmessageid,messagez.snowflake);
this.idToPrev.set(messagez.snowflake,this.lastmessageid);
@ -757,7 +813,9 @@ class Channel{
}
}
this.guild.unreads();
if(this===this.localuser.channelfocus){
this.infinite.addedBottom();
}
if(messagez.author===this.localuser.user){
return;
}

View file

@ -5,12 +5,12 @@ import { Localuser } from "./localuser.js";
import {User} from "./user.js";
import { Member } from "./member.js";
import { SnowFlake } from "./snowflake.js";
import { dirrectjson, memberjson } from "./jsontypes.js";
class Direct extends Guild{
constructor(json,owner:Localuser){
constructor(json:dirrectjson[],owner:Localuser){
super(-1,owner,null);
this.message_notifications=0;
console.log(json);
this.owner=owner;
if(!this.localuser){
console.error("Owner was not included, please fix")
@ -44,13 +44,13 @@ class Direct extends Guild{
return Number(-result);
});
}
giveMember(member){
giveMember(_member:memberjson){
console.error("not a real guild, can't give member object")
}
getRole(ID){
getRole(ID:string){
return null;
}
hasRole(r){
hasRole(r:string){
return false;
}
isAdmin(){
@ -64,7 +64,7 @@ class Direct extends Guild{
}
class Group extends Channel{
user:User;
constructor(json,owner:Direct){
constructor(json:dirrectjson,owner:Direct){
super(-1,owner);
this.owner=owner;
this.headers=this.guild.headers;
@ -166,13 +166,11 @@ class Group extends Channel{
const noti=document.createElement("div");
noti.classList.add("unread","notiunread","pinged");
noti.textContent=""+this.mentions;
console.log(this.mentions)
div["noti"]=noti;
div.append(noti)
const buildpfp=this.user.buildpfp();
div["all"]=this;
buildpfp.classList.add("mentioned");
console.log(this);
div.append(buildpfp)
sentdms.append(div);
div.onclick=_=>{

View file

@ -1,17 +1,18 @@
import {Fullscreen} from "./fullscreen.js";
import {Message} from "./message.js";
import {MarkDown} from "./markdown.js";
import { embedjson } from "./jsontypes.js";
class Embed{
type:string;
owner:Message;
json;
constructor(json, owner:Message){
json:embedjson;
constructor(json:embedjson, owner:Message){
this.type=this.getType(json);
this.owner=owner;
this.json=json;
}
getType(json){
getType(json:embedjson){
return json.type||"rich";
}
generateHTML(){
@ -42,7 +43,6 @@ class Embed{
return this.guild.localuser;
}
generateRich(){
console.log(this.json)
const div=document.createElement("div");
if(this.json.color){
div.style.backgroundColor="#"+this.json.color.toString(16);

View file

@ -1,6 +1,7 @@
import { Message } from "./message.js";
import { Fullscreen } from "./fullscreen.js";
type filejson= {id:string,filename:string,content_type:string,width:number,height:number,proxy_url:string|undefined,url:string,size:number};
import { filejson } from "./jsontypes.js";
class File{
owner:Message;
id:string;
@ -12,7 +13,6 @@ class File{
url:string;
size:number;
constructor(fileJSON:filejson,owner:Message){
console.log(fileJSON);
this.owner=owner;
this.id=fileJSON.id;
this.filename=fileJSON.filename;

View file

@ -7,6 +7,7 @@ import {Member} from "./member.js";
import {Settings,RoleList} from "./settings.js";
import {Permissions} from "./permissions.js";
import { SnowFlake } from "./snowflake.js";
import { channeljson, guildjson } from "./jsontypes.js";
class Guild{
owner:Localuser;
headers:Localuser["headers"];
@ -79,7 +80,7 @@ class Guild{
s1.options.push(new RoleList(permlist,this,this.updateRolePermissions.bind(this)));
settings.show();
}
constructor(json,owner:Localuser,member){
constructor(json:guildjson|-1,owner:Localuser,member){
if(json===-1){
return;
}
@ -404,7 +405,7 @@ class Guild{
loadGuild(){
this.localuser.loadGuild(this.id);
}
updateChannel(json){
updateChannel(json:channeljson){
SnowFlake.getSnowFlakeFromID(json.id,Channel).getObject().updateChannel(json);
this.headchannels=[];
for(const thing of this.channels){
@ -417,7 +418,7 @@ class Guild{
}
this.printServers();
}
createChannelpac(json){
createChannelpac(json:channeljson){
const thischannel=new Channel(json,this);
this.channelids[json.id]=thischannel;
this.channels.push(thischannel);
@ -470,7 +471,7 @@ class Guild{
]);
channelselect.show();
}
delChannel(json){
delChannel(json:channeljson){
const channel=this.channelids[json.id];
delete this.channelids[json.id];

View file

@ -1,6 +1,6 @@
class InfiniteScroller{
readonly getIDFromOffset:(ID:string,offset:number)=>Promise<string>;
readonly getHTMLFromID:(ID:string)=>HTMLElement;
readonly getHTMLFromID:(ID:string)=>Promise<HTMLElement>;
readonly destroyFromID:(ID:string)=>Promise<boolean>;
readonly reachesBottom:()=>void;
private readonly minDist=3000;
@ -15,7 +15,7 @@ class InfiniteScroller{
this.reachesBottom=reachesBottom;
}
interval:NodeJS.Timeout;
getDiv(initialId:string,bottom=true):HTMLDivElement{
async getDiv(initialId:string,bottom=true):Promise<HTMLDivElement>{
const div=document.createElement("div");
div.classList.add("messagecontainer");
//div.classList.add("flexttb")
@ -30,10 +30,10 @@ class InfiniteScroller{
new ResizeObserver(this.watchForChange.bind(this)).observe(div);
new ResizeObserver(this.watchForChange.bind(this)).observe(scroll);
this.firstElement(initialId);
await this.firstElement(initialId)
this.updatestuff();
await this.watchForChange().then(_=>{
this.updatestuff();
this.watchForChange().then(_=>{
this.scroll.scrollTop=this.scroll.scrollHeight;
})
return div;
}
@ -47,8 +47,8 @@ class InfiniteScroller{
}
//this.watchForChange();
}
firstElement(id:string){
const html=this.getHTMLFromID(id);
async firstElement(id:string){
const html=await this.getHTMLFromID(id);
this.scroll.append(html);
this.HTMLElements.push([html,id]);
}
@ -61,33 +61,20 @@ class InfiniteScroller{
this.scroll.scrollTop=this.scroll.scrollHeight;
}
}
async watchForChange():Promise<void>{
if(this.currrunning){
return;
}else{
this.currrunning=true;
}
private async watchForTop():Promise<void>{
let again=false;
if(!this.div){this.currrunning=false;return}
/*
if(this.scrollTop===0){
this.scrollTop=10;
}
*/
if(this.scrollTop===0){
this.scrollTop=1;
this.scroll.scrollTop=1;
}
if(this.scrollTop<this.minDist){
const previd=this.HTMLElements.at(0)[1];
const nextid=await this.getIDFromOffset(previd,1);
if(!nextid){
}else{
again=true;
const html=this.getHTMLFromID(nextid);
const html=await this.getHTMLFromID(nextid);
this.scroll.prepend(html);
this.HTMLElements.unshift([html,nextid]);
this.scrollTop+=60;
@ -100,6 +87,12 @@ class InfiniteScroller{
await this.destroyFromID(html[1]);
this.scrollTop-=60;
}
if(again){
await this.watchForTop();
}
}
async watchForBottom():Promise<void>{
let again=false;
const scrollBottom = this.scrollBottom;
if(scrollBottom<this.minDist){
@ -109,7 +102,7 @@ class InfiniteScroller{
if(!nextid){
}else{
again=true;
const html=this.getHTMLFromID(nextid);
const html=await this.getHTMLFromID(nextid);
this.scroll.append(html);
this.HTMLElements.push([html,nextid]);
this.scrollBottom+=60;
@ -125,13 +118,47 @@ class InfiniteScroller{
await this.destroyFromID(html[1]);
this.scrollBottom-=60;
}
this.currrunning=false;
if(again){
await this.watchForChange();
await this.watchForBottom();
}
}
async watchForChange():Promise<void>{
if(this.currrunning){
return;
}else{
this.currrunning=true;
}
if(!this.div){this.currrunning=false;return}
await Promise.allSettled([this.watchForBottom(),this.watchForTop()])
this.currrunning=false;
}
async focus(id:string,flash=true){
let element:HTMLElement;
for(const thing of this.HTMLElements){
if(thing[1]===id){
element=thing[0];
}
}
console.log(element,id,":3");
if(element){
element.scrollIntoView();
if(flash){
element.classList.remove("jumped");
await new Promise(resolve => setTimeout(resolve, 100));
element.classList.add("jumped");
}
}else{
for(const thing of this.HTMLElements){
await this.destroyFromID(thing[1]);
}
this.HTMLElements=[];
await this.firstElement(id);
this.updatestuff();
await this.watchForChange();
await new Promise(resolve => setTimeout(resolve, 100));
await this.focus(id,true);
}
}
async delete():Promise<void>{
for(const thing of this.HTMLElements){
await this.destroyFromID(thing[1]);

320
webpage/jsontypes.ts Normal file
View file

@ -0,0 +1,320 @@
type readyjson={
op:number;
t:string;
s:number;
d:{
v:number;
user:mainuserjson;
user_settings:{
index: number,
afk_timeout: number,
allow_accessibility_detection: boolean,
animate_emoji: boolean,
animate_stickers: number,
contact_sync_enabled: boolean,
convert_emoticons: boolean,
custom_status: string,
default_guilds_restricted: boolean,
detect_platform_accounts: boolean,
developer_mode: boolean,
disable_games_tab: boolean,
enable_tts_command: boolean,
explicit_content_filter: 0,
friend_discovery_flags: 0,
friend_source_flags: {
all: boolean
},//might be missing things here
gateway_connected: boolean,
gif_auto_play: boolean,
guild_folders: [],//need an example of this not empty
guild_positions: [],//need an example of this not empty
inline_attachment_media: boolean,
inline_embed_media: boolean,
locale: string,
message_display_compact: boolean,
native_phone_integration_enabled: boolean,
render_embeds: boolean,
render_reactions: boolean,
restricted_guilds: [],//need an example of this not empty
show_current_game: boolean,
status: string,
stream_notifications_enabled: boolean,
theme: string,
timezone_offset: number,
view_nsfw_guilds: boolean
};
guilds:guildjson[];
relationships:any[];
read_state:{
entries:{
id: string,
channel_id: string,
last_message_id: string,
last_pin_timestamp: string,
mention_count: number //in theory, the server doesn't actually send this as far as I'm aware
}[],
partial: boolean,
version: number
};
user_guild_settings:{
entries:{
channel_overrides: unknown[],//will have to find example
message_notifications: number,
flags: number,
hide_muted_channels: boolean,
mobile_push: boolean,
mute_config: null,
mute_scheduled_events: boolean,
muted: boolean,
notify_highlights: number,
suppress_everyone: boolean,
suppress_roles: boolean,
version: number,
guild_id: string
}[],
partial: boolean,
version: number
};
private_channels:dirrectjson[];
session_id:string;
country_code:string;
users:userjson[];
merged_members:memberjson[];
sessions:{
active: boolean,
activities: [],//will need to find example of this
client_info: {
version: number
},
session_id: string,
status: string
}[];
resume_gateway_url:string;
consents:{
personalization: {
consented: boolean
}
};
experiments: [],//not sure if I need to do this :P
guild_join_requests: [],//need to get examples
connected_accounts: [],//need to get examples
guild_experiments: [],//need to get examples
geo_ordered_rtc_regions: [],//need to get examples
api_code_version: number,
friend_suggestion_count: number,
analytics_token: string,
tutorial: boolean,
session_type: string,
auth_session_id_hash: string,
notification_settings: {
flags: number
}
}
}
type mainuserjson= userjson & {
flags: number,
mfa_enabled?: boolean,
email?: string,
phone?: string,
verified: boolean,
nsfw_allowed: boolean,
premium: boolean,
purchased_flags: number,
premium_usage_flags: number,
disabled: boolean
}
type userjson={
username: string,
discriminator: string,
id: string,
public_flags: number,
avatar: string,
accent_color: string,
banner: string,
bio: string,
bot: boolean,
premium_since: string,
premium_type: number,
theme_colors: string,
pronouns: string,
badge_ids: string,
}
type memberjson= {
index?:number,
id: string,
user: userjson,
guild_id: string,
guild: {
id: string
},
nick: string,
roles: string[],
joined_at: string,
premium_since: string,
deaf: boolean,
mute: boolean,
pending: boolean,
last_message_id: boolean
}
type guildjson={
application_command_counts: {[key:string]:number},
channels: channeljson[],
data_mode: string,
emojis: [],
guild_scheduled_events: [],
id: string,
large: boolean,
lazy: boolean,
member_count: number,
premium_subscription_count: number,
properties: {
name: string,
description: string,
icon: string,
splash: string,
banner: string,
features: string[],
preferred_locale: string,
owner_id: string,
application_id: string,
afk_channel_id: string,
afk_timeout: number,
system_channel_id: string,
verification_level: number,
explicit_content_filter: number,
default_message_notifications: number,
mfa_level: number,
vanity_url_code: number,
premium_tier: number,
premium_progress_bar_enabled: boolean,
system_channel_flags: number,
discovery_splash: string,
rules_channel_id: string,
public_updates_channel_id: string,
max_video_channel_users: number,
max_members: number,
nsfw_level: number,
hub_type: null,
home_header: null,
id: string,
latest_onboarding_question_id: string,
max_stage_video_channel_users: number,
nsfw: boolean,
safety_alerts_channel_id: string
},
roles: rolesjson[],
stage_instances: [],
stickers: [],
threads: [],
version: string,
guild_hashes: {},
joined_at: string
}
type channeljson={
id: string,
created_at: string,
name: string,
icon: string,
type: number,
last_message_id: string,
guild_id: string,
parent_id: string,
last_pin_timestamp: string,
default_auto_archive_duration: number,
permission_overwrites: {
id:string,
allow:string,
deny:string,
}[],
video_quality_mode: null,
nsfw: boolean,
topic: string,
retention_policy_id: string,
flags: number,
default_thread_rate_limit_per_user: number,
position: number
}
type rolesjson={
id: string,
guild_id: string,
color: number,
hoist: boolean,
managed: boolean,
mentionable: boolean,
name: string,
permissions: string,
position: number,
icon: string,
unicode_emoji: string,
flags: number
}
type dirrectjson={
id: string,
flags: number,
last_message_id: string,
type: number,
recipients: userjson[],
is_spam: boolean
}
type messagejson={
id: string,
channel_id: string,
guild_id: string,
author: userjson,
member?: memberjson,
content: string,
timestamp: string,
edited_timestamp: string,
tts: boolean,
mention_everyone: boolean,
mentions: [], //need examples to fix
mention_roles: [], //need examples to fix
attachments: filejson[],
embeds: embedjson[],
reactions: [], //ToDo
nonce: string,
pinned: boolean,
type: number
}
type filejson={
id:string,
filename:string,
content_type:string,
width:number,
height:number,
proxy_url:string|undefined,
url:string,
size:number
};
type embedjson={
type:string|null,
color?:number,
author:{
icon_url?:string,
name?:string,
url?:string,
title?:string,
},
title?:string,
url?:string,
description?:string,
fields?:{
name:string,
value:string,
inline:boolean,
}[],
footer?:{
icon_url?:string,
text?:string,
thumbnail?:string,
},
timestamp?:string,
thumbnail:{
proxy_url:string,
url:string,
},
provider:{
name:string,
}
}
export {readyjson,dirrectjson,channeljson,guildjson,rolesjson,userjson,memberjson,mainuserjson,messagejson,filejson,embedjson};

View file

@ -7,6 +7,7 @@ import {Fullscreen} from "./fullscreen.js";
import {setTheme, Specialuser} from "./login.js";
import { SnowFlake } from "./snowflake.js";
import { Message } from "./message.js";
import { channeljson, readyjson, userjson } from "./jsontypes.js";
const wsCodesRetry=new Set([4000,4003,4005,4007,4008,4009]);
@ -21,7 +22,7 @@ class Localuser{
usersettings:Fullscreen;
userConnections:Fullscreen;
devPortal:Fullscreen;
ready;
ready:readyjson;
guilds:Guild[];
guildids:Map<string,Guild>;
user:User;
@ -42,7 +43,7 @@ class Localuser{
this.info=this.serverurls;
this.headers={"Content-type": "application/json; charset=UTF-8",Authorization:this.userinfo.token};
}
gottenReady(ready):void{
gottenReady(ready:readyjson):void{
this.usersettings=null;
this.initialized=true;
this.ready=ready;
@ -90,8 +91,8 @@ class Localuser{
this.typing=[];
}
outoffocus():void{
document.getElementById("servers").textContent="";
document.getElementById("channels").textContent="";
document.getElementById("servers").innerHTML="";
document.getElementById("channels").innerHTML="";
if(this.channelfocus){
this.channelfocus.infinite.delete();
}
@ -104,7 +105,7 @@ class Localuser{
this.outoffocus();
this.guilds=[];
this.guildids=new Map();
this.ws.close(4000)
this.ws.close(4001)
}
async initwebsocket():Promise<void>{
let returny=null
@ -119,7 +120,7 @@ class Localuser{
"capabilities": 16381,
"properties": {
"browser": "Jank Client",
"client_build_number": 0,
"client_build_number": 0,//might update this eventually lol
"release_channel": "Custom",
"browser_user_agent": navigator.userAgent
},
@ -154,7 +155,7 @@ class Localuser{
SnowFlake.getSnowFlakeFromID(temp.d.id,Message).getObject().deleteEvent();
break;
case "READY":
this.gottenReady(temp);
this.gottenReady(temp as readyjson);
this.genusersettings();
returny();
break;
@ -226,7 +227,6 @@ class Localuser{
this.unload();
document.getElementById("loading").classList.remove("doneloading");
document.getElementById("loading").classList.add("loading");
if (((event.code>1000 && event.code<1016) || wsCodesRetry.has(event.code))) {
if (this.connectionSucceed!==0 && Date.now()>this.connectionSucceed+20000) this.errorBackoff=0;
else this.errorBackoff++;
@ -258,24 +258,24 @@ class Localuser{
}
return undefined;
}
updateChannel(json):void{
updateChannel(json:channeljson):void{
SnowFlake.getSnowFlakeFromID(json.guild_id,Guild).getObject().updateChannel(json);
if(json.guild_id===this.lookingguild.id){
this.loadGuild(json.guild_id);
}
}
createChannel(json):void{
createChannel(json:channeljson):void{
json.guild_id??="@me";
SnowFlake.getSnowFlakeFromID(json.guild_id,Guild).getObject().createChannelpac(json);
if(json.guild_id===this.lookingguild.id){
this.loadGuild(json.guild_id);
}
}
delChannel(json):void{
delChannel(json:channeljson):void{
json.guild_id??="@me";
this.guildids.get(json.guild_id).delChannel(json);
if(json.guild_id===this.lookingguild.snowflake){
if(json.guild_id===this.lookingguild.id){
this.loadGuild(json.guild_id);
}
}
@ -302,7 +302,6 @@ class Localuser{
if(!guild){
guild=this.guildids.get("@me");
}
console.log(this.guildids,id,guild);
if(this.lookingguild){
this.lookingguild.html.classList.remove("serveropen");
}
@ -363,7 +362,6 @@ class Localuser{
div.classList.add("home","servericon")
serverlist.appendChild(div)
div.onclick=_=>{
console.log("clicked :3")
this.createGuild();
}
@ -376,7 +374,6 @@ class Localuser{
});
}
console.log("test");
this.unreads();
}
createGuild(){
@ -390,7 +387,6 @@ class Localuser{
"Invite Link/Code",
"",
function(){
console.log(this)
inviteurl=this.value;
}
],
@ -410,7 +406,6 @@ class Localuser{
method:"POST",
headers:this.headers,
}).then(r=>r.json()).then(_=>{
console.log(_);
if(_.message){
error.textContent=_.message;
}
@ -492,11 +487,9 @@ class Localuser{
this.unreads();
}
unreads():void{
console.log(this.guildhtml)
for(const thing of this.guilds){
if(thing.id==="@me"){continue;}
const html=this.guildhtml.get(thing.id);
console.log(html);
thing.unreads(html);
}
}
@ -532,7 +525,6 @@ class Localuser{
updatepfp(file:Blob):void{
var reader = new FileReader();
reader.readAsDataURL(file);
console.log(this.headers);
reader.onload = ()=>{
fetch(this.info.api.toString()+"/users/@me",{
method:"PATCH",
@ -541,7 +533,6 @@ class Localuser{
avatar:reader.result,
})
});
console.log(reader.result);
};
}
@ -585,7 +576,6 @@ class Localuser{
}else{
build+=" is typing";
}
console.log(typingtext.classList);
if(showing){
typingtext.classList.remove("hidden");
document.getElementById("typingtext").textContent=build;
@ -598,7 +588,7 @@ class Localuser{
let file=null;
let newprouns=null;
let newbio=null;
let hypouser=new User(this.user,this,true);
let hypouser=this.user.clone();
function regen(){
hypotheticalProfile.textContent="";
const hypoprofile=hypouser.buildprofile(-1,-1);
@ -652,9 +642,9 @@ class Localuser{
["vdiv",
["html",hypotheticalProfile]
]
],_=>{},function(){
],_=>{},function(this:Localuser){
console.log(this);
hypouser=new User(this.user,this);
hypouser=this.user.clone();
regen();
file=null;
newprouns=null;

View file

@ -173,7 +173,9 @@ async function login(username:string, password:string, captcha:string){
console.log(response);
if(response.captcha_sitekey){
const capt=document.getElementById("h-captcha");
if(!capt.children.length){
const capty=document.createElement("div");
capty.classList.add("h-captcha");
@ -182,6 +184,10 @@ async function login(username:string, password:string, captcha:string){
script.src="https://js.hcaptcha.com/1/api.js";
capt.append(script);
capt.append(capty);
}else{
eval("hcaptcha.reset()");
}
return;
}else{
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token});
window.location.href = '/channels/@me';

View file

@ -3,6 +3,7 @@ import {Role} from "./role.js";
import {Guild} from "./guild.js";
import { Contextmenu } from "./contextmenu.js";
import { SnowFlake } from "./snowflake.js";
import { memberjson, userjson } from "./jsontypes.js";
class Member{
static already={};
@ -10,6 +11,7 @@ class Member{
user:User;
roles:Role[];
error:boolean;
id:string;
static contextmenu:Contextmenu=new Contextmenu("User Menu");
static setUpContextMenu(){
this.contextmenu.addbutton("Copy user id",function(){
@ -23,17 +25,18 @@ class Member{
});
});
}
constructor(memberjson,owner:Guild,error=false){
constructor(memberjson:memberjson|User|{guild_member:memberjson,user:userjson},owner:Guild,error=false){
this.error=error;
this.owner=owner;
let membery=memberjson;
this.roles=[];
if(!error){
if(memberjson.guild_member){
if(memberjson["guild_member"]){
memberjson=memberjson as {guild_member:memberjson,user:userjson};
membery=memberjson.guild_member;
this.user=memberjson.user;
}
}
membery=membery as User|memberjson;
for(const thing of Object.keys(membery)){
if(thing==="guild"){continue}
if(thing==="owner"){continue}
@ -49,7 +52,11 @@ class Member{
if(error){
this.user=memberjson as User;
}else{
this.user=new User(this.user,owner.localuser);
if(SnowFlake.getSnowFlakeFromID(this?.id,User)){
this.user=SnowFlake.getSnowFlakeFromID(this.id,User).getObject();
return;
}
this.user=new User((membery as memberjson).user,owner.localuser);
}
}
get guild(){
@ -61,7 +68,7 @@ class Member{
get info(){
return this.owner.info;
}
static async resolve(unkown:User|object|string,guild:Guild):Promise<Member>{
static async resolve(unkown:User|memberjson|string,guild:Guild):Promise<Member>{
if(!(guild instanceof Guild)){
console.error(guild)
}
@ -73,7 +80,7 @@ class Member{
}else if(typeof unkown===typeof ""){
id=new SnowFlake(unkown as string,undefined);
}else{
return new Member(unkown,guild);
return new Member(unkown as User|memberjson,guild);
}
if(guild.id==="@me"){return null}
if(!Member.already[guild.id]){

View file

@ -8,6 +8,7 @@ import {Localuser} from "./localuser.js";
import { Role } from "./role.js";
import {File} from "./file.js";
import { SnowFlake } from "./snowflake.js";
import { messagejson } from "./jsontypes.js";
class Message{
static contextmenu=new Contextmenu("message menu");
@ -26,6 +27,7 @@ class Message{
static del:Promise<void>;
static resolve:Function;
div:HTMLDivElement;
member:Member;
get id(){
return this.snowflake.id;
}
@ -47,7 +49,7 @@ class Message{
this.channel.setReplying(this);
});
Message.contextmenu.addbutton("Copy message id",function(){
navigator.clipboard.writeText(this.id.id);
navigator.clipboard.writeText(this.id);
});
Message.contextmenu.addbutton("Edit",function(){
this.channel.editing=this;
@ -59,13 +61,13 @@ class Message{
this.delete();
},null,_=>{return _.canDelete()})
}
constructor(messagejson,owner:Channel){
constructor(messagejson:messagejson,owner:Channel){
this.owner=owner;
this.headers=this.owner.headers;
this.giveData(messagejson);
}
giveData(messagejson){
giveData(messagejson:messagejson){
for(const thing of Object.keys(messagejson)){
if(thing==="attachments"){
this.attachments=[];
@ -79,16 +81,29 @@ class Message{
}else if(thing ==="id"){
this.snowflake=new SnowFlake(messagejson.id,this);
continue;
}else if(thing==="member"){
this.member=new Member(messagejson.member,this.guild);
continue;
}else if(thing ==="embeds"){
this.embeds=[];
for(const thing in messagejson.embeds){
console.log(thing,messagejson.embeds)
this.embeds[thing]=new Embed(messagejson.embeds[thing],this);
}
continue;
}
this[thing]=messagejson[thing];
}
for(const thing in this.embeds){
console.log(thing,this.embeds)
this.embeds[thing]=new Embed(this.embeds[thing],this);
this.author=new User(messagejson.author,this.localuser);
for(const thing in messagejson.mentions){
this.mentions[thing]=new User(messagejson.mentions[thing],this.localuser);
}
this.author=new User(this.author,this.localuser);
for(const thing in this.mentions){
this.mentions[thing]=new User(this.mentions[thing],this.localuser);
if(!this.member&&this.guild.id!=="@me"){
this.author.resolvemember(this.guild).then(_=>{
this.member=_;
})
}
if(this.mentions.length||this.mention_roles.length){//currently mention_roles isn't implemented on the spacebar servers
console.log(this.mentions,this.mention_roles)
@ -231,6 +246,10 @@ class Message{
username.textContent=author.username;
author.bind(username);
});
reply.onclick=_=>{
console.log("this got clicked :3")
this.channel.infinite.focus(this.message_reference.message_id);
}
div.appendChild(replyline);
}
build.classList.add("message");
@ -304,6 +323,7 @@ class Message{
messagedwrap.appendChild(attach)
}
if(this.embeds.length){
console.log(this.embeds);
const embeds = document.createElement("div")
embeds.classList.add("flexltr");
for(const thing of this.embeds){

View file

@ -8,30 +8,45 @@
<div id="logindiv">
<h1>Create an account</h1><br>
<form id="register" submit="registertry(e)">
<div>
<label for="instance"><b>Instance:</b></label><br>
<p id="verify"></p>
<input type="text" placeholder="Instance URL" name="instance" value="https://spacebar.chat/" id="instancein" required><br><br>
<input type="text" placeholder="Instance URL" name="instance" value="https://spacebar.chat/" id="instancein" required>
</div>
<div>
<label for="uname"><b>Email:</b></label><br>
<input type="text" placeholder="Enter Email" name="email" required><br><br>
<input type="text" placeholder="Enter Email" name="email" required>
</div>
<div>
<label for="uname"><b>Username:</b></label><br>
<input type="text" placeholder="Enter Username" name="uname" required><br><br>
<input type="text" placeholder="Enter Username" name="uname" required>
</div>
<div>
<label for="psw"><b>Password:</b></label><br>
<input type="password" placeholder="Enter Password" name="psw" required><br><br>
<input type="password" placeholder="Enter Password" name="psw" required>
</div>
<div>
<label for="psw2"><b>Enter password again:</b></label><br>
<input type="password" placeholder="Enter Password Again" name="psw2" required><br><br>
<input type="password" placeholder="Enter Password Again" name="psw2" required>
</div>
<div>
<label for="date"><b>Date of birth:</b></label><br>
<input type="date" id="start" name="date"/><br><br>
<input type="date" id="start" name="date"/>
</div>
<div>
<b id="TOSbox">I agree to the <a href="" id="TOSa">Terms of Service</a>:</b>
<input type="checkbox" id="TOS" name="TOS"/><br>
<p class="wrongred" id="wrong"></p><br>
<button type="submit">Create account</button>
<input type="checkbox" id="TOS" name="TOS"/>
</div>
<p class="wrongred" id="wrong"></p>
<div id="h-captcha">
</div>
<button type="submit" class="dontgrow">Create account</button>
</form>
<a href="/login.html">Already have an account?</a>
</div>

View file

@ -16,13 +16,14 @@ async function registertry(e){
const dateofbirth=elements[5].value;
const apiurl=new URL(JSON.parse(localStorage.getItem("instanceinfo")).api)
fetch(apiurl+"/auth/register",{
await fetch(apiurl+"/auth/register",{
body:JSON.stringify({
date_of_birth:dateofbirth,
email:email,
username:username,
password:password,
consent:elements[6].checked,
captcha_key:elements[7]?.value
}),
headers:{
"content-type": "application/json"
@ -30,9 +31,37 @@ async function registertry(e){
method:"POST"
}).then(e=>{
e.json().then(e=>{
if(e.captcha_sitekey){
const capt=document.getElementById("h-captcha");
if(!capt.children.length){
const capty=document.createElement("div");
capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", e.captcha_sitekey);
const script=document.createElement("script");
script.src="https://js.hcaptcha.com/1/api.js";
capt.append(script);
capt.append(capty);
}else{
eval("hcaptcha.reset()");
}
return;
}
if(!e.token){
console.log(e);
if(e.errors.consent){
error(elements[6],e.errors.consent._errors[0].message);
}else if(e.errors.password){
error(elements[3],"Password: "+e.errors.password._errors[0].message);
}else if(e.errors.username){
error(elements[2],"Username: "+e.errors.username._errors[0].message);
}else if(e.errors.email){
error(elements[1],"Email: "+e.errors.email._errors[0].message);
}else if(e.errors.date_of_birth){
error(elements[5],"Date of Birth: "+e.errors.date_of_birth._errors[0].message);
}else{
document.getElementById("wrong").textContent=e.errors[Object.keys(e.errors)[0]]._errors[0].message;
}
}else{
localStorage.setItem("token",e.token);
window.location.href = '/channels/@me';
@ -42,6 +71,20 @@ async function registertry(e){
//document.getElementById("wrong").textContent=h;
// console.log(h);
}
function error(e:HTMLFormElement,message:string){
const p=e.parentElement;
let element=p.getElementsByClassName("suberror")[0] as HTMLElement;
if(!element){
const div=document.createElement("div");
div.classList.add("suberror","suberrora");
p.append(div);
element=div;
}else{
element.classList.remove("suberror");
setTimeout(_=>{element.classList.add("suberror")},100);
}
element.textContent=message;
}
let TOSa=document.getElementById("TOSa");
async function tosLogic(){
const apiurl=new URL(JSON.parse(localStorage.getItem("instanceinfo")).api)

View file

@ -3,6 +3,7 @@ import {Permissions} from "./permissions.js";
import {Localuser} from "./localuser.js";
import {Guild} from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { rolesjson } from "./jsontypes.js";
class Role{
permissions:Permissions;
owner:Guild;
@ -18,7 +19,7 @@ class Role{
get id(){
return this.snowflake.id;
}
constructor(json, owner:Guild){
constructor(json:rolesjson, owner:Guild){
this.headers=owner.headers;
this.info=owner.info;
for(const thing of Object.keys(json)){

View file

@ -1,7 +1,7 @@
class SnowFlake<x>{
class SnowFlake<x extends WeakKey>{
public readonly id:string;
private static readonly SnowFlakes:Map<any,Map<string,WeakRef<SnowFlake<any>>>>=new Map();
private static readonly FinalizationRegistry=new FinalizationRegistry((a:[string,any])=>{
private static readonly FinalizationRegistry=new FinalizationRegistry((a:[string,WeakKey])=>{
SnowFlake.SnowFlakes.get(a[1]).delete(a[0]);
});
private obj:x;
@ -15,8 +15,12 @@ class SnowFlake<x>{
}
if(SnowFlake.SnowFlakes.get(obj.constructor).get(id)){
const snowflake=SnowFlake.SnowFlakes.get(obj.constructor).get(id).deref();
if(snowflake){
snowflake.obj=obj;
return snowflake;
}else{
SnowFlake.SnowFlakes.get(obj.constructor).delete(id);
}
}
this.id=id;
SnowFlake.SnowFlakes.get(obj.constructor).set(id,new WeakRef(this));
@ -33,7 +37,12 @@ class SnowFlake<x>{
}
const snowflake=SnowFlake.SnowFlakes.get(type).get(id);
if(snowflake){
return snowflake.deref();
const obj=snowflake.deref();
if(obj){
return obj;
}else{
SnowFlake.SnowFlakes.get(type).delete(id);
}
}
{
const snowflake=new SnowFlake(id,undefined);
@ -44,6 +53,22 @@ class SnowFlake<x>{
return snowflake;
}
}
static hasSnowFlakeFromID(id:string,type:any){
if(!SnowFlake.SnowFlakes.get(type)){
return false;
}
const flake=SnowFlake.SnowFlakes.get(type).get(id);
if(flake){
const flake2=flake.deref()?.getObject();
if(flake2){
return true;
}else{
return false;
}
}else{
return false;
}
}
getUnixTime():number{
return Number((BigInt(this.id)>>22n)+1420070400000n);
}

View file

@ -74,6 +74,22 @@ th {
.messagediv:hover {
background-color: var(--message-bg-hover);
}
.jumped{
animation-duration: .5s;
animation-name: jumped;
}
@keyframes jumped{
0% {
background-color: transparent;
}
50% {
background-color: var(--message-jump);
}
100% {
background-color: transparent;
}
}
.messagediv{
overflow: hidden;
max-width:100%;
@ -83,7 +99,7 @@ th {
flex-wrap: nowrap;
flex-direction: column;
max-height: 20in;
padding: .02in .2in 0 .1in;
padding: .02in .1in 0 .1in;
flex-shrink: 0;
width: 100%;
box-sizing: border-box;
@ -546,7 +562,7 @@ hr {
.replypfp {
border-radius: 50%;
width: .2in;
height: 2.in;
height: .2in;
padding: .05in;
user-select: none;
cursor: pointer;
@ -567,6 +583,7 @@ hr {
/* display: inline-block !important; */
width: 25vw;
grid-column: 2;
cursor: pointer;
}
.replytext pre {
/* padding: 0 .05in; */
@ -837,6 +854,10 @@ select{
border-style: solid;
border-color: var(--login-border);
text-align: center;
height: 90%;
display: flex;
flex-direction: column;
justify-content: center;
}
#logindiv input {
@ -1398,7 +1419,7 @@ span {
.messagediv .flexttb{
display:flex;
overflow: hidden;
flex-wrap: wrap;
flex-wrap: nowrap;
width: 100%;
flex-direction: column;
max-height:100in;
@ -1441,3 +1462,56 @@ span {
content:'You can\'t chat here';
color:color-mix(in hsl, var(--primary-bg),var(--primary-text));
}
.scroller{
padding-bottom: .2in;
}
.suberror{
animation: goout 6s forwards;
}
.suberrora{
background:var(--channel-hover);
border-radius:.1in;
position:absolute;
border:solid var(--primary-text) .02in;
color:color-mix(in hsl,var(--yellow),var(--red));
font-weight:bold;
opacity:0;
cursor:default;
/* height: .4in; */
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-evenly;
padding: .075in;
box-sizing: border-box;
}
@keyframes goout {
0%{
opacity:0;
}
5%{
opacity:1;
}
90%{
opacity:1;
}
100%{
opacity:0;
}
}
#register{
display:flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
justify-content: space-between;
}
.dontgrow{
flex-grow:0;
width: 1.6in;
text-align: center;
}
form div{
width:100%;
}

View file

@ -3,6 +3,7 @@
/* Default value */
--red: red;
--green: green;
--yellow:yellow;
}
.Dark-theme { /* thanks to TomatoCake for the updated CSS vars and such*/
color-scheme: dark;
@ -51,6 +52,7 @@
--embed: #1a1823;
--link: #8888ff;
--discovery-bg: #37373b;
--message-jump:#7678b0;
}
.WHITE-theme {
color-scheme: light;
@ -101,6 +103,7 @@
--embed: #f2f3f5;
--link: #3333ee;
--discovery-bg: #c6c6d8;
--message-jump:#52558a;
}
.Light-theme {
color-scheme: light;
@ -159,4 +162,5 @@
--embed: #cdccd1;
--link: #5566cc;
--discovery-bg: #c6c6d8;
--message-jump:#52558a;
}

View file

@ -5,6 +5,7 @@ import {Contextmenu} from "./contextmenu.js";
import {Localuser} from "./localuser.js";
import {Guild} from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { userjson } from "./jsontypes.js";
class User{
static userids={};
@ -17,6 +18,31 @@ class User{
discriminator:string;
pronouns:string;
bot:boolean;
public_flags: number;
accent_color: string;
banner: string;
premium_since: string;
premium_type: number;
theme_colors: string;
badge_ids: string;
clone(){
return new User({
username:this.username,
id:this.id+"#clone",
public_flags:this.public_flags,
discriminator:this.discriminator,
avatar:this.avatar,
accent_color:this.accent_color,
banner:this.banner,
bio:this.bio.rawString,
premium_since:this.premium_since,
premium_type:this.premium_type,
bot:this.bot,
theme_colors:this.theme_colors,
pronouns:this.pronouns,
badge_ids:this.badge_ids
},this.owner)
}
get id(){
return this.snowflake.id;
}
@ -33,12 +59,12 @@ class User{
});
})
}
static checkuser(userjson,owner:Localuser){
if(User.userids[userjson.id]){
return User.userids[userjson.id];
static checkuser(user:User|userjson,owner:Localuser):User{
if(User.userids[user.id]){
return User.userids[user.id];
}else{
const tempuser=new User(userjson,owner,true)
User.userids[userjson.id]=tempuser;
const tempuser=new User(user as userjson,owner,true)
User.userids[user.id]=tempuser;
return tempuser;
}
}
@ -48,7 +74,7 @@ class User{
get localuser(){
return this.owner;
}
constructor(userjson,owner:Localuser,dontclone=false){
constructor(userjson:userjson,owner:Localuser,dontclone=false){
this.owner=owner;
if(!owner){console.error("missing localuser")}
if(dontclone){
@ -69,7 +95,7 @@ class User{
}
}
async resolvemember(guild:Guild){
await Member.resolve(this,guild);
return await Member.resolve(this,guild);
}
buildpfp(){
const pfp=document.createElement('img');
@ -78,7 +104,7 @@ class User{
pfp.classList.add("userid:"+this.id);
return pfp;
}
userupdate(json){
userupdate(json:userjson){
if(json.avatar!==this.avatar){
console.log
this.changepfp(json.avatar);