jank-client-fork/src/webpage/localuser.ts

1867 lines
52 KiB
TypeScript

import{ Guild }from"./guild.js";
import{ Channel }from"./channel.js";
import{ Direct }from"./direct.js";
import{ AVoice }from"./audio.js";
import{ User }from"./user.js";
import{ Dialog }from"./dialog.js";
import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js";
import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js";
import{ Member }from"./member.js";
import{ Form, FormError, Options, Settings }from"./settings.js";
import{ MarkDown }from"./markdown.js";
import { Bot } from "./bot.js";
import { Role } from "./role.js";
import { VoiceFactory } from "./voice.js";
import { I18n } from "./i18n.js";
const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]);
class Localuser{
badges: Map<string,{ id: string; description: string; icon: string; link: string }> = new Map();
lastSequence: number | null = null;
token!: string;
userinfo!: Specialuser;
serverurls!: Specialuser["serverurls"];
initialized!: boolean;
info!: Specialuser["serverurls"];
headers!: { "Content-type": string; Authorization: string };
userConnections!: Dialog;
devPortal!: Dialog;
ready!: readyjson;
guilds!: Guild[];
guildids: Map<string, Guild> = new Map();
user!: User;
status!: string;
channelfocus: Channel | undefined;
lookingguild: Guild | undefined;
guildhtml: Map<string, HTMLDivElement> = new Map();
ws: WebSocket | undefined;
connectionSucceed = 0;
errorBackoff = 0;
channelids: Map<string, Channel> = new Map();
readonly userMap: Map<string, User> = new Map();
voiceFactory?:VoiceFactory;
instancePing = {
name: "Unknown",
};
mfa_enabled!: boolean;
get perminfo(){
return this.userinfo.localuserStore;
}
set perminfo(e){
this.userinfo.localuserStore = e;
}
constructor(userinfo: Specialuser | -1){
if(userinfo === -1){
return;
}
this.token = userinfo.token;
this.userinfo = userinfo;
this.perminfo.guilds ??= {};
this.serverurls = this.userinfo.serverurls;
this.initialized = false;
this.info = this.serverurls;
this.headers = {
"Content-type": "application/json; charset=UTF-8",
Authorization: this.userinfo.token,
};
}
async gottenReady(ready: readyjson): Promise<void>{
await I18n.done;
this.initialized = true;
this.ready = ready;
this.guilds = [];
this.guildids = new Map();
this.user = new User(ready.d.user, this);
this.user.setstatus("online");
this.voiceFactory=new VoiceFactory({id:this.user.id});
this.handleVoice();
this.mfa_enabled = ready.d.user.mfa_enabled as boolean;
this.userinfo.username = this.user.username;
this.userinfo.id = this.user.id;
this.userinfo.pfpsrc = this.user.getpfpsrc();
this.status = this.ready.d.user_settings.status;
this.channelfocus = undefined;
this.lookingguild = undefined;
this.guildhtml = new Map();
const members: { [key: string]: memberjson } = {};
if(ready.d.merged_members){
for(const thing of ready.d.merged_members){
members[thing[0].guild_id] = thing[0];
}
}
for(const thing of ready.d.guilds){
const temp = new Guild(thing, this, members[thing.id]);
this.guilds.push(temp);
this.guildids.set(temp.id, temp);
}
{
const temp = new Direct(ready.d.private_channels, this);
this.guilds.push(temp);
this.guildids.set(temp.id, temp);
}
console.log(ready.d.user_guild_settings.entries);
for(const thing of ready.d.user_guild_settings.entries){
(this.guildids.get(thing.guild_id) as Guild).notisetting(thing);
}
for(const thing of ready.d.read_state.entries){
const channel = this.channelids.get(thing.channel_id);
if(!channel){
continue;
}
channel.readStateInfo(thing);
}
for(const thing of ready.d.relationships){
const user = new User(thing.user, this);
user.nickname = thing.nickname;
user.relationshipType = thing.type;
}
this.pingEndpoint();
this.userinfo.updateLocal();
}
outoffocus(): void{
const servers = document.getElementById("servers") as HTMLDivElement;
servers.innerHTML = "";
const channels = document.getElementById("channels") as HTMLDivElement;
channels.innerHTML = "";
if(this.channelfocus){
this.channelfocus.infinite.delete();
}
this.lookingguild = undefined;
this.channelfocus = undefined;
}
unload(): void{
this.initialized = false;
this.outoffocus();
this.guilds = [];
this.guildids = new Map();
if(this.ws){
this.ws.close(4001);
}
}
swapped = false;
async initwebsocket(): Promise<void>{
let returny: () => void;
const ws = new WebSocket(
this.serverurls.gateway.toString() +
"?encoding=json&v=9" +
(DecompressionStream ? "&compress=zlib-stream" : "")
);
this.ws = ws;
let ds: DecompressionStream;
let w: WritableStreamDefaultWriter;
let r: ReadableStreamDefaultReader;
let arr: Uint8Array;
let build = "";
if(DecompressionStream){
ds = new DecompressionStream("deflate");
w = ds.writable.getWriter();
r = ds.readable.getReader();
arr = new Uint8Array();
}
const promise = new Promise<void>(res=>{
returny = res;
ws.addEventListener("open", _event=>{
console.log("WebSocket connected");
ws.send(
JSON.stringify({
op: 2,
d: {
token: this.token,
capabilities: 16381,
properties: {
browser: "Jank Client",
client_build_number: 0, //might update this eventually lol
release_channel: "Custom",
browser_user_agent: navigator.userAgent,
},
compress: Boolean(DecompressionStream),
presence: {
status: "online",
since: null, //new Date().getTime()
activities: [],
afk: false,
},
},
})
);
});
const textdecode = new TextDecoder();
if(DecompressionStream){
(async ()=>{
while(true){
const read = await r.read();
const data = textdecode.decode(read.value);
build += data;
try{
const temp = JSON.parse(build);
build = "";
await this.handleEvent(temp);
if(temp.op === 0 && temp.t === "READY"){
returny();
}
}catch{}
}
})();
}
});
let order = new Promise<void>(res=>res());
ws.addEventListener("message", async event=>{
const temp2 = order;
order = new Promise<void>(async res=>{
await temp2;
let temp: { op: number; t: string };
try{
if(event.data instanceof Blob){
const buff = await event.data.arrayBuffer();
const array = new Uint8Array(buff);
const temparr = new Uint8Array(array.length + arr.length);
temparr.set(arr, 0);
temparr.set(array, arr.length);
arr = temparr;
const len = array.length;
if(
!(
array[len - 1] === 255 &&
array[len - 2] === 255 &&
array[len - 3] === 0 &&
array[len - 4] === 0
)
){
return;
}
w.write(arr.buffer);
arr = new Uint8Array();
return; //had to move the while loop due to me being dumb
}else{
temp = JSON.parse(event.data);
}
await this.handleEvent(temp as readyjson);
if(temp.op === 0 && temp.t === "READY"){
returny();
}
}catch(e){
console.error(e);
}finally{
res();
}
});
});
ws.addEventListener("close", async event=>{
this.ws = undefined;
console.log("WebSocket closed with code " + event.code);
this.unload();
(document.getElementById("loading") as HTMLElement).classList.remove(
"doneloading"
);
(document.getElementById("loading") as HTMLElement).classList.add(
"loading"
);
this.fetchingmembers = new Map();
this.noncemap = new Map();
this.noncebuild = new Map();
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++;
this.connectionSucceed = 0;
(document.getElementById("load-desc") as HTMLElement).innerHTML = I18n.getTranslation("errorReconnect",Math.round(0.2 + this.errorBackoff * 2.8)+"")
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;
}
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;
}
break;
}
case 5: {
const breakappart = new URL(this.info.wellknown).origin.split(".");
const url =
"https://" + breakappart.at(-2) + "." + breakappart.at(-1);
const newurls = await getapiurls(url);
if(newurls){
this.info = newurls;
this.serverurls = newurls;
this.userinfo.json.serverurls = this.info;
this.userinfo.updateLocal();
}
break;
}
}
setTimeout(()=>{
if(this.swapped)return;
(document.getElementById("load-desc") as HTMLElement).textContent =I18n.getTranslation("retrying");
this.initwebsocket().then(()=>{
this.loaduser();
this.init();
const loading = document.getElementById("loading") as HTMLElement;
loading.classList.add("doneloading");
loading.classList.remove("loading");
console.log("done loading");
});
}, 200 + this.errorBackoff * 2800);
}else
(document.getElementById("load-desc") as HTMLElement).textContent = I18n.getTranslation("unableToConnect")
});
await promise;
}
async handleEvent(temp: wsjson){
console.debug(temp);
if(temp.s)this.lastSequence = temp.s;
if(temp.op == 0){
switch(temp.t){
case"MESSAGE_CREATE":
if(this.initialized){
this.messageCreate(temp);
}
break;
case"MESSAGE_DELETE": {
temp.d.guild_id ??= "@me";
const channel = this.channelids.get(temp.d.channel_id);
if(!channel)break;
const message = channel.messages.get(temp.d.id);
if(!message)break;
message.deleteEvent();
break;
}
case"READY":
await this.gottenReady(temp as readyjson);
break;
case"MESSAGE_UPDATE": {
temp.d.guild_id ??= "@me";
const channel = this.channelids.get(temp.d.channel_id);
if(!channel)break;
const message = channel.messages.get(temp.d.id);
if(!message)break;
message.giveData(temp.d);
break;
}
case"TYPING_START":
if(this.initialized){
this.typingStart(temp);
}
break;
case"USER_UPDATE":
if(this.initialized){
const users = this.userMap.get(temp.d.id);
if(users){
users.userupdate(temp.d);
}
}
break;
case"CHANNEL_UPDATE":
if(this.initialized){
this.updateChannel(temp.d);
}
break;
case"CHANNEL_CREATE":
if(this.initialized){
this.createChannel(temp.d);
}
break;
case"CHANNEL_DELETE":
if(this.initialized){
this.delChannel(temp.d);
}
break;
case"GUILD_DELETE": {
const guildy = this.guildids.get(temp.d.id);
if(guildy){
this.guildids.delete(temp.d.id);
this.guilds.splice(this.guilds.indexOf(guildy), 1);
guildy.html.remove();
}
break;
}
case"GUILD_CREATE": {
const guildy = new Guild(temp.d, this, this.user);
this.guilds.push(guildy);
this.guildids.set(guildy.id, guildy);
(document.getElementById("servers") as HTMLDivElement).insertBefore(
guildy.generateGuildIcon(),
document.getElementById("bottomseparator")
);
break;
}
case"MESSAGE_REACTION_ADD":
{
temp.d.guild_id ??= "@me";
const guild = this.guildids.get(temp.d.guild_id);
if(!guild)break;
const channel = this.channelids.get(temp.d.channel_id);
if(!channel)break;
const message = channel.messages.get(temp.d.message_id);
if(!message)break;
let thing: Member | { id: string };
if(temp.d.member){
thing = (await Member.new(temp.d.member, guild)) as Member;
}else{
thing = { id: temp.d.user_id };
}
message.reactionAdd(temp.d.emoji, thing);
}
break;
case"MESSAGE_REACTION_REMOVE":
{
temp.d.guild_id ??= "@me";
const channel = this.channelids.get(temp.d.channel_id);
if(!channel)break;
const message = channel.messages.get(temp.d.message_id);
if(!message)break;
message.reactionRemove(temp.d.emoji, temp.d.user_id);
}
break;
case"MESSAGE_REACTION_REMOVE_ALL":
{
temp.d.guild_id ??= "@me";
const channel = this.channelids.get(temp.d.channel_id);
if(!channel)break;
const message = channel.messages.get(temp.d.message_id);
if(!message)break;
message.reactionRemoveAll();
}
break;
case"MESSAGE_REACTION_REMOVE_EMOJI":
{
temp.d.guild_id ??= "@me";
const channel = this.channelids.get(temp.d.channel_id);
if(!channel)break;
const message = channel.messages.get(temp.d.message_id);
if(!message)break;
message.reactionRemoveEmoji(temp.d.emoji);
}
break;
case"GUILD_MEMBERS_CHUNK":
this.gotChunk(temp.d);
break;
case"GUILD_MEMBER_LIST_UPDATE":
{
this.memberListUpdate(temp)
break;
}
case "VOICE_STATE_UPDATE":
if(this.voiceFactory){
this.voiceFactory.voiceStateUpdate(temp)
}
break;
case "VOICE_SERVER_UPDATE":
if(this.voiceFactory){
this.voiceFactory.voiceServerUpdate(temp)
}
break;
case "GUILD_ROLE_CREATE":{
const guild=this.guildids.get(temp.d.guild_id);
if(!guild) break;
guild.newRole(temp.d.role);
break;
}
case "GUILD_ROLE_UPDATE":{
const guild=this.guildids.get(temp.d.guild_id);
if(!guild) break;
guild.updateRole(temp.d.role);
break;
}
case "GUILD_ROLE_DELETE":{
const guild=this.guildids.get(temp.d.guild_id);
if(!guild) break;
guild.deleteRole(temp.d.role_id);
break;
}
case "GUILD_MEMBER_UPDATE":{
const guild=this.guildids.get(temp.d.guild_id);
if(!guild) break;
guild.memberupdate(temp.d)
break
}
}
}else if(temp.op === 10){
if(!this.ws)return;
console.log("heartbeat down");
this.heartbeat_interval = temp.d.heartbeat_interval;
this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence }));
}else if(temp.op === 11){
setTimeout((_: any)=>{
if(!this.ws)return;
if(this.connectionSucceed === 0)this.connectionSucceed = Date.now();
this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence }));
}, this.heartbeat_interval);
}
}
get currentVoice(){
return this.voiceFactory?.currentVoice;
}
async joinVoice(channel:Channel){
if(!this.voiceFactory) return;
if(!this.ws) return;
this.ws.send(JSON.stringify(this.voiceFactory.joinVoice(channel.id,channel.guild.id)));
return undefined;
}
changeVCStatus(status:string){
const statuselm=document.getElementById("VoiceStatus");
if(!statuselm) throw new Error("Missing status element");
statuselm.textContent=status;
}
handleVoice(){
if(this.voiceFactory){
this.voiceFactory.onJoin=voice=>{
voice.onSatusChange=status=>{
this.changeVCStatus(status);
}
}
}
}
heartbeat_interval: number = 0;
updateChannel(json: channeljson): void{
const guild = this.guildids.get(json.guild_id);
if(guild){
guild.updateChannel(json);
if(json.guild_id === this.lookingguild?.id){
this.loadGuild(json.guild_id);
}
}
}
createChannel(json: channeljson): undefined | Channel{
json.guild_id ??= "@me";
const guild = this.guildids.get(json.guild_id);
if(!guild)return;
const channel = guild.createChannelpac(json);
if(json.guild_id === this.lookingguild?.id){
this.loadGuild(json.guild_id);
}
if(channel.id === this.gotoid){
guild.loadGuild();
guild.loadChannel(channel.id);
this.gotoid = undefined;
}
return channel; // Add this line to return the 'channel' variable
}
async memberListUpdate(list:memberlistupdatejson|void){
const div=document.getElementById("sideDiv") as HTMLDivElement;
div.innerHTML="";
if(!list) return;
const counts=new Map<string,number>();
const guild=this.lookingguild;
if(!guild) return;
const channel=this.channelfocus;
if(!channel) return;
for(const thing of list.d.ops[0].items){
if("member" in thing){
await Member.new(thing.member,guild);
}else{
counts.set(thing.group.id,thing.group.count);
}
}
const elms:Map<Role|"offline"|"online",Member[]>=new Map([]);
for(const role of guild.roles){
if(role.hoist){
elms.set(role,[]);
}
}
elms.set("online",[]);
elms.set("offline",[])
const members=new Set(guild.members);
members.forEach((member)=>{
if(!channel.hasPermission("VIEW_CHANNEL",member)){
members.delete(member);
console.log(member)
return;
}
})
for(const [role, list] of elms){
members.forEach((member)=>{
if(role === "offline"){
if(member.user.status === "offline"){
list.push(member);
members.delete(member);
}
return;
}
if(member.user.status === "offline"){
return;
}
if(role !== "online"&&member.hasRole(role.id)){
list.push(member);
members.delete(member);
}
});
if(!list.length) continue;
list.sort((a,b)=>{
return (a.name.toLowerCase()>b.name.toLowerCase())?1:-1;
});
}
const online=[...members];
online.sort((a,b)=>{
return (a.name.toLowerCase()>b.name.toLowerCase())?1:-1;
});
elms.set("online",online);
for(const [role, list] of elms){
if(!list.length) continue;
const category=document.createElement("div");
category.classList.add("memberList");
let title=document.createElement("h3");
if(role==="offline"){
title.textContent=I18n.getTranslation("user.offline");
category.classList.add("offline");
}else if(role==="online"){
title.textContent=I18n.getTranslation("user.online");
}else{
title.textContent=role.name;
}
category.append(title);
const membershtml=document.createElement("div");
membershtml.classList.add("flexttb");
for(const member of list){
const memberdiv=document.createElement("div");
const pfp=await member.user.buildstatuspfp();
const username=document.createElement("span");
username.classList.add("ellipsis");
username.textContent=member.name;
member.bind(username)
member.user.bind(memberdiv,member.guild,false);
memberdiv.append(pfp,username);
memberdiv.classList.add("flexltr","liststyle");
membershtml.append(memberdiv);
}
category.append(membershtml);
div.append(category);
}
console.log(elms);
}
async getSidePannel(){
if(this.ws&&this.channelfocus){
console.log(this.channelfocus.guild.id);
if(this.channelfocus.guild.id==="@me"){
this.memberListUpdate();
return;
}
this.ws.send(JSON.stringify({
d:{
channels:{[this.channelfocus.id]:[[0,99]]},
guild_id:this.channelfocus.guild.id
},
op:14
}))
}else{
console.log("false? :3")
}
}
gotoid: string | undefined;
async goToChannel(id: string){
const channel = this.channelids.get(id);
if(channel){
const guild = channel.guild;
guild.loadGuild();
guild.loadChannel(id);
}else{
this.gotoid = id;
}
}
delChannel(json: channeljson): void{
let guild_id = json.guild_id;
guild_id ??= "@me";
const guild = this.guildids.get(guild_id);
if(guild){
guild.delChannel(json);
}
if(json.guild_id === this.lookingguild?.id){
this.loadGuild(json.guild_id);
}
}
init(): void{
const location = window.location.href.split("/");
this.buildservers();
if(location[3] === "channels"){
const guild = this.loadGuild(location[4]);
if(!guild){
return;
}
guild.loadChannel(location[5]);
this.channelfocus = this.channelids.get(location[5]);
}
}
loaduser(): void{
(document.getElementById("username") as HTMLSpanElement).textContent = this.user.username;
(document.getElementById("userpfp") as HTMLImageElement).src = this.user.getpfpsrc();
(document.getElementById("status") as HTMLSpanElement).textContent = this.status;
}
isAdmin(): boolean{
if(this.lookingguild){
return this.lookingguild.isAdmin();
}else{
return false;
}
}
loadGuild(id: string): Guild | undefined{
let guild = this.guildids.get(id);
if(!guild){
guild = this.guildids.get("@me");
}
if(this.lookingguild === guild){
return guild;
}
if(this.channelfocus){
this.channelfocus.infinite.delete();
this.channelfocus = undefined;
}
if(this.lookingguild){
this.lookingguild.html.classList.remove("serveropen");
}
if(!guild)return;
if(guild.html){
guild.html.classList.add("serveropen");
}
this.lookingguild = guild;
(document.getElementById("serverName") as HTMLElement).textContent =
guild.properties.name;
//console.log(this.guildids,id)
const channels = document.getElementById("channels") as HTMLDivElement;
channels.innerHTML = "";
const html = guild.getHTML();
channels.appendChild(html);
return guild;
}
buildservers(): void{
const serverlist = document.getElementById("servers") as HTMLDivElement; //
const outdiv = document.createElement("div");
const home: any = document.createElement("span");
const div = document.createElement("div");
div.classList.add("home", "servericon");
home.classList.add("svgicon", "svg-home");
home.all = this.guildids.get("@me");
(this.guildids.get("@me") as Guild).html = outdiv;
const unread = document.createElement("div");
unread.classList.add("unread");
outdiv.append(unread);
outdiv.append(div);
div.appendChild(home);
outdiv.classList.add("servernoti");
serverlist.append(outdiv);
home.onclick = function(){
this.all.loadGuild();
this.all.loadChannel();
};
const sentdms = document.createElement("div");
sentdms.classList.add("sentdms");
serverlist.append(sentdms);
sentdms.id = "sentdms";
const br = document.createElement("hr");
br.classList.add("lightbr");
serverlist.appendChild(br);
for(const thing of this.guilds){
if(thing instanceof Direct){
(thing as Direct).unreaddms();
continue;
}
const divy = thing.generateGuildIcon();
serverlist.append(divy);
}
{
const br = document.createElement("hr");
br.classList.add("lightbr");
serverlist.appendChild(br);
br.id = "bottomseparator";
const div = document.createElement("div");
const plus = document.createElement("span");
plus.classList.add("svgicon", "svg-plus");
div.classList.add("home", "servericon");
div.appendChild(plus);
serverlist.appendChild(div);
div.onclick = _=>{
this.createGuild();
};
const guilddsdiv = document.createElement("div");
const guildDiscoveryContainer = document.createElement("span");
guildDiscoveryContainer.classList.add("svgicon", "svg-explore");
guilddsdiv.classList.add("home", "servericon");
guilddsdiv.appendChild(guildDiscoveryContainer);
serverlist.appendChild(guilddsdiv);
guildDiscoveryContainer.addEventListener("click", ()=>{
this.guildDiscovery();
});
}
this.unreads();
}
createGuild(){
let inviteurl = "";
const error = document.createElement("span");
const fields: { name: string; icon: string | null } = {
name: "",
icon: null,
};
const full = new Dialog([
"tabs",
[
[
I18n.getTranslation("invite.joinUsing"),
[
"vdiv",
[
"textbox",
I18n.getTranslation("invite.inviteLinkCode"),
"",
function(this: HTMLInputElement){
inviteurl = this.value;
},
],
["html", error],
[
"button",
"",
I18n.getTranslation("submit"),
(_: any)=>{
let parsed = "";
if(inviteurl.includes("/")){
parsed =
inviteurl.split("/")[inviteurl.split("/").length - 1];
}else{
parsed = inviteurl;
}
fetch(this.info.api + "/invites/" + parsed, {
method: "POST",
headers: this.headers,
})
.then(r=>r.json())
.then(_=>{
if(_.message){
error.textContent = _.message;
}
});
},
],
],
],
[
I18n.getTranslation("guild.create"),
[
"vdiv",
["title", I18n.getTranslation("guild.create")],
[
"fileupload",
I18n.getTranslation("guild.icon:"),
function(event: Event){
const target = event.target as HTMLInputElement;
if(!target.files)return;
const reader = new FileReader();
reader.readAsDataURL(target.files[0]);
reader.onload = ()=>{
fields.icon = reader.result as string;
};
},
],
[
"textbox",
I18n.getTranslation("guild.name:"),
"",
function(this: HTMLInputElement, event: Event){
const target = event.target as HTMLInputElement;
fields.name = target.value;
},
],
[
"button",
"",
I18n.getTranslation("submit"),
()=>{
this.makeGuild(fields).then(_=>{
if(_.message){
alert(_.errors.name._errors[0].message);
}else{
full.hide();
}
});
},
],
],
],
],
]);
full.show();
}
async makeGuild(fields: { name: string; icon: string | null }){
return await (
await fetch(this.info.api + "/guilds", {
method: "POST",
headers: this.headers,
body: JSON.stringify(fields),
})
).json();
}
async guildDiscovery(){
const content = document.createElement("div");
content.classList.add("flexttb","guildy");
content.textContent = I18n.getTranslation("guild.loadingDiscovery");
const full = new Dialog(["html", content]);
full.show();
const res = await fetch(this.info.api + "/discoverable-guilds?limit=50", {
headers: this.headers,
});
const json = await res.json();
content.innerHTML = "";
const title = document.createElement("h2");
title.textContent = I18n.getTranslation("guild.disoveryTitle",json.guilds.length+"");
content.appendChild(title);
const guilds = document.createElement("div");
guilds.id = "discovery-guild-content";
json.guilds.forEach((guild: guildjson["properties"])=>{
const content = document.createElement("div");
content.classList.add("discovery-guild");
if(guild.banner){
const banner = document.createElement("img");
banner.classList.add("banner");
banner.crossOrigin = "anonymous";
banner.src =this.info.cdn +"/icons/" +guild.id +"/" +guild.banner +".png?size=256";
banner.alt = "";
content.appendChild(banner);
}
const nameContainer = document.createElement("div");
nameContainer.classList.add("flex");
const img = document.createElement("img");
img.classList.add("icon");
img.crossOrigin = "anonymous";
img.src =
this.info.cdn +
(guild.icon
? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48"
: "/embed/avatars/3.png");
img.alt = "";
nameContainer.appendChild(img);
const name = document.createElement("h3");
name.textContent = guild.name;
nameContainer.appendChild(name);
content.appendChild(nameContainer);
const desc = document.createElement("p");
desc.textContent = guild.description;
content.appendChild(desc);
content.addEventListener("click", async ()=>{
const joinRes = await fetch(
this.info.api + "/guilds/" + guild.id + "/members/@me",
{
method: "PUT",
headers: this.headers,
}
);
if(joinRes.ok) full.hide();
});
guilds.appendChild(content);
});
content.appendChild(guilds);
}
messageCreate(messagep: messageCreateJson): void{
messagep.d.guild_id ??= "@me";
const channel = this.channelids.get(messagep.d.channel_id);
if(channel){
channel.messageCreate(messagep);
this.unreads();
}
}
unreads(): void{
for(const thing of this.guilds){
if(thing.id === "@me"){
continue;
}
const html = this.guildhtml.get(thing.id);
thing.unreads(html);
}
}
async typingStart(typing: startTypingjson): Promise<void>{
const channel = this.channelids.get(typing.d.channel_id);
if(!channel)return;
channel.typingStart(typing);
}
updatepfp(file: Blob): void{
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ()=>{
fetch(this.info.api + "/users/@me", {
method: "PATCH",
headers: this.headers,
body: JSON.stringify({
avatar: reader.result,
}),
});
};
}
updatebanner(file: Blob | null): void{
if(file){
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ()=>{
fetch(this.info.api + "/users/@me", {
method: "PATCH",
headers: this.headers,
body: JSON.stringify({
banner: reader.result,
}),
});
};
}else{
fetch(this.info.api + "/users/@me", {
method: "PATCH",
headers: this.headers,
body: JSON.stringify({
banner: null,
}),
});
}
}
updateProfile(json: {
bio?: string;
pronouns?: string;
accent_color?: number;
}){
fetch(this.info.api + "/users/@me/profile", {
method: "PATCH",
headers: this.headers,
body: JSON.stringify(json),
});
}
async showusersettings(){
const settings = new Settings(I18n.getTranslation("localuser.settings"));
{
const userOptions = settings.addButton(I18n.getTranslation("localuser.userSettings"), { ltr: true });
const hypotheticalProfile = document.createElement("div");
let file: undefined | File | null;
let newpronouns: string | undefined;
let newbio: string | undefined;
const hypouser = this.user.clone();
let color: string;
async function regen(){
hypotheticalProfile.textContent = "";
const hypoprofile = await hypouser.buildprofile(-1, -1);
hypotheticalProfile.appendChild(hypoprofile);
}
regen();
const settingsLeft = userOptions.addOptions("");
const settingsRight = userOptions.addOptions("");
settingsRight.addHTMLArea(hypotheticalProfile);
const finput = settingsLeft.addFileInput(
I18n.getTranslation("uploadPfp"),
_=>{
if(file){
this.updatepfp(file);
}
},
{ clear: true }
);
finput.watchForChange(_=>{
if(!_){
file = null;
hypouser.avatar = null;
hypouser.hypotheticalpfp = true;
regen();
return;
}
if(_.length){
file = _[0];
const blob = URL.createObjectURL(file);
hypouser.avatar = blob;
hypouser.hypotheticalpfp = true;
regen();
}
});
let bfile: undefined | File | null;
const binput = settingsLeft.addFileInput(
I18n.getTranslation("uploadBanner"),
_=>{
if(bfile !== undefined){
this.updatebanner(bfile);
}
},
{ clear: true }
);
binput.watchForChange(_=>{
if(!_){
bfile = null;
hypouser.banner = undefined;
hypouser.hypotheticalbanner = true;
regen();
return;
}
if(_.length){
bfile = _[0];
const blob = URL.createObjectURL(bfile);
hypouser.banner = blob;
hypouser.hypotheticalbanner = true;
regen();
}
});
let changed = false;
const pronounbox = settingsLeft.addTextInput(
I18n.getTranslation("pronouns"),
_=>{
if(newpronouns || newbio || changed){
this.updateProfile({
pronouns: newpronouns,
bio: newbio,
accent_color: Number.parseInt("0x" + color.substr(1), 16),
});
}
},
{ initText: this.user.pronouns }
);
pronounbox.watchForChange(_=>{
hypouser.pronouns = _;
newpronouns = _;
regen();
});
const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, {
initText: this.user.bio.rawString,
});
bioBox.watchForChange(_=>{
newbio = _;
hypouser.bio = new MarkDown(_, this);
regen();
});
if(this.user.accent_color){
color = "#" + this.user.accent_color.toString(16);
}else{
color = "transparent";
}
const colorPicker = settingsLeft.addColorInput(
I18n.getTranslation("profileColor"),
_=>{},
{ initColor: color }
);
colorPicker.watchForChange(_=>{
console.log();
color = _;
hypouser.accent_color = Number.parseInt("0x" + _.substr(1), 16);
changed = true;
regen();
});
}
{
const tas = settings.addButton(I18n.getTranslation("localuser.themesAndSounds"));
{
const themes = ["Dark", "WHITE", "Light", "Dark-Accent"];
tas.addSelect(
I18n.getTranslation("localuser.theme:"),
_=>{
localStorage.setItem("theme", themes[_]);
setTheme();
},
themes,
{
defaultIndex: themes.indexOf(
localStorage.getItem("theme") as string
),
}
);
}
{
const sounds = AVoice.sounds;
tas
.addSelect(
I18n.getTranslation("localuser.notisound"),
_=>{
AVoice.setNotificationSound(sounds[_]);
},
sounds,
{ defaultIndex: sounds.indexOf(AVoice.getNotificationSound()) }
)
.watchForChange(_=>{
AVoice.noises(sounds[_]);
});
}
{
const userinfos = getBulkInfo();
tas.addColorInput(
I18n.getTranslation("localuser.accentColor"),
_=>{
userinfos.accent_color = _;
localStorage.setItem("userinfos", JSON.stringify(userinfos));
document.documentElement.style.setProperty(
"--accent-color",
userinfos.accent_color
);
},
{ initColor: userinfos.accent_color }
);
}
}
{
const update=settings.addButton(I18n.getTranslation("localuser.updateSettings"))
const sw=update.addSelect(I18n.getTranslation("localuser.swSettings"),()=>{},["SWOff","SWOffline","SWOn"].map(e=>I18n.getTranslation("localuser."+e)),{
defaultIndex:["false","offlineOnly","true"].indexOf(localStorage.getItem("SWMode") as string)
});
sw.onchange=(e)=>{
SW.setMode(["false","offlineOnly","true"][e] as "false"|"offlineOnly"|"true")
}
update.addButtonInput("",I18n.getTranslation("localuser.CheckUpdate"),()=>{
SW.checkUpdate();
});
update.addButtonInput("",I18n.getTranslation("localuser.clearCache"),()=>{
SW.forceClear();
});
}
{
const security = settings.addButton(I18n.getTranslation("localuser.accountSettings"));
const genSecurity = ()=>{
security.removeAll();
if(this.mfa_enabled){
security.addButtonInput("", I18n.getTranslation("localuser.2faDisable"), ()=>{
const form = security.addSubForm(
I18n.getTranslation("localuser.2faDisable"),
(_: any)=>{
if(_.message){
switch(_.code){
case 60008:
form.error("code", I18n.getTranslation("badCode"));
break;
}
}else{
this.mfa_enabled = false;
security.returnFromSub();
genSecurity();
}
},
{
fetchURL: this.info.api + "/users/@me/mfa/totp/disable",
headers: this.headers,
}
);
form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code", { required: true });
});
}else{
security.addButtonInput("", I18n.getTranslation("localuser.2faEnable"), async ()=>{
let secret = "";
for(let i = 0; i < 18; i++){
secret += "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[
Math.floor(Math.random() * 32)
];
}
const form = security.addSubForm(
I18n.getTranslation("localuser.setUp2fa"),
(_: any)=>{
if(_.message){
switch(_.code){
case 60008:
form.error("code", I18n.getTranslation("localuser.badCode"));
break;
case 400:
form.error("password", I18n.getTranslation("localuser.badPassword"));
break;
}
}else{
genSecurity();
this.mfa_enabled = true;
security.returnFromSub();
}
},
{
fetchURL: this.info.api + "/users/@me/mfa/totp/enable/",
headers: this.headers,
}
);
form.addTitle(
I18n.getTranslation("localuser.setUp2faInstruction")
);
form.addText(
I18n.getTranslation("localuser.2faCodeGive",secret)
);
form.addTextInput(I18n.getTranslation("localuser.password:"), "password", {
required: true,
password: true,
});
form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code", { required: true });
form.setValue("secret", secret);
});
}
security.addButtonInput("", I18n.getTranslation("localuser.changeDiscriminator"), ()=>{
const form = security.addSubForm(
I18n.getTranslation("localuser.changeDiscriminator"),
_=>{
security.returnFromSub();
},
{
fetchURL: this.info.api + "/users/@me/",
headers: this.headers,
method: "PATCH",
}
);
form.addTextInput(I18n.getTranslation("lcoaluser.newDiscriminator"), "discriminator");
});
security.addButtonInput("", I18n.getTranslation("localuser.changeEmail"), ()=>{
const form = security.addSubForm(
I18n.getTranslation("lcoaluser.changeEmail"),
_=>{
security.returnFromSub();
},
{
fetchURL: this.info.api + "/users/@me/",
headers: this.headers,
method: "PATCH",
}
);
form.addTextInput(I18n.getTranslation("localuser.password:"), "password", { password: true });
if(this.mfa_enabled){
form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code");
}
form.addTextInput(I18n.getTranslation("localuser.newEmail:"), "email");
});
security.addButtonInput("", I18n.getTranslation("localuser.changeUsername"), ()=>{
const form = security.addSubForm(
I18n.getTranslation("localuser.changeUsername"),
_=>{
security.returnFromSub();
},
{
fetchURL: this.info.api + "/users/@me/",
headers: this.headers,
method: "PATCH",
}
);
form.addTextInput(I18n.getTranslation("localuser.password:"), "password", { password: true });
if(this.mfa_enabled){
form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code");
}
form.addTextInput(I18n.getTranslation("localuser.newUsername"), "username");
});
security.addButtonInput("", I18n.getTranslation("localuser.changePassword"), ()=>{
const form = security.addSubForm(
I18n.getTranslation("localuser.changePassword"),
_=>{
security.returnFromSub();
},
{
fetchURL: this.info.api + "/users/@me/",
headers: this.headers,
method: "PATCH",
}
);
form.addTextInput(I18n.getTranslation("localuser.oldPassword:"), "password", { password: true });
if(this.mfa_enabled){
form.addTextInput(I18n.getTranslation("localuser.2faCode"), "code");
}
let in1 = "";
let in2 = "";
form.addTextInput(I18n.getTranslation("localuser.newPassword:"), "").watchForChange(text=>{
in1 = text;
});
const copy = form.addTextInput("New password again:", "");
copy.watchForChange(text=>{
in2 = text;
});
form.setValue("new_password", ()=>{
if(in1 === in2){
return in1;
}else{
throw new FormError(copy, I18n.getTranslation("localuser.PasswordsNoMatch"));
}
});
});
security.addSelect(I18n.getTranslation("localuser.language"),(e)=>{
I18n.setLanguage(I18n.options()[e]);
},I18n.options(),{
defaultIndex:I18n.options().indexOf(I18n.lang)
});
{
const box=security.addCheckboxInput(I18n.getTranslation("localuser.enableEVoice"),()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))});
box.onchange=(e)=>{
if(e){
if(confirm(I18n.getTranslation("localuser.VoiceWarning"))){
localStorage.setItem("Voice enabled","true")
}else{
box.value=true;
const checkbox=box.input.deref();
if(checkbox){
checkbox.checked=false;
}
}
}else{
localStorage.removeItem("Voice enabled");
}
}
}
};
genSecurity();
}
{
const connections = settings.addButton("Connections");
const connectionContainer = document.createElement("div");
connectionContainer.id = "connection-container";
fetch(this.info.api + "/connections", {
headers: this.headers,
})
.then(r=>r.json())
.then(json=>{
Object.keys(json)
.sort(key=>(json[key].enabled ? -1 : 1))
.forEach(key=>{
const connection = json[key];
const container = document.createElement("div");
container.textContent =
key.charAt(0).toUpperCase() + key.slice(1);
if(connection.enabled){
container.addEventListener("click", async ()=>{
const connectionRes = await fetch(
this.info.api + "/connections/" + key + "/authorize",
{
headers: this.headers,
}
);
const connectionJSON = await connectionRes.json();
window.open(
connectionJSON.url,
"_blank",
"noopener noreferrer"
);
});
}else{
container.classList.add("disabled");
container.title = I18n.getTranslation("localuser.PasswordsNoMatch");
}
connectionContainer.appendChild(container);
});
});
connections.addHTMLArea(connectionContainer);
}
{
const devPortal = settings.addButton(I18n.getTranslation("localuser.devPortal"));
fetch(this.info.api + "/teams", {
headers: this.headers,
}).then(async (teamsRes)=>{
const teams = await teamsRes.json();
devPortal.addButtonInput("", I18n.getTranslation("localuser.createApp"), ()=>{
const form = devPortal.addSubForm(
I18n.getTranslation("localuser.createApp"),
(json: any)=>{
if(json.message) form.error("name", json.message);
else{
devPortal.returnFromSub();
this.manageApplication(json.id,devPortal);
}
},
{
fetchURL: this.info.api + "/applications",
headers: this.headers,
method: "POST",
}
);
form.addTextInput("Name:", "name", { required: true });
form.addSelect(
I18n.getTranslation("localuser.team:"),
"team_id",
["Personal", ...teams.map((team: { name: string })=>team.name)],
{
defaultIndex: 0,
}
);
});
const appListContainer = document.createElement("div");
appListContainer.id = "app-list-container";
fetch(this.info.api + "/applications", {
headers: this.headers,
})
.then(r=>r.json())
.then(json=>{
json.forEach(
(application: {
cover_image: any;
icon: any;
id: string | undefined;
name: string | number;
bot: any;
})=>{
const container = document.createElement("div");
if(application.cover_image || application.icon){
const cover = document.createElement("img");
cover.crossOrigin = "anonymous";
cover.src =
this.info.cdn +
"/app-icons/" +
application.id +
"/" +
(application.cover_image || application.icon) +
".png?size=256";
cover.alt = "";
cover.loading = "lazy";
container.appendChild(cover);
}
const name = document.createElement("h2");
name.textContent = application.name + (application.bot ? " (Bot)" : "");
container.appendChild(name);
container.addEventListener("click", async ()=>{
this.manageApplication(application.id,devPortal);
});
appListContainer.appendChild(container);
}
);
});
devPortal.addHTMLArea(appListContainer);
});
}
settings.show();
}
readonly botTokens:Map<string,string>=new Map();
async manageApplication(appId = "", container:Options){
if(this.perminfo.applications){
for(const item of Object.keys(this.perminfo.applications)){
this.botTokens.set(item,this.perminfo.applications[item]);
}
}
const res = await fetch(this.info.api + "/applications/" + appId, {
headers: this.headers,
});
const json = await res.json();
const form=container.addSubForm(json.name,()=>{},{
fetchURL:this.info.api + "/applications/" + appId,
method:"PATCH",
headers:this.headers,
traditionalSubmit:true
});
form.addTextInput(I18n.getTranslation("localuser.appName"),"name",{initText:json.name});
form.addMDInput(I18n.getTranslation("localuser.description"),"description",{initText:json.description});
form.addFileInput("Icon:","icon");
form.addTextInput(I18n.getTranslation("localuser.privacyPolcyURL"),"privacy_policy_url",{initText:json.privacy_policy_url});
form.addTextInput(I18n.getTranslation("localuser.TOSURL"),"terms_of_service_url",{initText:json.terms_of_service_url});
form.addCheckboxInput(I18n.getTranslation("localuser.publicAvaliable"),"bot_public",{initState:json.bot_public});
form.addCheckboxInput(I18n.getTranslation("localuser.requireCode"),"bot_require_code_grant",{initState:json.bot_require_code_grant});
form.addButtonInput("",I18n.getTranslation("localuser."+(json.bot?"manageBot":"addBot")),async ()=>{
if(!json.bot){
if(!confirm(I18n.getTranslation("localuser.confirmAddBot"))){
return;
}
const updateRes = await fetch(
this.info.api + "/applications/" + appId + "/bot",
{
method: "POST",
headers: this.headers,
}
);
const updateJSON = await updateRes.json();
this.botTokens.set(appId,updateJSON.token);
}
this.manageBot(appId,form);
})
}
async manageBot(appId = "",container:Form){
const res = await fetch(this.info.api + "/applications/" + appId, {
headers: this.headers
});
const json = await res.json();
if(!json.bot){
return alert(I18n.getTranslation("localuser.confuseNoBot"));
}
const bot:mainuserjson=json.bot;
const form=container.addSubForm(I18n.getTranslation("localuser.editingBot",bot.username),out=>{console.log(out)},{
method:"PATCH",
fetchURL:this.info.api + "/applications/" + appId + "/bot",
headers:this.headers,
traditionalSubmit:true
});
form.addTextInput(I18n.getTranslation("localuser.botUsername"),"username",{initText:bot.username});
form.addFileInput(I18n.getTranslation("localuser.botAvatar"),"avatar");
form.addButtonInput("",I18n.getTranslation("localuser.resetToken"),async ()=>{
if(!confirm(I18n.getTranslation("localuser.confirmReset"))){
return;
}
const updateRes = await fetch(
this.info.api + "/applications/" + appId + "/bot/reset",
{
method: "POST",
headers: this.headers,
}
);
const updateJSON = await updateRes.json();
text.setText(I18n.getTranslation("localuser.tokenDisplay",updateJSON.token));
this.botTokens.set(appId,updateJSON.token);
if(this.perminfo.applications[appId]){
this.perminfo.applications[appId]=updateJSON.token;
this.userinfo.updateLocal();
}
});
const text=form.addText(I18n.getTranslation("localuser.tokenDisplay",this.botTokens.has(appId)?this.botTokens.get(appId) as string:"*****************") );
const check=form.addOptions("",{noSubmit:true});
if(!this.perminfo.applications){
this.perminfo.applications={};
this.userinfo.updateLocal();
}
const checkbox=check.addCheckboxInput(I18n.getTranslation("localuser.saveToken"),()=>{},{initState:!!this.perminfo.applications[appId]});
checkbox.watchForChange(_=>{
if(_){
if(this.botTokens.has(appId)){
this.perminfo.applications[appId]=this.botTokens.get(appId);
this.userinfo.updateLocal();
}else{
alert(I18n.getTranslation("localuser.noToken"));
checkbox.setState(false);
}
}else{
delete this.perminfo.applications[appId];
this.userinfo.updateLocal();
}
});
form.addButtonInput("",I18n.getTranslation("localuser.advancedBot"),()=>{
const token=this.botTokens.get(appId);
if(token){
const botc=new Bot(bot,token,this);
botc.settings();
}
});
form.addButtonInput("",I18n.getTranslation("localuser.botInviteCreate"),()=>{
Bot.InviteMaker(appId,form,this.info);
})
}
//---------- resolving members code -----------
readonly waitingmembers: Map<
string,
Map<string, (returns: memberjson | undefined) => void>
> = new Map();
readonly presences: Map<string, presencejson> = new Map();
async resolvemember(
id: string,
guildid: string
): Promise<memberjson | undefined>{
if(guildid === "@me"){
return undefined;
}
const guild = this.guildids.get(guildid);
const borked = true;
if(borked && guild && guild.member_count > 250){
//sorry puyo, I need to fix member resolving while it's broken on large guilds
try{
const req = await fetch(
this.info.api + "/guilds/" + guild.id + "/members/" + id,
{
headers: this.headers,
}
);
if(req.status !== 200){
return undefined;
}
return await req.json();
}catch{
return undefined;
}
}
let guildmap = this.waitingmembers.get(guildid);
if(!guildmap){
guildmap = new Map();
this.waitingmembers.set(guildid, guildmap);
}
const promise: Promise<memberjson | undefined> = new Promise(res=>{
guildmap.set(id, res);
this.getmembers();
});
return await promise;
}
fetchingmembers: Map<string, boolean> = new Map();
noncemap: Map<string, (r: [memberjson[], string[]]) => void> = new Map();
noncebuild: Map<string, [memberjson[], string[], number[]]> = new Map();
async gotChunk(chunk: {
chunk_index: number;
chunk_count: number;
nonce: string;
not_found?: string[];
members?: memberjson[];
presences: presencejson[];
}){
for(const thing of chunk.presences){
if(thing.user){
this.presences.set(thing.user.id, thing);
}
}
chunk.members ??= [];
const arr = this.noncebuild.get(chunk.nonce);
if(!arr)return;
arr[0] = arr[0].concat(chunk.members);
if(chunk.not_found){
arr[1] = chunk.not_found;
}
arr[2].push(chunk.chunk_index);
if(arr[2].length === chunk.chunk_count){
this.noncebuild.delete(chunk.nonce);
const func = this.noncemap.get(chunk.nonce);
if(!func)return;
func([arr[0], arr[1]]);
this.noncemap.delete(chunk.nonce);
}
}
async getmembers(){
const promise = new Promise(res=>{
setTimeout(res, 10);
});
await promise; //allow for more to be sent at once :P
if(this.ws){
this.waitingmembers.forEach(async (value, guildid)=>{
const keys = value.keys();
if(this.fetchingmembers.has(guildid)){
return;
}
const build: string[] = [];
for(const key of keys){
build.push(key);
if(build.length === 100){
break;
}
}
if(!build.length){
this.waitingmembers.delete(guildid);
return;
}
const promise: Promise<[memberjson[], string[]]> = new Promise(
res=>{
const nonce = "" + Math.floor(Math.random() * 100000000000);
this.noncemap.set(nonce, res);
this.noncebuild.set(nonce, [[], [], []]);
if(!this.ws)return;
this.ws.send(
JSON.stringify({
op: 8,
d: {
user_ids: build,
guild_id: guildid,
limit: 100,
nonce,
presences: true,
},
})
);
this.fetchingmembers.set(guildid, true);
}
);
const prom = await promise;
const data = prom[0];
for(const thing of data){
if(value.has(thing.id)){
const func = value.get(thing.id);
if(!func){
value.delete(thing.id);
continue;
}
func(thing);
value.delete(thing.id);
}
}
for(const thing of prom[1]){
if(value.has(thing)){
const func = value.get(thing);
if(!func){
value.delete(thing);
continue;
}
func(undefined);
value.delete(thing);
}
}
this.fetchingmembers.delete(guildid);
this.getmembers();
});
}
}
async pingEndpoint(){
const userInfo = getBulkInfo();
if(!userInfo.instances) userInfo.instances = {};
const wellknown = this.info.wellknown;
if(!userInfo.instances[wellknown]){
const pingRes = await fetch(this.info.api + "/ping");
const pingJSON = await pingRes.json();
userInfo.instances[wellknown] = pingJSON;
localStorage.setItem("userinfos", JSON.stringify(userInfo));
}
this.instancePing = userInfo.instances[wellknown].instance;
this.pageTitle("Loading...");
}
pageTitle(channelName = "", guildName = ""){
(document.getElementById("channelname") as HTMLSpanElement).textContent = channelName;
(document.getElementsByTagName("title")[0] as HTMLTitleElement).textContent = channelName + (guildName ? " | " + guildName : "") + " | " + this.instancePing.name +" | Jank Client";
}
async instanceStats(){
const res = await fetch(this.info.api + "/policies/stats", {
headers: this.headers,
});
const json = await res.json();
const dialog = new Dialog([
"vdiv",
["title", I18n.getTranslation("instanceStats.name",this.instancePing.name) ],
["text", I18n.getTranslation("instanceStats.users",json.counts.user)],
["text", I18n.getTranslation("instanceStats.servers",json.counts.guild)],
["text", I18n.getTranslation("instanceStats.messages",json.counts.message)],
["text", I18n.getTranslation("instanceStats.members",json.counts.members)],
]);
dialog.show();
}
}
export{ Localuser };