commit
80e6fb1924
41 changed files with 4624 additions and 3306 deletions
|
@ -102,10 +102,6 @@ app.use("/", async (req: Request, res: Response)=>{
|
||||||
res.sendFile(path.join(__dirname, "webpage", "invite.html"));
|
res.sendFile(path.join(__dirname, "webpage", "invite.html"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(req.path.endsWith("service.js")){
|
|
||||||
res.send("nope :3");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const filePath = path.join(__dirname, "webpage", req.path);
|
const filePath = path.join(__dirname, "webpage", req.path);
|
||||||
try{
|
try{
|
||||||
await fs.access(filePath);
|
await fs.access(filePath);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import{ getBulkInfo }from"./login.js";
|
import{ getBulkInfo }from"./login.js";
|
||||||
|
|
||||||
class Voice{
|
class AVoice{
|
||||||
audioCtx: AudioContext;
|
audioCtx: AudioContext;
|
||||||
info: { wave: string | Function; freq: number };
|
info: { wave: string | Function; freq: number };
|
||||||
playing: boolean;
|
playing: boolean;
|
||||||
|
@ -95,7 +95,7 @@ class Voice{
|
||||||
static noises(noise: string): void{
|
static noises(noise: string): void{
|
||||||
switch(noise){
|
switch(noise){
|
||||||
case"three": {
|
case"three": {
|
||||||
const voicy = new Voice("sin", 800);
|
const voicy = new AVoice("sin", 800);
|
||||||
voicy.play();
|
voicy.play();
|
||||||
setTimeout(_=>{
|
setTimeout(_=>{
|
||||||
voicy.freq = 1000;
|
voicy.freq = 1000;
|
||||||
|
@ -109,7 +109,7 @@ class Voice{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case"zip": {
|
case"zip": {
|
||||||
const voicy = new Voice((t: number, freq: number)=>{
|
const voicy = new AVoice((t: number, freq: number)=>{
|
||||||
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
|
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
|
||||||
}, 700);
|
}, 700);
|
||||||
voicy.play();
|
voicy.play();
|
||||||
|
@ -119,7 +119,7 @@ class Voice{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case"square": {
|
case"square": {
|
||||||
const voicy = new Voice("square", 600, 0.4);
|
const voicy = new AVoice("square", 600, 0.4);
|
||||||
voicy.play();
|
voicy.play();
|
||||||
setTimeout(_=>{
|
setTimeout(_=>{
|
||||||
voicy.freq = 800;
|
voicy.freq = 800;
|
||||||
|
@ -133,7 +133,7 @@ class Voice{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case"beep": {
|
case"beep": {
|
||||||
const voicy = new Voice("sin", 800);
|
const voicy = new AVoice("sin", 800);
|
||||||
voicy.play();
|
voicy.play();
|
||||||
setTimeout(_=>{
|
setTimeout(_=>{
|
||||||
voicy.stop();
|
voicy.stop();
|
||||||
|
@ -146,6 +146,38 @@ class Voice{
|
||||||
}, 150);
|
}, 150);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "join":{
|
||||||
|
const voicy = new AVoice("triangle", 600,.1);
|
||||||
|
voicy.play();
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.freq=800;
|
||||||
|
}, 75);
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.freq=1000;
|
||||||
|
}, 150);
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.stop();
|
||||||
|
}, 200);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "leave":{
|
||||||
|
const voicy = new AVoice("triangle", 850,.5);
|
||||||
|
voicy.play();
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.freq=700;
|
||||||
|
}, 100);
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.stop();
|
||||||
|
voicy.freq=400;
|
||||||
|
}, 180);
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.play();
|
||||||
|
}, 200);
|
||||||
|
setTimeout(_=>{
|
||||||
|
voicy.stop();
|
||||||
|
}, 250);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static get sounds(){
|
static get sounds(){
|
||||||
|
@ -161,4 +193,4 @@ class Voice{
|
||||||
return userinfos.preferences.notisound;
|
return userinfos.preferences.notisound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export{ Voice };
|
export{ AVoice as AVoice };
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
import{ Message }from"./message.js";
|
import{ Message }from"./message.js";
|
||||||
import{ Voice }from"./audio.js";
|
import{ AVoice }from"./audio.js";
|
||||||
import{ Contextmenu }from"./contextmenu.js";
|
import{ Contextmenu }from"./contextmenu.js";
|
||||||
import{ Dialog }from"./dialog.js";
|
import{ Dialog }from"./dialog.js";
|
||||||
import{ Guild }from"./guild.js";
|
import{ Guild }from"./guild.js";
|
||||||
|
@ -10,16 +10,11 @@ import{ Settings }from"./settings.js";
|
||||||
import{ Role, RoleList }from"./role.js";
|
import{ Role, RoleList }from"./role.js";
|
||||||
import{ InfiniteScroller }from"./infiniteScroller.js";
|
import{ InfiniteScroller }from"./infiniteScroller.js";
|
||||||
import{ SnowFlake }from"./snowflake.js";
|
import{ SnowFlake }from"./snowflake.js";
|
||||||
import{
|
import{channeljson,embedjson,messageCreateJson,messagejson,readyjson,startTypingjson}from"./jsontypes.js";
|
||||||
channeljson,
|
|
||||||
embedjson,
|
|
||||||
messageCreateJson,
|
|
||||||
messagejson,
|
|
||||||
readyjson,
|
|
||||||
startTypingjson,
|
|
||||||
}from"./jsontypes.js";
|
|
||||||
import{ MarkDown }from"./markdown.js";
|
import{ MarkDown }from"./markdown.js";
|
||||||
import{ Member }from"./member.js";
|
import{ Member }from"./member.js";
|
||||||
|
import { Voice } from "./voice.js";
|
||||||
|
import { User } from "./user.js";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface NotificationOptions {
|
interface NotificationOptions {
|
||||||
|
@ -55,6 +50,8 @@ class Channel extends SnowFlake{
|
||||||
idToPrev: Map<string, string> = new Map();
|
idToPrev: Map<string, string> = new Map();
|
||||||
idToNext: Map<string, string> = new Map();
|
idToNext: Map<string, string> = new Map();
|
||||||
messages: Map<string, Message> = new Map();
|
messages: Map<string, Message> = new Map();
|
||||||
|
voice?:Voice;
|
||||||
|
bitrate:number=128000;
|
||||||
static setupcontextmenu(){
|
static setupcontextmenu(){
|
||||||
this.contextmenu.addbutton("Copy channel id", function(this: Channel){
|
this.contextmenu.addbutton("Copy channel id", function(this: Channel){
|
||||||
navigator.clipboard.writeText(this.id);
|
navigator.clipboard.writeText(this.id);
|
||||||
|
@ -64,7 +61,7 @@ class Channel extends SnowFlake{
|
||||||
this.readbottom();
|
this.readbottom();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contextmenu.addbutton("Settings[temp]", function(this: Channel){
|
this.contextmenu.addbutton("Settings", function(this: Channel){
|
||||||
this.generateSettings();
|
this.generateSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,17 +76,6 @@ class Channel extends SnowFlake{
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.contextmenu.addbutton(
|
|
||||||
"Edit channel",
|
|
||||||
function(this: Channel){
|
|
||||||
this.editChannel();
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
function(){
|
|
||||||
return this.isAdmin();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.contextmenu.addbutton(
|
this.contextmenu.addbutton(
|
||||||
"Make invite",
|
"Make invite",
|
||||||
function(this: Channel){
|
function(this: Channel){
|
||||||
|
@ -123,13 +109,14 @@ class Channel extends SnowFlake{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("invitediv");
|
div.classList.add("invitediv");
|
||||||
const text = document.createElement("span");
|
const text = document.createElement("span");
|
||||||
|
text.classList.add("ellipsis");
|
||||||
div.append(text);
|
div.append(text);
|
||||||
let uses = 0;
|
let uses = 0;
|
||||||
let expires = 1800;
|
let expires = 1800;
|
||||||
const copycontainer = document.createElement("div");
|
const copycontainer = document.createElement("div");
|
||||||
copycontainer.classList.add("copycontainer");
|
copycontainer.classList.add("copycontainer");
|
||||||
const copy = document.createElement("span");
|
const copy = document.createElement("span");
|
||||||
copy.classList.add("copybutton", "svgtheme", "svg-copy");
|
copy.classList.add("copybutton", "svgicon", "svg-copy");
|
||||||
copycontainer.append(copy);
|
copycontainer.append(copy);
|
||||||
copycontainer.onclick = _=>{
|
copycontainer.onclick = _=>{
|
||||||
if(text.textContent){
|
if(text.textContent){
|
||||||
|
@ -207,15 +194,33 @@ class Channel extends SnowFlake{
|
||||||
generateSettings(){
|
generateSettings(){
|
||||||
this.sortPerms();
|
this.sortPerms();
|
||||||
const settings = new Settings("Settings for " + this.name);
|
const settings = new Settings("Settings for " + this.name);
|
||||||
|
{
|
||||||
const s1 = settings.addButton("roles");
|
const gensettings=settings.addButton("Settings");
|
||||||
|
const form=gensettings.addForm("",()=>{},{
|
||||||
|
fetchURL:this.info.api + "/channels/" + this.id,
|
||||||
|
method: "PATCH",
|
||||||
|
headers: this.headers,
|
||||||
|
});
|
||||||
|
form.addTextInput("Name:","name",{initText:this.name});
|
||||||
|
form.addMDInput("Topic:","topic",{initText:this.topic});
|
||||||
|
form.addCheckboxInput("NSFW:","nsfw",{initState:this.nsfw});
|
||||||
|
if(this.type!==4){
|
||||||
|
const options=["voice", "text", "announcement"];
|
||||||
|
form.addSelect("Type:","type",options,{
|
||||||
|
defaultIndex:options.indexOf({0:"text", 2:"voice", 5:"announcement", 4:"category" }[this.type] as string)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
form.addPreprocessor((obj:any)=>{
|
||||||
|
obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.type as string]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const s1 = settings.addButton("Permisions");
|
||||||
s1.options.push(
|
s1.options.push(
|
||||||
new RoleList(
|
new RoleList(
|
||||||
this.permission_overwritesar,
|
this.permission_overwritesar,
|
||||||
this.guild,
|
this.guild,
|
||||||
this.updateRolePermissions.bind(this),
|
this.updateRolePermissions.bind(this),
|
||||||
true
|
this
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
settings.show();
|
settings.show();
|
||||||
|
@ -336,6 +341,10 @@ class Channel extends SnowFlake{
|
||||||
}
|
}
|
||||||
this.setUpInfiniteScroller();
|
this.setUpInfiniteScroller();
|
||||||
this.perminfo ??= {};
|
this.perminfo ??= {};
|
||||||
|
if(this.type===2&&this.localuser.voiceFactory){
|
||||||
|
this.voice=this.localuser.voiceFactory.makeVoice(this.guild.id,this.id,{bitrate:this.bitrate});
|
||||||
|
this.setUpVoice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
get perminfo(){
|
get perminfo(){
|
||||||
return this.guild.perminfo.channels[this.id];
|
return this.guild.perminfo.channels[this.id];
|
||||||
|
@ -457,6 +466,7 @@ class Channel extends SnowFlake{
|
||||||
get visable(){
|
get visable(){
|
||||||
return this.hasPermission("VIEW_CHANNEL");
|
return this.hasPermission("VIEW_CHANNEL");
|
||||||
}
|
}
|
||||||
|
voiceUsers=new WeakRef(document.createElement("div"));
|
||||||
createguildHTML(admin = false): HTMLDivElement{
|
createguildHTML(admin = false): HTMLDivElement{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
this.html = new WeakRef(div);
|
this.html = new WeakRef(div);
|
||||||
|
@ -487,18 +497,18 @@ class Channel extends SnowFlake{
|
||||||
|
|
||||||
const decdiv = document.createElement("div");
|
const decdiv = document.createElement("div");
|
||||||
const decoration = document.createElement("span");
|
const decoration = document.createElement("span");
|
||||||
decoration.classList.add("svgtheme", "collapse-icon", "svg-category");
|
decoration.classList.add("svgicon", "collapse-icon", "svg-category");
|
||||||
decdiv.appendChild(decoration);
|
decdiv.appendChild(decoration);
|
||||||
|
|
||||||
const myhtml = document.createElement("p2");
|
const myhtml = document.createElement("p2");
|
||||||
|
myhtml.classList.add("ellipsis");
|
||||||
myhtml.textContent = this.name;
|
myhtml.textContent = this.name;
|
||||||
decdiv.appendChild(myhtml);
|
decdiv.appendChild(myhtml);
|
||||||
caps.appendChild(decdiv);
|
caps.appendChild(decdiv);
|
||||||
const childrendiv = document.createElement("div");
|
const childrendiv = document.createElement("div");
|
||||||
if(admin){
|
if(admin){
|
||||||
const addchannel = document.createElement("span");
|
const addchannel = document.createElement("span");
|
||||||
addchannel.textContent = "+";
|
addchannel.classList.add("addchannel","svgicon","svg-plus");
|
||||||
addchannel.classList.add("addchannel");
|
|
||||||
caps.appendChild(addchannel);
|
caps.appendChild(addchannel);
|
||||||
addchannel.onclick = _=>{
|
addchannel.onclick = _=>{
|
||||||
this.guild.createchannels(this.createChannel.bind(this));
|
this.guild.createchannels(this.createChannel.bind(this));
|
||||||
|
@ -506,8 +516,8 @@ class Channel extends SnowFlake{
|
||||||
this.coatDropDiv(decdiv, childrendiv);
|
this.coatDropDiv(decdiv, childrendiv);
|
||||||
}
|
}
|
||||||
div.appendChild(caps);
|
div.appendChild(caps);
|
||||||
caps.classList.add("capsflex");
|
caps.classList.add("flexltr","capsflex");
|
||||||
decdiv.classList.add("channeleffects");
|
decdiv.classList.add("flexltr","channeleffects");
|
||||||
decdiv.classList.add("channel");
|
decdiv.classList.add("channel");
|
||||||
|
|
||||||
Channel.contextmenu.bindContextmenu(decdiv, this,undefined);
|
Channel.contextmenu.bindContextmenu(decdiv, this,undefined);
|
||||||
|
@ -552,32 +562,84 @@ class Channel extends SnowFlake{
|
||||||
}
|
}
|
||||||
// @ts-ignore I dont wanna deal with this
|
// @ts-ignore I dont wanna deal with this
|
||||||
div.all = this;
|
div.all = this;
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.classList.add("channelbutton");
|
||||||
|
div.append(button);
|
||||||
const myhtml = document.createElement("span");
|
const myhtml = document.createElement("span");
|
||||||
|
myhtml.classList.add("ellipsis");
|
||||||
myhtml.textContent = this.name;
|
myhtml.textContent = this.name;
|
||||||
if(this.type === 0){
|
if(this.type === 0){
|
||||||
const decoration = document.createElement("span");
|
const decoration = document.createElement("span");
|
||||||
div.appendChild(decoration);
|
button.appendChild(decoration);
|
||||||
decoration.classList.add("space", "svgtheme", "svg-channel");
|
decoration.classList.add("space", "svgicon", "svg-channel");
|
||||||
}else if(this.type === 2){
|
}else if(this.type === 2){
|
||||||
//
|
//
|
||||||
const decoration = document.createElement("span");
|
const decoration = document.createElement("span");
|
||||||
div.appendChild(decoration);
|
button.appendChild(decoration);
|
||||||
decoration.classList.add("space", "svgtheme", "svg-voice");
|
decoration.classList.add("space", "svgicon", "svg-voice");
|
||||||
}else if(this.type === 5){
|
}else if(this.type === 5){
|
||||||
//
|
//
|
||||||
const decoration = document.createElement("span");
|
const decoration = document.createElement("span");
|
||||||
div.appendChild(decoration);
|
button.appendChild(decoration);
|
||||||
decoration.classList.add("space", "svgtheme", "svg-announce");
|
decoration.classList.add("space", "svgicon", "svg-announce");
|
||||||
}else{
|
}else{
|
||||||
console.log(this.type);
|
console.log(this.type);
|
||||||
}
|
}
|
||||||
div.appendChild(myhtml);
|
button.appendChild(myhtml);
|
||||||
div.onclick = _=>{
|
button.onclick = _=>{
|
||||||
this.getHTML();
|
this.getHTML();
|
||||||
|
const toggle = document.getElementById("maintoggle") as HTMLInputElement;
|
||||||
|
toggle.checked = true;
|
||||||
};
|
};
|
||||||
|
if(this.type===2){
|
||||||
|
const voiceUsers=document.createElement("div");
|
||||||
|
div.append(voiceUsers);
|
||||||
|
this.voiceUsers=new WeakRef(voiceUsers);
|
||||||
|
this.updateVoiceUsers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
async setUpVoice(){
|
||||||
|
if(!this.voice) return;
|
||||||
|
this.voice.onMemberChange=async (memb,joined)=>{
|
||||||
|
console.log(memb,joined);
|
||||||
|
if(typeof memb!=="string"){
|
||||||
|
await Member.new(memb,this.guild);
|
||||||
|
}
|
||||||
|
this.updateVoiceUsers();
|
||||||
|
if(this.voice===this.localuser.currentVoice){
|
||||||
|
AVoice.noises("join");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async updateVoiceUsers(){
|
||||||
|
const voiceUsers=this.voiceUsers.deref();
|
||||||
|
if(!voiceUsers||!this.voice) return;
|
||||||
|
console.warn(this.voice.userids)
|
||||||
|
|
||||||
|
const html=(await Promise.all(this.voice.userids.entries().toArray().map(async _=>{
|
||||||
|
const user=await User.resolve(_[0],this.localuser);
|
||||||
|
console.log(user);
|
||||||
|
const member=await Member.resolveMember(user,this.guild);
|
||||||
|
const array=[member,_[1]] as [Member, typeof _[1]];
|
||||||
|
return array;
|
||||||
|
}))).flatMap(([member,_obj])=>{
|
||||||
|
if(!member){
|
||||||
|
console.warn("This is weird, member doesn't exist :P");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const div=document.createElement("div");
|
||||||
|
div.classList.add("voiceuser");
|
||||||
|
const span=document.createElement("span");
|
||||||
|
span.textContent=member.name;
|
||||||
|
div.append(span);
|
||||||
|
return div;
|
||||||
|
});
|
||||||
|
|
||||||
|
voiceUsers.innerHTML="";
|
||||||
|
voiceUsers.append(...html);
|
||||||
|
}
|
||||||
get myhtml(){
|
get myhtml(){
|
||||||
if(this.html){
|
if(this.html){
|
||||||
return this.html.deref();
|
return this.html.deref();
|
||||||
|
@ -684,68 +746,6 @@ class Channel extends SnowFlake{
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
editChannel(){
|
|
||||||
let name = this.name;
|
|
||||||
let topic = this.topic;
|
|
||||||
let nsfw = this.nsfw;
|
|
||||||
const thisid = this.id;
|
|
||||||
const thistype = this.type;
|
|
||||||
const full = new Dialog([
|
|
||||||
"hdiv",
|
|
||||||
[
|
|
||||||
"vdiv",
|
|
||||||
[
|
|
||||||
"textbox",
|
|
||||||
"Channel name:",
|
|
||||||
this.name,
|
|
||||||
function(this: HTMLInputElement){
|
|
||||||
name = this.value;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"mdbox",
|
|
||||||
"Channel topic:",
|
|
||||||
this.topic,
|
|
||||||
function(this: HTMLTextAreaElement){
|
|
||||||
topic = this.value;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"checkbox",
|
|
||||||
"NSFW Channel",
|
|
||||||
this.nsfw,
|
|
||||||
function(this: HTMLInputElement){
|
|
||||||
nsfw = this.checked;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"button",
|
|
||||||
"",
|
|
||||||
"submit",
|
|
||||||
()=>{
|
|
||||||
fetch(this.info.api + "/channels/" + thisid, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: this.headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
name,
|
|
||||||
type: thistype,
|
|
||||||
topic,
|
|
||||||
bitrate: 64000,
|
|
||||||
user_limit: 0,
|
|
||||||
nsfw,
|
|
||||||
flags: 0,
|
|
||||||
rate_limit_per_user: 0,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
console.log(full);
|
|
||||||
full.hide();
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
full.show();
|
|
||||||
console.log(full);
|
|
||||||
}
|
|
||||||
deleteChannel(){
|
deleteChannel(){
|
||||||
fetch(this.info.api + "/channels/" + this.id, {
|
fetch(this.info.api + "/channels/" + this.id, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
@ -764,6 +764,7 @@ class Channel extends SnowFlake{
|
||||||
}
|
}
|
||||||
makereplybox(){
|
makereplybox(){
|
||||||
const replybox = document.getElementById("replybox") as HTMLElement;
|
const replybox = document.getElementById("replybox") as HTMLElement;
|
||||||
|
const typebox = document.getElementById("typebox") as HTMLElement;
|
||||||
if(this.replyingto){
|
if(this.replyingto){
|
||||||
replybox.innerHTML = "";
|
replybox.innerHTML = "";
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
|
@ -776,14 +777,16 @@ class Channel extends SnowFlake{
|
||||||
replybox.classList.add("hideReplyBox");
|
replybox.classList.add("hideReplyBox");
|
||||||
this.replyingto = null;
|
this.replyingto = null;
|
||||||
replybox.innerHTML = "";
|
replybox.innerHTML = "";
|
||||||
|
typebox.classList.remove("typeboxreplying");
|
||||||
};
|
};
|
||||||
replybox.classList.remove("hideReplyBox");
|
replybox.classList.remove("hideReplyBox");
|
||||||
X.textContent = "⦻";
|
X.classList.add("cancelReply","svgicon","svg-x");
|
||||||
X.classList.add("cancelReply");
|
|
||||||
replybox.append(span);
|
replybox.append(span);
|
||||||
replybox.append(X);
|
replybox.append(X);
|
||||||
|
typebox.classList.add("typeboxreplying");
|
||||||
}else{
|
}else{
|
||||||
replybox.classList.add("hideReplyBox");
|
replybox.classList.add("hideReplyBox");
|
||||||
|
typebox.classList.remove("typeboxreplying");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getmessage(id: string): Promise<Message>{
|
async getmessage(id: string): Promise<Message>{
|
||||||
|
@ -840,6 +843,9 @@ class Channel extends SnowFlake{
|
||||||
loading.classList.add("loading");
|
loading.classList.add("loading");
|
||||||
this.rendertyping();
|
this.rendertyping();
|
||||||
this.localuser.getSidePannel();
|
this.localuser.getSidePannel();
|
||||||
|
if(this.voice&&localStorage.getItem("Voice enabled")){
|
||||||
|
this.localuser.joinVoice(this);
|
||||||
|
}
|
||||||
await this.putmessages();
|
await this.putmessages();
|
||||||
await prom;
|
await prom;
|
||||||
if(id !== Channel.genid){
|
if(id !== Channel.genid){
|
||||||
|
@ -972,12 +978,7 @@ class Channel extends SnowFlake{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await fetch(
|
await fetch(
|
||||||
this.info.api +
|
this.info.api + "/channels/" +this.id +"/messages?limit=100&after=" +id,{
|
||||||
"/channels/" +
|
|
||||||
this.id +
|
|
||||||
"/messages?limit=100&after=" +
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1175,12 +1176,11 @@ class Channel extends SnowFlake{
|
||||||
|
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.guild_id = json.guild_id;
|
this.guild_id = json.guild_id;
|
||||||
|
const oldover=this.permission_overwrites;
|
||||||
this.permission_overwrites = new Map();
|
this.permission_overwrites = new Map();
|
||||||
|
this.permission_overwritesar=[];
|
||||||
for(const thing of json.permission_overwrites){
|
for(const thing of json.permission_overwrites){
|
||||||
if(
|
if(thing.id === "1182819038095799904" || thing.id === "1182820803700625444"){
|
||||||
thing.id === "1182819038095799904" ||
|
|
||||||
thing.id === "1182820803700625444"
|
|
||||||
){
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.permission_overwrites.set(
|
this.permission_overwrites.set(
|
||||||
|
@ -1195,9 +1195,26 @@ class Channel extends SnowFlake{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const nchange=[...new Set<string>().union(oldover).difference(this.permission_overwrites)];
|
||||||
|
const pchange=[...new Set<string>().union(this.permission_overwrites).difference(oldover)];
|
||||||
|
for(const thing of nchange){
|
||||||
|
const role=this.guild.roleids.get(thing);
|
||||||
|
if(role){
|
||||||
|
this.croleUpdate(role,new Permissions("0"),false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(const thing of pchange){
|
||||||
|
const role=this.guild.roleids.get(thing);
|
||||||
|
const perms=this.permission_overwrites.get(thing);
|
||||||
|
if(role&&perms){
|
||||||
|
this.croleUpdate(role,perms,true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(pchange,nchange);
|
||||||
this.topic = json.topic;
|
this.topic = json.topic;
|
||||||
this.nsfw = json.nsfw;
|
this.nsfw = json.nsfw;
|
||||||
}
|
}
|
||||||
|
croleUpdate:(role:Role,perm:Permissions,added:boolean)=>unknown=()=>{};
|
||||||
typingstart(){
|
typingstart(){
|
||||||
if(this.typing > Date.now()){
|
if(this.typing > Date.now()){
|
||||||
return;
|
return;
|
||||||
|
@ -1310,31 +1327,25 @@ class Channel extends SnowFlake{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(
|
if(
|
||||||
this.localuser.lookingguild?.prevchannel === this &&
|
this.localuser.lookingguild?.prevchannel === this && document.hasFocus()
|
||||||
document.hasFocus()
|
|
||||||
){
|
){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.notification === "all"){
|
if(this.notification === "all"){
|
||||||
this.notify(messagez);
|
this.notify(messagez);
|
||||||
}else if(
|
}else if(
|
||||||
this.notification === "mentions" &&
|
this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)
|
||||||
messagez.mentionsuser(this.localuser.user)
|
|
||||||
){
|
){
|
||||||
this.notify(messagez);
|
this.notify(messagez);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notititle(message: Message): string{
|
notititle(message: Message): string{
|
||||||
return(
|
return(
|
||||||
message.author.username +
|
message.author.username + " > " + this.guild.properties.name + " > " + this.name
|
||||||
" > " +
|
|
||||||
this.guild.properties.name +
|
|
||||||
" > " +
|
|
||||||
this.name
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
notify(message: Message, deep = 0){
|
notify(message: Message, deep = 0){
|
||||||
Voice.noises(Voice.getNotificationSound());
|
AVoice.noises(AVoice.getNotificationSound());
|
||||||
if(!("Notification" in window)){
|
if(!("Notification" in window)){
|
||||||
}else if(Notification.permission === "granted"){
|
}else if(Notification.permission === "granted"){
|
||||||
let noticontent: string | undefined | null = message.content.textContent;
|
let noticontent: string | undefined | null = message.content.textContent;
|
||||||
|
@ -1393,21 +1404,23 @@ class Channel extends SnowFlake{
|
||||||
if(permission){
|
if(permission){
|
||||||
permission.allow = perms.allow;
|
permission.allow = perms.allow;
|
||||||
permission.deny = perms.deny;
|
permission.deny = perms.deny;
|
||||||
|
}else{
|
||||||
|
//this.permission_overwrites.set(id,perms);
|
||||||
|
}
|
||||||
await fetch(
|
await fetch(
|
||||||
this.info.api + "/channels/" + this.id + "/permissions/" + id,
|
this.info.api + "/channels/" + this.id + "/permissions/" + id,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
allow: permission.allow.toString(),
|
allow: perms.allow.toString(),
|
||||||
deny: permission.deny.toString(),
|
deny: perms.deny.toString(),
|
||||||
id,
|
id,
|
||||||
type: 0,
|
type: 0,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Channel.setupcontextmenu();
|
Channel.setupcontextmenu();
|
||||||
export{ Channel };
|
export{ Channel };
|
||||||
|
|
|
@ -2,7 +2,7 @@ class Contextmenu<x, y>{
|
||||||
static currentmenu: HTMLElement | "";
|
static currentmenu: HTMLElement | "";
|
||||||
name: string;
|
name: string;
|
||||||
buttons: [
|
buttons: [
|
||||||
string,
|
string|(()=>string),
|
||||||
(this: x, arg: y, e: MouseEvent) => void,
|
(this: x, arg: y, e: MouseEvent) => void,
|
||||||
string | null,
|
string | null,
|
||||||
(this: x, arg: y) => boolean,
|
(this: x, arg: y) => boolean,
|
||||||
|
@ -27,7 +27,7 @@ class Contextmenu<x, y>{
|
||||||
this.buttons = [];
|
this.buttons = [];
|
||||||
}
|
}
|
||||||
addbutton(
|
addbutton(
|
||||||
text: string,
|
text: string|(()=>string),
|
||||||
onclick: (this: x, arg: y, e: MouseEvent) => void,
|
onclick: (this: x, arg: y, e: MouseEvent) => void,
|
||||||
img: null | string = null,
|
img: null | string = null,
|
||||||
shown: (this: x, arg: y) => boolean = _=>true,
|
shown: (this: x, arg: y) => boolean = _=>true,
|
||||||
|
@ -58,7 +58,11 @@ class Contextmenu<x, y>{
|
||||||
const intext = document.createElement("button");
|
const intext = document.createElement("button");
|
||||||
intext.disabled = !thing[4].bind(addinfo).call(addinfo, other);
|
intext.disabled = !thing[4].bind(addinfo).call(addinfo, other);
|
||||||
intext.classList.add("contextbutton");
|
intext.classList.add("contextbutton");
|
||||||
|
if(thing[0] instanceof Function){
|
||||||
|
intext.textContent = thing[0]();
|
||||||
|
}else{
|
||||||
intext.textContent = thing[0];
|
intext.textContent = thing[0];
|
||||||
|
}
|
||||||
console.log(thing);
|
console.log(thing);
|
||||||
if(thing[5] === "button" || thing[5] === "submenu"){
|
if(thing[5] === "button" || thing[5] === "submenu"){
|
||||||
intext.onclick = thing[1].bind(addinfo, other);
|
intext.onclick = thing[1].bind(addinfo, other);
|
||||||
|
@ -86,6 +90,13 @@ class Contextmenu<x, y>{
|
||||||
this.makemenu(event.clientX, event.clientY, addinfo, other);
|
this.makemenu(event.clientX, event.clientY, addinfo, other);
|
||||||
};
|
};
|
||||||
obj.addEventListener("contextmenu", func);
|
obj.addEventListener("contextmenu", func);
|
||||||
|
obj.addEventListener("touchstart",(event: TouchEvent)=>{
|
||||||
|
if(event.touches.length > 1){
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
|
||||||
|
}
|
||||||
|
},{passive:true});
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
static keepOnScreen(obj: HTMLElement){
|
static keepOnScreen(obj: HTMLElement){
|
||||||
|
|
|
@ -11,13 +11,7 @@ type dialogjson =
|
||||||
| ["title", string]
|
| ["title", string]
|
||||||
| ["radio", string, string[], (this: unknown, e: string) => unknown, number]
|
| ["radio", string, string[], (this: unknown, e: string) => unknown, number]
|
||||||
| ["html", HTMLElement]
|
| ["html", HTMLElement]
|
||||||
| [
|
| ["select", string, string[], (this: HTMLSelectElement, e: Event) => unknown, number]
|
||||||
"select",
|
|
||||||
string,
|
|
||||||
string[],
|
|
||||||
(this: HTMLSelectElement, e: Event) => unknown,
|
|
||||||
number
|
|
||||||
]
|
|
||||||
| ["tabs", [string, dialogjson][]];
|
| ["tabs", [string, dialogjson][]];
|
||||||
class Dialog{
|
class Dialog{
|
||||||
layout: dialogjson;
|
layout: dialogjson;
|
||||||
|
@ -197,11 +191,17 @@ class Dialog{
|
||||||
case"select": {
|
case"select": {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
const label = document.createElement("label");
|
const label = document.createElement("label");
|
||||||
|
const selectSpan = document.createElement("span");
|
||||||
|
selectSpan.classList.add("selectspan");
|
||||||
const select = document.createElement("select");
|
const select = document.createElement("select");
|
||||||
|
const selectArrow = document.createElement("span");
|
||||||
|
selectArrow.classList.add("svgicon","svg-category","selectarrow");
|
||||||
|
|
||||||
label.textContent = array[1];
|
label.textContent = array[1];
|
||||||
|
selectSpan.append(select);
|
||||||
|
selectSpan.append(selectArrow);
|
||||||
div.append(label);
|
div.append(label);
|
||||||
div.appendChild(select);
|
div.appendChild(selectSpan);
|
||||||
for(const thing of array[2]){
|
for(const thing of array[2]){
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.textContent = thing;
|
option.textContent = thing;
|
||||||
|
|
|
@ -153,8 +153,9 @@ class Group extends Channel{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
Group.contextmenu.bindContextmenu(div, this,undefined);
|
Group.contextmenu.bindContextmenu(div, this,undefined);
|
||||||
this.html = new WeakRef(div);
|
this.html = new WeakRef(div);
|
||||||
div.classList.add("channeleffects");
|
div.classList.add("flexltr","liststyle");
|
||||||
const myhtml = document.createElement("span");
|
const myhtml = document.createElement("span");
|
||||||
|
myhtml.classList.add("ellipsis");
|
||||||
myhtml.textContent = this.name;
|
myhtml.textContent = this.name;
|
||||||
div.appendChild(this.user.buildpfp());
|
div.appendChild(this.user.buildpfp());
|
||||||
div.appendChild(myhtml);
|
div.appendChild(myhtml);
|
||||||
|
|
|
@ -157,13 +157,11 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
||||||
if(this.json?.footer?.text){
|
if(this.json?.footer?.text){
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
span.textContent = this.json.footer.text;
|
span.textContent = this.json.footer.text;
|
||||||
span.classList.add("spaceright");
|
|
||||||
footer.append(span);
|
footer.append(span);
|
||||||
}
|
}
|
||||||
if(this.json?.footer && this.json?.timestamp){
|
if(this.json?.footer && this.json?.timestamp){
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
span.textContent = "•";
|
span.textContent = " • ";
|
||||||
span.classList.add("spaceright");
|
|
||||||
footer.append(span);
|
footer.append(span);
|
||||||
}
|
}
|
||||||
if(this.json?.timestamp){
|
if(this.json?.timestamp){
|
||||||
|
@ -288,7 +286,7 @@ json.guild;
|
||||||
guild as invitejson["guild"] & { info: { cdn: string } }
|
guild as invitejson["guild"] & { info: { cdn: string } }
|
||||||
);
|
);
|
||||||
const iconrow = document.createElement("div");
|
const iconrow = document.createElement("div");
|
||||||
iconrow.classList.add("flexltr", "flexstart");
|
iconrow.classList.add("flexltr");
|
||||||
iconrow.append(icon);
|
iconrow.append(icon);
|
||||||
{
|
{
|
||||||
const guildinfo = document.createElement("div");
|
const guildinfo = document.createElement("div");
|
||||||
|
|
|
@ -143,7 +143,7 @@ class Emoji{
|
||||||
title.classList.add("emojiTitle");
|
title.classList.add("emojiTitle");
|
||||||
menu.append(title);
|
menu.append(title);
|
||||||
const selection = document.createElement("div");
|
const selection = document.createElement("div");
|
||||||
selection.classList.add("flexltr", "dontshrink", "emojirow");
|
selection.classList.add("flexltr", "emojirow");
|
||||||
const body = document.createElement("div");
|
const body = document.createElement("div");
|
||||||
body.classList.add("emojiBody");
|
body.classList.add("emojiBody");
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,9 @@ class File{
|
||||||
div.append(contained);
|
div.append(contained);
|
||||||
const controls = document.createElement("div");
|
const controls = document.createElement("div");
|
||||||
const garbage = document.createElement("button");
|
const garbage = document.createElement("button");
|
||||||
garbage.textContent = "🗑";
|
const icon = document.createElement("span");
|
||||||
|
icon.classList.add("svgicon","svg-delete");
|
||||||
|
garbage.append(icon);
|
||||||
garbage.onclick = _=>{
|
garbage.onclick = _=>{
|
||||||
div.remove();
|
div.remove();
|
||||||
files.splice(files.indexOf(file), 1);
|
files.splice(files.indexOf(file), 1);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import{
|
||||||
emojijson,
|
emojijson,
|
||||||
memberjson,
|
memberjson,
|
||||||
invitejson,
|
invitejson,
|
||||||
|
rolesjson,
|
||||||
}from"./jsontypes.js";
|
}from"./jsontypes.js";
|
||||||
import{ User }from"./user.js";
|
import{ User }from"./user.js";
|
||||||
|
|
||||||
|
@ -114,16 +115,67 @@ class Guild extends SnowFlake{
|
||||||
}
|
}
|
||||||
form.addTextInput("Region:", "region", { initText: region });
|
form.addTextInput("Region:", "region", { initText: region });
|
||||||
}
|
}
|
||||||
const s1 = settings.addButton("roles");
|
const s1 = settings.addButton("Roles");
|
||||||
const permlist: [Role, Permissions][] = [];
|
const permlist: [Role, Permissions][] = [];
|
||||||
for(const thing of this.roles){
|
for(const thing of this.roles){
|
||||||
permlist.push([thing, thing.permissions]);
|
permlist.push([thing, thing.permissions]);
|
||||||
}
|
}
|
||||||
s1.options.push(
|
s1.options.push(
|
||||||
new RoleList(permlist, this, this.updateRolePermissions.bind(this))
|
new RoleList(permlist, this, this.updateRolePermissions.bind(this),false)
|
||||||
);
|
);
|
||||||
settings.show();
|
settings.show();
|
||||||
}
|
}
|
||||||
|
roleUpdate:(role:Role,added:-1|0|1)=>unknown=()=>{};
|
||||||
|
sortRoles(){
|
||||||
|
this.roles.sort((a,b)=>(b.position-a.position));
|
||||||
|
}
|
||||||
|
async recalcRoles(){
|
||||||
|
let position=this.roles.length;
|
||||||
|
const map=this.roles.map(_=>{
|
||||||
|
position--;
|
||||||
|
return {id:_.id,position};
|
||||||
|
})
|
||||||
|
await fetch(this.info.api+"/guilds/"+this.id+"/roles",{
|
||||||
|
method:"PATCH",
|
||||||
|
body:JSON.stringify(map),
|
||||||
|
headers:this.headers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
newRole(rolej:rolesjson){
|
||||||
|
const role=new Role(rolej,this);
|
||||||
|
this.roles.push(role);
|
||||||
|
this.roleids.set(role.id, role);
|
||||||
|
this.sortRoles();
|
||||||
|
this.roleUpdate(role,1);
|
||||||
|
}
|
||||||
|
updateRole(rolej:rolesjson){
|
||||||
|
const role=this.roleids.get(rolej.id) as Role;
|
||||||
|
role.newJson(rolej);
|
||||||
|
this.roleUpdate(role,0);
|
||||||
|
}
|
||||||
|
memberupdate(json:memberjson){
|
||||||
|
let member:undefined|Member=undefined;
|
||||||
|
for(const thing of this.members){
|
||||||
|
if(thing.id===json.id){
|
||||||
|
member=thing;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!member) return;
|
||||||
|
member.update(json);
|
||||||
|
if(member===this.member){
|
||||||
|
console.log(member);
|
||||||
|
this.loadGuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteRole(id:string){
|
||||||
|
const role = this.roleids.get(id);
|
||||||
|
if(!role) return;
|
||||||
|
this.roleids.delete(id);
|
||||||
|
this.roles.splice(this.roles.indexOf(role),1);
|
||||||
|
this.roleUpdate(role,-1);
|
||||||
|
}
|
||||||
constructor(
|
constructor(
|
||||||
json: guildjson | -1,
|
json: guildjson | -1,
|
||||||
owner: Localuser,
|
owner: Localuser,
|
||||||
|
@ -153,6 +205,7 @@ class Guild extends SnowFlake{
|
||||||
this.roles.push(roleh);
|
this.roles.push(roleh);
|
||||||
this.roleids.set(roleh.id, roleh);
|
this.roleids.set(roleh.id, roleh);
|
||||||
}
|
}
|
||||||
|
this.sortRoles();
|
||||||
if(member instanceof User){
|
if(member instanceof User){
|
||||||
Member.resolveMember(member, this).then(_=>{
|
Member.resolveMember(member, this).then(_=>{
|
||||||
if(_){
|
if(_){
|
||||||
|
|
|
@ -11,28 +11,29 @@
|
||||||
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
||||||
<link href="/style.css" rel="stylesheet">
|
<link href="/style.css" rel="stylesheet">
|
||||||
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
||||||
|
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="Dark-theme" style="overflow-y: scroll;">
|
<body class="no-theme" style="overflow-y: scroll;">
|
||||||
<div id="titleDiv">
|
<div id="titleDiv">
|
||||||
<img src="/logo.svg" width="40">
|
<img src="/logo.svg" width="40">
|
||||||
<h1 id="pageTitle">Jank Client</h1>
|
<h1 id="pageTitle">Jank Client</h1>
|
||||||
<a href="https://sb-jankclient.vanillaminigames.net/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat"
|
<a href="https://sb-jankclient.vanillaminigames.net/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat"
|
||||||
class="TitleButtons">
|
class="TitleButtons">
|
||||||
<h1>Spacebar Guild</h1>
|
Spacebar Guild
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
|
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
|
||||||
<h1>Github</h1>
|
Github
|
||||||
|
</a>
|
||||||
|
<a href="/channels/@me" class="TitleButtons">
|
||||||
|
Open Client
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexttb">
|
<div id="homePage">
|
||||||
|
|
||||||
<div class="flexttb pagehead">
|
<h1 class="pagehead">Welcome to Jank Client</h1>
|
||||||
<h1>Welcome to Jank Client</h1>
|
|
||||||
</div>
|
|
||||||
<div class="pagebox">
|
<div class="pagebox">
|
||||||
<p>Jank Client is a spacebar compatible client seeking to be as good as it can be with many features including:
|
<p>Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:</p>
|
||||||
</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Direct Messaging</li>
|
<li>Direct Messaging</li>
|
||||||
<li>Reactions support</li>
|
<li>Reactions support</li>
|
||||||
|
@ -44,16 +45,16 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagebox">
|
<div class="pagebox">
|
||||||
<h2>Spacebar compatible Instances:</h2>
|
<h2>Spacebar-Compatible Instances:</h2>
|
||||||
<div id="instancebox">
|
<div id="instancebox">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagebox">
|
<div class="pagebox">
|
||||||
<h2>Contribute to Jank Client</h2>
|
<h2>Contribute to Jank Client</h2>
|
||||||
<p>We always appreciate some help, wether that be in the form of bug reports, or code, or even just pointing out
|
<p>We always appreciate some help, whether that be in the form of bug reports, or code, or even just pointing out
|
||||||
some typos.</p><br>
|
some typos.</p><br>
|
||||||
</a><a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
|
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
|
||||||
<h1>Github</h1>
|
Github
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,22 +7,22 @@ fetch("/instances.json")
|
||||||
.then(
|
.then(
|
||||||
(
|
(
|
||||||
json: {
|
json: {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
descriptionLong?: string;
|
descriptionLong?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
display?: boolean;
|
display?: boolean;
|
||||||
online?: boolean;
|
online?: boolean;
|
||||||
uptime: { alltime: number; daytime: number; weektime: number };
|
uptime: { alltime: number; daytime: number; weektime: number };
|
||||||
urls: {
|
urls: {
|
||||||
wellknown: string;
|
wellknown: string;
|
||||||
api: string;
|
api: string;
|
||||||
cdn: string;
|
cdn: string;
|
||||||
gateway: string;
|
gateway: string;
|
||||||
login?: string;
|
login?: string;
|
||||||
};
|
};
|
||||||
}[]
|
}[]
|
||||||
)=>{
|
)=>{
|
||||||
console.warn(json);
|
console.warn(json);
|
||||||
for(const instance of json){
|
for(const instance of json){
|
||||||
|
@ -37,11 +37,11 @@ login?: string;
|
||||||
div.append(img);
|
div.append(img);
|
||||||
}
|
}
|
||||||
const statbox = document.createElement("div");
|
const statbox = document.createElement("div");
|
||||||
statbox.classList.add("flexttb");
|
statbox.classList.add("flexttb","flexgrow");
|
||||||
|
|
||||||
{
|
{
|
||||||
const textbox = document.createElement("div");
|
const textbox = document.createElement("div");
|
||||||
textbox.classList.add("flexttb", "instatancetextbox");
|
textbox.classList.add("flexttb", "instancetextbox");
|
||||||
const title = document.createElement("h2");
|
const title = document.createElement("h2");
|
||||||
title.innerText = instance.name;
|
title.innerText = instance.name;
|
||||||
if(instance.online !== undefined){
|
if(instance.online !== undefined){
|
||||||
|
@ -77,8 +77,7 @@ login?: string;
|
||||||
div.append(statbox);
|
div.append(statbox);
|
||||||
div.onclick = _=>{
|
div.onclick = _=>{
|
||||||
if(instance.online){
|
if(instance.online){
|
||||||
window.location.href =
|
window.location.href = "/register.html?instance=" + encodeURI(instance.name);
|
||||||
"/register.html?instance=" + encodeURI(instance.name);
|
|
||||||
}else{
|
}else{
|
||||||
alert("Instance is offline, can't connect");
|
alert("Instance is offline, can't connect");
|
||||||
}
|
}
|
||||||
|
|
96
src/webpage/i18n.ts
Normal file
96
src/webpage/i18n.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
type translation={
|
||||||
|
[key:string]:string|{[key:string]:string}
|
||||||
|
};
|
||||||
|
let res:()=>unknown=()=>{};
|
||||||
|
class I18n{
|
||||||
|
static lang:string;
|
||||||
|
static translations:{[key:string]:string}[]=[];
|
||||||
|
static done=new Promise<void>((res2,_reject)=>{
|
||||||
|
res=res2;
|
||||||
|
});
|
||||||
|
static async create(json:translation|string,lang:string){
|
||||||
|
if(typeof json === "string"){
|
||||||
|
json=await (await fetch(json)).json() as translation;
|
||||||
|
}
|
||||||
|
const translations:{[key:string]:string}[]=[];
|
||||||
|
let translation=json[lang];
|
||||||
|
if(!translation){
|
||||||
|
translation=json[lang[0]+lang[1]];
|
||||||
|
if(!translation){
|
||||||
|
console.error(lang+" does not exist in the translations");
|
||||||
|
translation=json["en"];
|
||||||
|
lang="en";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
translations.push(await this.toTranslation(translation,lang));
|
||||||
|
if(lang!=="en"){
|
||||||
|
translations.push(await this.toTranslation(json["en"],"en"))
|
||||||
|
}
|
||||||
|
this.lang=lang;
|
||||||
|
this.translations=translations;
|
||||||
|
res();
|
||||||
|
}
|
||||||
|
static getTranslation(msg:string,...params:string[]):string{
|
||||||
|
let str:string|undefined;
|
||||||
|
for(const json of this.translations){
|
||||||
|
str=json[msg];
|
||||||
|
if(str){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(str){
|
||||||
|
return this.fillInBlanks(str,params);
|
||||||
|
}else{
|
||||||
|
throw new Error(msg+" not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static fillInBlanks(msg:string,params:string[]):string{
|
||||||
|
//thanks to geotale for the regex
|
||||||
|
msg=msg.replace(/{{(.+?)}}/g,
|
||||||
|
(str, match:string) => {
|
||||||
|
const [op,strsSplit]=this.fillInBlanks(match,params).split(":");
|
||||||
|
const [first,...strs]=strsSplit.split("|");
|
||||||
|
switch(op.toUpperCase()){
|
||||||
|
case "PLURAL":{
|
||||||
|
const numb=Number(first);
|
||||||
|
if(numb===0){
|
||||||
|
return strs[strs.length-1];
|
||||||
|
}
|
||||||
|
return strs[Math.min(strs.length-1,numb-1)];
|
||||||
|
}
|
||||||
|
case "GENDER":{
|
||||||
|
if(first==="male"){
|
||||||
|
return strs[0];
|
||||||
|
}else if(first==="female"){
|
||||||
|
return strs[1];
|
||||||
|
}else if(first==="neutral"){
|
||||||
|
if(strs[2]){
|
||||||
|
return strs[2];
|
||||||
|
}else{
|
||||||
|
return strs[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
msg=msg.replace(/\$\d+/g,(str, match:string) => {
|
||||||
|
const number=Number(match);
|
||||||
|
if(params[number-1]){
|
||||||
|
return params[number-1];
|
||||||
|
}else{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
private static async toTranslation(trans:string|{[key:string]:string},lang:string):Promise<{[key:string]:string}>{
|
||||||
|
if(typeof trans==='string'){
|
||||||
|
return this.toTranslation((await (await fetch(trans)).json() as translation)[lang],lang);
|
||||||
|
}else{
|
||||||
|
return trans;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export{I18n};
|
1
src/webpage/icons/plus.svg
Normal file
1
src/webpage/icons/plus.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="#c9c9c9" stroke="red" stroke-linecap="round" stroke-width="24" d="M90 168V12M12 90h156"/></svg>
|
After Width: | Height: | Size: 169 B |
1
src/webpage/icons/x.svg
Normal file
1
src/webpage/icons/x.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="red" d="M90 0A90 90 0 0 0 0 90a90 90 0 0 0 90 90 90 90 0 0 0 90-90A90 90 0 0 0 90 0zM61.7 49.7a12 12 0 0 1 8.5 3.5L90 73l19.8-19.8a12 12 0 0 1 8.5-3.5 12 12 0 0 1 8.5 3.5 12 12 0 0 1 0 17L107 90l19.8 19.8a12 12 0 0 1 0 17 12 12 0 0 1-17 0L90 107l-19.8 19.8a12 12 0 0 1-17 0 12 12 0 0 1 0-17L73 90 53.2 70.2a12 12 0 0 1 0-17 12 12 0 0 1 8.5-3.5z"/></svg>
|
After Width: | Height: | Size: 427 B |
|
@ -11,15 +11,15 @@
|
||||||
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
||||||
<link href="/style.css" rel="stylesheet">
|
<link href="/style.css" rel="stylesheet">
|
||||||
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
||||||
|
<style>body.no-theme,#loading{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme,#loading{background:#9397bd;}}</style>
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="Dark-theme">
|
<body class="no-theme">
|
||||||
<script src="/index.js" type="module"></script>
|
<script src="/index.js" type="module"></script>
|
||||||
|
|
||||||
<div id="loading" class="loading">
|
<div id="loading" class="loading">
|
||||||
<div id="centerdiv">
|
<div class="centeritem">
|
||||||
<img src="/logo.svg" style="width:3in;height:3in;">
|
<img src="/logo.svg" style="width:3in;height:3in;">
|
||||||
<h1>Jank Client is loading</h1>
|
<h1>Jank Client is loading</h1>
|
||||||
<h2 id="load-desc">This shouldn't take long</h2>
|
<h2 id="load-desc">This shouldn't take long</h2>
|
||||||
|
@ -27,50 +27,62 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexltr" id="page">
|
<div class="flexltr" id="page">
|
||||||
<div id="neunence">
|
|
||||||
<div id="servers"></div>
|
<div id="servers"></div>
|
||||||
</div>
|
|
||||||
<div class="flexttb channelflex">
|
<div class="flexttb channelflex">
|
||||||
<div class="servertd" id="servertd">
|
<div class="flexltr header" id="servertd">
|
||||||
<h2 id="serverName">Server Name</h2>
|
<h2 id="serverName" class="ellipsis">Server Name</h2>
|
||||||
</div>
|
</div>
|
||||||
<div id="channels"></div>
|
<div id="channels"></div>
|
||||||
|
<div class="flexttb">
|
||||||
|
<div class="flexltr" id="VoiceBox">
|
||||||
|
<span id="VoiceStatus"></span>
|
||||||
|
</div>
|
||||||
<div class="flexltr" id="userdock">
|
<div class="flexltr" id="userdock">
|
||||||
<div class="flexltr" id="userinfo">
|
<div class="flexltr" id="userinfo">
|
||||||
<img id="userpfp" class="pfp">
|
<img id="userpfp" class="pfp">
|
||||||
|
|
||||||
<div class="userflex">
|
<div class="flexttb userflex">
|
||||||
<p id="username">USERNAME</p>
|
<p id="username">USERNAME</p>
|
||||||
<p id="status">STATUS</p>
|
<p id="status">STATUS</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="user-actions">
|
<div id="user-actions">
|
||||||
<span id="settings" class="svgtheme svg-settings"></span>
|
<span id="settings" class="svgicon svg-settings"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexttb messageflex">
|
</div>
|
||||||
<div class="servertd channelnamediv">
|
<div class="flexttb flexgrow" id="mainarea">
|
||||||
<span id="mobileback" hidden></span>
|
<div class="flexltr header channelnamediv">
|
||||||
|
<label for="maintoggle" id="maintoggleicon">
|
||||||
|
<span class="svgicon svg-category"></span>
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" id="maintoggle">
|
||||||
|
<span class="flexltr">
|
||||||
<span id="channelname">Channel name</span>
|
<span id="channelname">Channel name</span>
|
||||||
<span id="channelTopic" hidden>Channel topic</span>
|
<span id="channelTopic" class="ellipsis" hidden>Channel topic</span>
|
||||||
|
</span>
|
||||||
|
<label for="memberlisttoggle" id="memberlisttoggleicon">
|
||||||
|
<span class="svgicon svg-channel"></span>
|
||||||
|
</label>
|
||||||
|
<input type="checkbox" id="memberlisttoggle" checked>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexltr">
|
<div class="flexltr flexgrow">
|
||||||
<div class="flexttb">
|
<div class="flexttb flexgrow">
|
||||||
<div id="channelw">
|
<div id="channelw" class="flexltr">
|
||||||
<div id="loadingdiv">
|
<div id="loadingdiv">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="pasteimage"></div>
|
<div id="pasteimage" class="flexltr"></div>
|
||||||
<div id="replybox" class="hideReplyBox"></div>
|
<div id="replybox" class="hideReplyBox"></div>
|
||||||
<div id="typediv">
|
<div id="typediv">
|
||||||
<div id="realbox">
|
<div id="realbox">
|
||||||
<div id="typebox" contentEditable="true"></div>
|
<div id="typebox" contentEditable="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="typing" class="hidden">
|
<div id="typing" class="hidden flexltr">
|
||||||
<p id="typingtext">typing</p>
|
<p id="typingtext">typing</p>
|
||||||
<div class="loading-indicator">
|
<div class="flexltr loading-indicator">
|
||||||
<div class="dot"></div>
|
<div class="dot"></div>
|
||||||
<div class="dot"></div>
|
<div class="dot"></div>
|
||||||
<div class="dot"></div>
|
<div class="dot"></div>
|
||||||
|
|
|
@ -22,7 +22,7 @@ import{ File }from"./file.js";
|
||||||
|
|
||||||
function showAccountSwitcher(): void{
|
function showAccountSwitcher(): void{
|
||||||
const table = document.createElement("div");
|
const table = document.createElement("div");
|
||||||
table.classList.add("accountSwitcher");
|
table.classList.add("flexttb","accountSwitcher");
|
||||||
|
|
||||||
for(const user of Object.values(users.users)){
|
for(const user of Object.values(users.users)){
|
||||||
const specialUser = user as Specialuser;
|
const specialUser = user as Specialuser;
|
||||||
|
@ -235,25 +235,12 @@ import{ File }from"./file.js";
|
||||||
userSettings;
|
userSettings;
|
||||||
|
|
||||||
if(mobile){
|
if(mobile){
|
||||||
const channelWrapper = document.getElementById(
|
const channelWrapper = document.getElementById("channelw") as HTMLDivElement;
|
||||||
"channelw"
|
|
||||||
) as HTMLDivElement;
|
|
||||||
channelWrapper.onclick = ()=>{
|
channelWrapper.onclick = ()=>{
|
||||||
(
|
const toggle = document.getElementById("maintoggle") as HTMLInputElement;
|
||||||
document.getElementById("channels")!.parentNode as HTMLElement
|
toggle.checked = true;
|
||||||
).classList.add("collapse");
|
|
||||||
document.getElementById("servertd")!.classList.add("collapse");
|
|
||||||
document.getElementById("servers")!.classList.add("collapse");
|
|
||||||
};
|
|
||||||
|
|
||||||
const mobileBack = document.getElementById("mobileback") as HTMLDivElement;
|
|
||||||
mobileBack.textContent = "#";
|
|
||||||
mobileBack.onclick = ()=>{
|
|
||||||
(
|
|
||||||
document.getElementById("channels")!.parentNode as HTMLElement
|
|
||||||
).classList.remove("collapse");
|
|
||||||
document.getElementById("servertd")!.classList.remove("collapse");
|
|
||||||
document.getElementById("servers")!.classList.remove("collapse");
|
|
||||||
};
|
};
|
||||||
|
const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement;
|
||||||
|
memberListToggle.checked = false;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -39,7 +39,7 @@ offset: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const scroll = document.createElement("div");
|
const scroll = document.createElement("div");
|
||||||
scroll.classList.add("flexttb", "scroller");
|
scroll.classList.add("scroller");
|
||||||
this.div = scroll;
|
this.div = scroll;
|
||||||
|
|
||||||
this.div.addEventListener("scroll", ()=>{
|
this.div.addEventListener("scroll", ()=>{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<body class="Dark-theme">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -10,7 +11,9 @@
|
||||||
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
||||||
<link href="/style.css" rel="stylesheet">
|
<link href="/style.css" rel="stylesheet">
|
||||||
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
||||||
|
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body class="no-theme">
|
||||||
<div>
|
<div>
|
||||||
<div id="invitebody">
|
<div id="invitebody">
|
||||||
<div id="inviteimg"></div>
|
<div id="inviteimg"></div>
|
||||||
|
@ -20,4 +23,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/invite.js"></script>
|
<script type="module" src="/invite.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
|
@ -136,7 +136,7 @@ document.getElementById("inviteimg")!.append(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.append(td);
|
table.append(td);
|
||||||
table.classList.add("accountSwitcher");
|
table.classList.add("flexttb","accountSwitcher");
|
||||||
console.log(table);
|
console.log(table);
|
||||||
document.body.append(table);
|
document.body.append(table);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,120 +1,120 @@
|
||||||
type readyjson = {
|
type readyjson = {
|
||||||
op: 0;
|
op: 0;
|
||||||
t: "READY";
|
t: "READY";
|
||||||
s: number;
|
s: number;
|
||||||
d: {
|
d: {
|
||||||
v: number;
|
v: number;
|
||||||
user: mainuserjson;
|
user: mainuserjson;
|
||||||
user_settings: {
|
user_settings: {
|
||||||
index: number;
|
index: number;
|
||||||
afk_timeout: number;
|
afk_timeout: number;
|
||||||
allow_accessibility_detection: boolean;
|
allow_accessibility_detection: boolean;
|
||||||
animate_emoji: boolean;
|
animate_emoji: boolean;
|
||||||
animate_stickers: number;
|
animate_stickers: number;
|
||||||
contact_sync_enabled: boolean;
|
contact_sync_enabled: boolean;
|
||||||
convert_emoticons: boolean;
|
convert_emoticons: boolean;
|
||||||
custom_status: string;
|
custom_status: string;
|
||||||
default_guilds_restricted: boolean;
|
default_guilds_restricted: boolean;
|
||||||
detect_platform_accounts: boolean;
|
detect_platform_accounts: boolean;
|
||||||
developer_mode: boolean;
|
developer_mode: boolean;
|
||||||
disable_games_tab: boolean;
|
disable_games_tab: boolean;
|
||||||
enable_tts_command: boolean;
|
enable_tts_command: boolean;
|
||||||
explicit_content_filter: 0;
|
explicit_content_filter: 0;
|
||||||
friend_discovery_flags: 0;
|
friend_discovery_flags: 0;
|
||||||
friend_source_flags: {
|
friend_source_flags: {
|
||||||
all: boolean;
|
all: boolean;
|
||||||
}; //might be missing things here
|
}; //might be missing things here
|
||||||
gateway_connected: boolean;
|
gateway_connected: boolean;
|
||||||
gif_auto_play: boolean;
|
gif_auto_play: boolean;
|
||||||
guild_folders: []; //need an example of this not empty
|
guild_folders: []; //need an example of this not empty
|
||||||
guild_positions: []; //need an example of this not empty
|
guild_positions: []; //need an example of this not empty
|
||||||
inline_attachment_media: boolean;
|
inline_attachment_media: boolean;
|
||||||
inline_embed_media: boolean;
|
inline_embed_media: boolean;
|
||||||
locale: string;
|
locale: string;
|
||||||
message_display_compact: boolean;
|
message_display_compact: boolean;
|
||||||
native_phone_integration_enabled: boolean;
|
native_phone_integration_enabled: boolean;
|
||||||
render_embeds: boolean;
|
render_embeds: boolean;
|
||||||
render_reactions: boolean;
|
render_reactions: boolean;
|
||||||
restricted_guilds: []; //need an example of this not empty
|
restricted_guilds: []; //need an example of this not empty
|
||||||
show_current_game: boolean;
|
show_current_game: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
stream_notifications_enabled: boolean;
|
stream_notifications_enabled: boolean;
|
||||||
theme: string;
|
theme: string;
|
||||||
timezone_offset: number;
|
timezone_offset: number;
|
||||||
view_nsfw_guilds: boolean;
|
view_nsfw_guilds: boolean;
|
||||||
};
|
};
|
||||||
guilds: guildjson[];
|
guilds: guildjson[];
|
||||||
relationships: {
|
relationships: {
|
||||||
id: string;
|
id: string;
|
||||||
type: 0 | 1 | 2 | 3 | 4;
|
type: 0 | 1 | 2 | 3 | 4;
|
||||||
nickname: string | null;
|
nickname: string | null;
|
||||||
user: userjson;
|
user: userjson;
|
||||||
}[];
|
}[];
|
||||||
read_state: {
|
read_state: {
|
||||||
entries: {
|
entries: {
|
||||||
id: string;
|
id: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
last_message_id: string;
|
last_message_id: string;
|
||||||
last_pin_timestamp: string;
|
last_pin_timestamp: string;
|
||||||
mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware
|
mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware
|
||||||
}[];
|
}[];
|
||||||
partial: boolean;
|
partial: boolean;
|
||||||
version: number;
|
version: number;
|
||||||
};
|
};
|
||||||
user_guild_settings: {
|
user_guild_settings: {
|
||||||
entries: {
|
entries: {
|
||||||
channel_overrides: unknown[]; //will have to find example
|
channel_overrides: unknown[]; //will have to find example
|
||||||
message_notifications: number;
|
message_notifications: number;
|
||||||
flags: number;
|
flags: number;
|
||||||
hide_muted_channels: boolean;
|
hide_muted_channels: boolean;
|
||||||
mobile_push: boolean;
|
mobile_push: boolean;
|
||||||
mute_config: null;
|
mute_config: null;
|
||||||
mute_scheduled_events: boolean;
|
mute_scheduled_events: boolean;
|
||||||
muted: boolean;
|
muted: boolean;
|
||||||
notify_highlights: number;
|
notify_highlights: number;
|
||||||
suppress_everyone: boolean;
|
suppress_everyone: boolean;
|
||||||
suppress_roles: boolean;
|
suppress_roles: boolean;
|
||||||
version: number;
|
version: number;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
}[];
|
}[];
|
||||||
partial: boolean;
|
partial: boolean;
|
||||||
version: number;
|
version: number;
|
||||||
};
|
};
|
||||||
private_channels: dirrectjson[];
|
private_channels: dirrectjson[];
|
||||||
session_id: string;
|
session_id: string;
|
||||||
country_code: string;
|
country_code: string;
|
||||||
users: userjson[];
|
users: userjson[];
|
||||||
merged_members: [memberjson][];
|
merged_members: [memberjson][];
|
||||||
sessions: {
|
sessions: {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
activities: []; //will need to find example of this
|
activities: []; //will need to find example of this
|
||||||
client_info: {
|
client_info: {
|
||||||
version: number;
|
version: number;
|
||||||
};
|
};
|
||||||
session_id: string;
|
session_id: string;
|
||||||
status: string;
|
status: string;
|
||||||
}[];
|
}[];
|
||||||
resume_gateway_url: string;
|
resume_gateway_url: string;
|
||||||
consents: {
|
consents: {
|
||||||
personalization: {
|
personalization: {
|
||||||
consented: boolean;
|
consented: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
experiments: []; //not sure if I need to do this :P
|
experiments: []; //not sure if I need to do this :P
|
||||||
guild_join_requests: []; //need to get examples
|
guild_join_requests: []; //need to get examples
|
||||||
connected_accounts: []; //need to get examples
|
connected_accounts: []; //need to get examples
|
||||||
guild_experiments: []; //need to get examples
|
guild_experiments: []; //need to get examples
|
||||||
geo_ordered_rtc_regions: []; //need to get examples
|
geo_ordered_rtc_regions: []; //need to get examples
|
||||||
api_code_version: number;
|
api_code_version: number;
|
||||||
friend_suggestion_count: number;
|
friend_suggestion_count: number;
|
||||||
analytics_token: string;
|
analytics_token: string;
|
||||||
tutorial: boolean;
|
tutorial: boolean;
|
||||||
session_type: string;
|
session_type: string;
|
||||||
auth_session_id_hash: string;
|
auth_session_id_hash: string;
|
||||||
notification_settings: {
|
notification_settings: {
|
||||||
flags: number;
|
flags: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
type mainuserjson = userjson & {
|
type mainuserjson = userjson & {
|
||||||
flags: number;
|
flags: number;
|
||||||
|
@ -129,20 +129,20 @@ type mainuserjson = userjson & {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
};
|
};
|
||||||
type userjson = {
|
type userjson = {
|
||||||
username: string;
|
username: string;
|
||||||
discriminator: string;
|
discriminator: string;
|
||||||
id: string;
|
id: string;
|
||||||
public_flags: number;
|
public_flags: number;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
accent_color: number;
|
accent_color: number;
|
||||||
banner?: string;
|
banner?: string;
|
||||||
bio: string;
|
bio: string;
|
||||||
bot: boolean;
|
bot: boolean;
|
||||||
premium_since: string;
|
premium_since: string;
|
||||||
premium_type: number;
|
premium_type: number;
|
||||||
theme_colors: string;
|
theme_colors: string;
|
||||||
pronouns: string;
|
pronouns: string;
|
||||||
badge_ids: string[];
|
badge_ids: string[];
|
||||||
};
|
};
|
||||||
type memberjson = {
|
type memberjson = {
|
||||||
index?: number;
|
index?: number;
|
||||||
|
@ -163,9 +163,9 @@ type memberjson = {
|
||||||
last_message_id?: boolean; //What???
|
last_message_id?: boolean; //What???
|
||||||
};
|
};
|
||||||
type emojijson = {
|
type emojijson = {
|
||||||
name: string;
|
name: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
animated?: boolean;
|
animated?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type guildjson = {
|
type guildjson = {
|
||||||
|
@ -225,37 +225,37 @@ type guildjson = {
|
||||||
joined_at: string;
|
joined_at: string;
|
||||||
};
|
};
|
||||||
type startTypingjson = {
|
type startTypingjson = {
|
||||||
d: {
|
d: {
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
member?: memberjson;
|
member?: memberjson;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
type channeljson = {
|
type channeljson = {
|
||||||
id: string;
|
id: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
type: number;
|
type: number;
|
||||||
last_message_id: string;
|
last_message_id: string;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
parent_id: string;
|
parent_id: string;
|
||||||
last_pin_timestamp: string;
|
last_pin_timestamp: string;
|
||||||
default_auto_archive_duration: number;
|
default_auto_archive_duration: number;
|
||||||
permission_overwrites: {
|
permission_overwrites: {
|
||||||
id: string;
|
id: string;
|
||||||
allow: string;
|
allow: string;
|
||||||
deny: string;
|
deny: string;
|
||||||
}[];
|
}[];
|
||||||
video_quality_mode: null;
|
video_quality_mode: null;
|
||||||
nsfw: boolean;
|
nsfw: boolean;
|
||||||
topic: string;
|
topic: string;
|
||||||
retention_policy_id: string;
|
retention_policy_id: string;
|
||||||
flags: number;
|
flags: number;
|
||||||
default_thread_rate_limit_per_user: number;
|
default_thread_rate_limit_per_user: number;
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
type rolesjson = {
|
type rolesjson = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -272,127 +272,136 @@ type rolesjson = {
|
||||||
flags: number;
|
flags: number;
|
||||||
};
|
};
|
||||||
type dirrectjson = {
|
type dirrectjson = {
|
||||||
id: string;
|
id: string;
|
||||||
flags: number;
|
flags: number;
|
||||||
last_message_id: string;
|
last_message_id: string;
|
||||||
type: number;
|
type: number;
|
||||||
recipients: userjson[];
|
recipients: userjson[];
|
||||||
is_spam: boolean;
|
is_spam: boolean;
|
||||||
};
|
};
|
||||||
type messagejson = {
|
type messagejson = {
|
||||||
id: string;
|
id: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
author: userjson;
|
author: userjson;
|
||||||
member?: memberjson;
|
member?: memberjson;
|
||||||
content: string;
|
content: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
edited_timestamp: string;
|
edited_timestamp: string;
|
||||||
tts: boolean;
|
tts: boolean;
|
||||||
mention_everyone: boolean;
|
mention_everyone: boolean;
|
||||||
mentions: []; //need examples to fix
|
mentions: []; //need examples to fix
|
||||||
mention_roles: []; //need examples to fix
|
mention_roles: []; //need examples to fix
|
||||||
attachments: filejson[];
|
attachments: filejson[];
|
||||||
embeds: embedjson[];
|
embeds: embedjson[];
|
||||||
reactions: {
|
reactions: {
|
||||||
count: number;
|
count: number;
|
||||||
emoji: emojijson; //very likely needs expanding
|
emoji: emojijson; //very likely needs expanding
|
||||||
me: boolean;
|
me: boolean;
|
||||||
}[];
|
}[];
|
||||||
nonce: string;
|
nonce: string;
|
||||||
pinned: boolean;
|
pinned: boolean;
|
||||||
type: number;
|
type: number;
|
||||||
};
|
};
|
||||||
type filejson = {
|
type filejson = {
|
||||||
id: string;
|
id: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
content_type: string;
|
content_type: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
proxy_url: string | undefined;
|
proxy_url: string | undefined;
|
||||||
url: string;
|
url: string;
|
||||||
size: number;
|
size: number;
|
||||||
};
|
};
|
||||||
type embedjson = {
|
type embedjson = {
|
||||||
type: string | null;
|
type: string | null;
|
||||||
color?: number;
|
color?: number;
|
||||||
author: {
|
author: {
|
||||||
icon_url?: string;
|
icon_url?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
title?: string;
|
title?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
fields?: {
|
fields?: {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
inline: boolean;
|
inline: boolean;
|
||||||
}[];
|
}[];
|
||||||
footer?: {
|
footer?: {
|
||||||
icon_url?: string;
|
icon_url?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
thumbnail?: string;
|
thumbnail?: string;
|
||||||
};
|
};
|
||||||
timestamp?: string;
|
timestamp?: string;
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
proxy_url: string;
|
proxy_url: string;
|
||||||
url: string;
|
url: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
provider: {
|
provider: {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
video?: {
|
video?: {
|
||||||
url: string;
|
url: string;
|
||||||
width?: number | null;
|
width?: number | null;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
proxy_url?: string;
|
proxy_url?: string;
|
||||||
};
|
};
|
||||||
invite?: {
|
invite?: {
|
||||||
url: string;
|
url: string;
|
||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
type invitejson = {
|
type invitejson = {
|
||||||
code: string;
|
code: string;
|
||||||
temporary: boolean;
|
temporary: boolean;
|
||||||
uses: number;
|
uses: number;
|
||||||
max_use: number;
|
max_use: number;
|
||||||
max_age: number;
|
max_age: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
expires_at: string;
|
expires_at: string;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
inviter_id: string;
|
inviter_id: string;
|
||||||
target_user_id: string | null;
|
target_user_id: string | null;
|
||||||
target_user_type: string | null;
|
target_user_type: string | null;
|
||||||
vanity_url: string | null;
|
vanity_url: string | null;
|
||||||
flags: number;
|
flags: number;
|
||||||
guild: guildjson["properties"];
|
guild: guildjson["properties"];
|
||||||
channel: channeljson;
|
channel: channeljson;
|
||||||
inviter: userjson;
|
inviter: userjson;
|
||||||
};
|
};
|
||||||
type presencejson = {
|
type presencejson = {
|
||||||
status: string;
|
status: string;
|
||||||
since: number | null;
|
since: number | null;
|
||||||
activities: any[]; //bit more complicated but not now
|
activities: any[]; //bit more complicated but not now
|
||||||
afk: boolean;
|
afk: boolean;
|
||||||
user?: userjson;
|
user?: userjson;
|
||||||
};
|
};
|
||||||
type messageCreateJson = {
|
type messageCreateJson = {
|
||||||
op: 0;
|
op: 0;
|
||||||
d: {
|
d: {
|
||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
channel_id?: string;
|
channel_id?: string;
|
||||||
} & messagejson;
|
} & messagejson;
|
||||||
s: number;
|
s: number;
|
||||||
t: "MESSAGE_CREATE";
|
t: "MESSAGE_CREATE";
|
||||||
};
|
};
|
||||||
|
type roleCreate={
|
||||||
|
op: 0,
|
||||||
|
t: "GUILD_ROLE_CREATE",
|
||||||
|
d: {
|
||||||
|
guild_id: string,
|
||||||
|
role: rolesjson
|
||||||
|
},
|
||||||
|
s: 6
|
||||||
|
}
|
||||||
type wsjson =
|
type wsjson =
|
||||||
| {
|
roleCreate | {
|
||||||
op: 0;
|
op: 0;
|
||||||
d: any;
|
d: any;
|
||||||
s: number;
|
s: number;
|
||||||
|
@ -408,79 +417,130 @@ type wsjson =
|
||||||
| "MESSAGE_REACTION_REMOVE_EMOJI";
|
| "MESSAGE_REACTION_REMOVE_EMOJI";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: 0;
|
op: 0;
|
||||||
t: "GUILD_MEMBERS_CHUNK";
|
t: "GUILD_MEMBERS_CHUNK";
|
||||||
d: memberChunk;
|
d: memberChunk;
|
||||||
s: number;
|
s: number;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: 0;
|
op: 0;
|
||||||
d: {
|
d: {
|
||||||
id: string;
|
id: string;
|
||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
};
|
};
|
||||||
s: number;
|
s: number;
|
||||||
t: "MESSAGE_DELETE";
|
t: "MESSAGE_DELETE";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: 0;
|
op: 0;
|
||||||
d: {
|
d: {
|
||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
} & messagejson;
|
} & messagejson;
|
||||||
s: number;
|
s: number;
|
||||||
t: "MESSAGE_UPDATE";
|
t: "MESSAGE_UPDATE";
|
||||||
}
|
}
|
||||||
| messageCreateJson
|
| messageCreateJson
|
||||||
| readyjson
|
| readyjson
|
||||||
| {
|
| {
|
||||||
op: 11;
|
op: 11;
|
||||||
s: undefined;
|
s: undefined;
|
||||||
d: {};
|
d: {};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: 10;
|
op: 10;
|
||||||
s: undefined;
|
s: undefined;
|
||||||
d: {
|
d: {
|
||||||
heartbeat_interval: number;
|
heartbeat_interval: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: 0;
|
op: 0;
|
||||||
t: "MESSAGE_REACTION_ADD";
|
t: "MESSAGE_REACTION_ADD";
|
||||||
d: {
|
d: {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
message_id: string;
|
message_id: string;
|
||||||
guild_id?: string;
|
guild_id?: string;
|
||||||
emoji: emojijson;
|
emoji: emojijson;
|
||||||
member?: memberjson;
|
member?: memberjson;
|
||||||
};
|
};
|
||||||
s: number;
|
s: number;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
op: 0;
|
op: 0;
|
||||||
t: "MESSAGE_REACTION_REMOVE";
|
t: "MESSAGE_REACTION_REMOVE";
|
||||||
d: {
|
d: {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
message_id: string;
|
message_id: string;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
emoji: emojijson;
|
emoji: emojijson;
|
||||||
};
|
};
|
||||||
s: 3;
|
s: number;
|
||||||
}|memberlistupdatejson;
|
}|{
|
||||||
type memberChunk = {
|
op: 0,
|
||||||
guild_id: string;
|
t: "GUILD_ROLE_UPDATE",
|
||||||
nonce: string;
|
d: {
|
||||||
members: memberjson[];
|
guild_id: string,
|
||||||
presences: presencejson[];
|
role: rolesjson
|
||||||
chunk_index: number;
|
},
|
||||||
chunk_count: number;
|
"s": number
|
||||||
not_found: string[];
|
}|{
|
||||||
};
|
op: 0,
|
||||||
|
t: "GUILD_ROLE_DELETE",
|
||||||
|
d: {
|
||||||
|
guild_id: string,
|
||||||
|
role_id: string
|
||||||
|
},
|
||||||
|
s:number
|
||||||
|
}|{
|
||||||
|
op: 0,
|
||||||
|
t: "GUILD_MEMBER_UPDATE",
|
||||||
|
d: memberjson,
|
||||||
|
"s": 3
|
||||||
|
}|memberlistupdatejson|voiceupdate|voiceserverupdate;
|
||||||
|
|
||||||
|
|
||||||
|
type memberChunk = {
|
||||||
|
guild_id: string;
|
||||||
|
nonce: string;
|
||||||
|
members: memberjson[];
|
||||||
|
presences: presencejson[];
|
||||||
|
chunk_index: number;
|
||||||
|
chunk_count: number;
|
||||||
|
not_found: string[];
|
||||||
|
};
|
||||||
|
type voiceupdate={
|
||||||
|
op: 0,
|
||||||
|
t: "VOICE_STATE_UPDATE",
|
||||||
|
d: {
|
||||||
|
guild_id: string,
|
||||||
|
channel_id: string,
|
||||||
|
user_id: string,
|
||||||
|
member: memberjson,
|
||||||
|
session_id: string,
|
||||||
|
token: string,
|
||||||
|
deaf: boolean,
|
||||||
|
mute: boolean,
|
||||||
|
self_deaf: boolean,
|
||||||
|
self_mute: boolean,
|
||||||
|
self_video: boolean,
|
||||||
|
suppress: boolean
|
||||||
|
},
|
||||||
|
s: number
|
||||||
|
};
|
||||||
|
type voiceserverupdate={
|
||||||
|
op: 0,
|
||||||
|
t: "VOICE_SERVER_UPDATE",
|
||||||
|
d: {
|
||||||
|
token: string,
|
||||||
|
guild_id: string,
|
||||||
|
endpoint: string
|
||||||
|
},
|
||||||
|
s: 6
|
||||||
|
};
|
||||||
type memberlistupdatejson={
|
type memberlistupdatejson={
|
||||||
op: 0,
|
op: 0,
|
||||||
s: number,
|
s: number,
|
||||||
|
@ -513,6 +573,72 @@ type memberlistupdatejson={
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type webRTCSocket= {
|
||||||
|
op: 8,
|
||||||
|
d: {
|
||||||
|
heartbeat_interval: number
|
||||||
|
}
|
||||||
|
}|{
|
||||||
|
op:6,
|
||||||
|
d:{t: number}
|
||||||
|
}|{
|
||||||
|
op: 2,
|
||||||
|
d: {
|
||||||
|
ssrc: number,
|
||||||
|
"streams": {
|
||||||
|
type: "video",//probally more options, but idk
|
||||||
|
rid: string,
|
||||||
|
quality: number,
|
||||||
|
ssrc: number,
|
||||||
|
rtx_ssrc:number
|
||||||
|
}[],
|
||||||
|
ip: number,
|
||||||
|
port: number,
|
||||||
|
"modes": [],//no clue
|
||||||
|
"experiments": []//no clue
|
||||||
|
}
|
||||||
|
}|sdpback|opRTC12|{
|
||||||
|
op: 5,
|
||||||
|
d: {
|
||||||
|
user_id: string,
|
||||||
|
speaking: 0,
|
||||||
|
ssrc: 940464811
|
||||||
|
}
|
||||||
|
};
|
||||||
|
type sdpback={
|
||||||
|
op: 4,
|
||||||
|
d: {
|
||||||
|
audioCodec: string,
|
||||||
|
videoCodec: string,
|
||||||
|
media_session_id: string,
|
||||||
|
sdp: string
|
||||||
|
}
|
||||||
|
};
|
||||||
|
type opRTC12={
|
||||||
|
op: 12,
|
||||||
|
d: {
|
||||||
|
user_id: string,
|
||||||
|
audio_ssrc: number,
|
||||||
|
video_ssrc: number,
|
||||||
|
streams: [
|
||||||
|
{
|
||||||
|
type: "video",
|
||||||
|
rid: "100",
|
||||||
|
ssrc: number,
|
||||||
|
active: boolean,
|
||||||
|
quality: 100,
|
||||||
|
rtx_ssrc: number,
|
||||||
|
max_bitrate: 2500000,
|
||||||
|
max_framerate: number,
|
||||||
|
max_resolution: {
|
||||||
|
type: "fixed",
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
export{
|
export{
|
||||||
readyjson,
|
readyjson,
|
||||||
dirrectjson,
|
dirrectjson,
|
||||||
|
@ -532,5 +658,10 @@ export{
|
||||||
messageCreateJson,
|
messageCreateJson,
|
||||||
memberChunk,
|
memberChunk,
|
||||||
invitejson,
|
invitejson,
|
||||||
memberlistupdatejson
|
memberlistupdatejson,
|
||||||
|
voiceupdate,
|
||||||
|
voiceserverupdate,
|
||||||
|
webRTCSocket,
|
||||||
|
sdpback,
|
||||||
|
opRTC12
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,35 +1,23 @@
|
||||||
import{ Guild }from"./guild.js";
|
import{ Guild }from"./guild.js";
|
||||||
import{ Channel }from"./channel.js";
|
import{ Channel }from"./channel.js";
|
||||||
import{ Direct }from"./direct.js";
|
import{ Direct }from"./direct.js";
|
||||||
import{ Voice }from"./audio.js";
|
import{ AVoice }from"./audio.js";
|
||||||
import{ User }from"./user.js";
|
import{ User }from"./user.js";
|
||||||
import{ Dialog }from"./dialog.js";
|
import{ Dialog }from"./dialog.js";
|
||||||
import{ getapiurls, getBulkInfo, setTheme, Specialuser }from"./login.js";
|
import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js";
|
||||||
import{
|
import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js";
|
||||||
channeljson,
|
|
||||||
guildjson,
|
|
||||||
mainuserjson,
|
|
||||||
memberjson,
|
|
||||||
memberlistupdatejson,
|
|
||||||
messageCreateJson,
|
|
||||||
presencejson,
|
|
||||||
readyjson,
|
|
||||||
startTypingjson,
|
|
||||||
wsjson,
|
|
||||||
}from"./jsontypes.js";
|
|
||||||
import{ Member }from"./member.js";
|
import{ Member }from"./member.js";
|
||||||
import{ Form, FormError, Options, Settings }from"./settings.js";
|
import{ Form, FormError, Options, Settings }from"./settings.js";
|
||||||
import{ MarkDown }from"./markdown.js";
|
import{ MarkDown }from"./markdown.js";
|
||||||
import { Bot } from "./bot.js";
|
import { Bot } from "./bot.js";
|
||||||
import { Role } from "./role.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]);
|
const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]);
|
||||||
|
|
||||||
class Localuser{
|
class Localuser{
|
||||||
badges: Map<
|
badges: Map<string,{ id: string; description: string; icon: string; link: string }> = new Map();
|
||||||
string,
|
|
||||||
{ id: string; description: string; icon: string; link: string }
|
|
||||||
> = new Map();
|
|
||||||
lastSequence: number | null = null;
|
lastSequence: number | null = null;
|
||||||
token!: string;
|
token!: string;
|
||||||
userinfo!: Specialuser;
|
userinfo!: Specialuser;
|
||||||
|
@ -52,6 +40,7 @@ class Localuser{
|
||||||
errorBackoff = 0;
|
errorBackoff = 0;
|
||||||
channelids: Map<string, Channel> = new Map();
|
channelids: Map<string, Channel> = new Map();
|
||||||
readonly userMap: Map<string, User> = new Map();
|
readonly userMap: Map<string, User> = new Map();
|
||||||
|
voiceFactory?:VoiceFactory;
|
||||||
instancePing = {
|
instancePing = {
|
||||||
name: "Unknown",
|
name: "Unknown",
|
||||||
};
|
};
|
||||||
|
@ -76,26 +65,33 @@ class Localuser{
|
||||||
"Content-type": "application/json; charset=UTF-8",
|
"Content-type": "application/json; charset=UTF-8",
|
||||||
Authorization: this.userinfo.token,
|
Authorization: this.userinfo.token,
|
||||||
};
|
};
|
||||||
|
I18n.create("/translations/en.json","en")
|
||||||
}
|
}
|
||||||
gottenReady(ready: readyjson): void{
|
async gottenReady(ready: readyjson): Promise<void>{
|
||||||
|
await I18n.done;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
this.ready = ready;
|
this.ready = ready;
|
||||||
this.guilds = [];
|
this.guilds = [];
|
||||||
this.guildids = new Map();
|
this.guildids = new Map();
|
||||||
this.user = new User(ready.d.user, this);
|
this.user = new User(ready.d.user, this);
|
||||||
this.user.setstatus("online");
|
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.mfa_enabled = ready.d.user.mfa_enabled as boolean;
|
||||||
this.userinfo.username = this.user.username;
|
this.userinfo.username = this.user.username;
|
||||||
|
this.userinfo.id = this.user.id;
|
||||||
this.userinfo.pfpsrc = this.user.getpfpsrc();
|
this.userinfo.pfpsrc = this.user.getpfpsrc();
|
||||||
this.status = this.ready.d.user_settings.status;
|
this.status = this.ready.d.user_settings.status;
|
||||||
this.channelfocus = undefined;
|
this.channelfocus = undefined;
|
||||||
this.lookingguild = undefined;
|
this.lookingguild = undefined;
|
||||||
this.guildhtml = new Map();
|
this.guildhtml = new Map();
|
||||||
const members: { [key: string]: memberjson } = {};
|
const members: { [key: string]: memberjson } = {};
|
||||||
|
if(ready.d.merged_members){
|
||||||
for(const thing of ready.d.merged_members){
|
for(const thing of ready.d.merged_members){
|
||||||
members[thing[0].guild_id] = thing[0];
|
members[thing[0].guild_id] = thing[0];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for(const thing of ready.d.guilds){
|
for(const thing of ready.d.guilds){
|
||||||
const temp = new Guild(thing, this, members[thing.id]);
|
const temp = new Guild(thing, this, members[thing.id]);
|
||||||
this.guilds.push(temp);
|
this.guilds.push(temp);
|
||||||
|
@ -127,6 +123,7 @@ class Localuser{
|
||||||
|
|
||||||
this.pingEndpoint();
|
this.pingEndpoint();
|
||||||
this.userinfo.updateLocal();
|
this.userinfo.updateLocal();
|
||||||
|
|
||||||
}
|
}
|
||||||
outoffocus(): void{
|
outoffocus(): void{
|
||||||
const servers = document.getElementById("servers") as HTMLDivElement;
|
const servers = document.getElementById("servers") as HTMLDivElement;
|
||||||
|
@ -205,10 +202,10 @@ class Localuser{
|
||||||
try{
|
try{
|
||||||
const temp = JSON.parse(build);
|
const temp = JSON.parse(build);
|
||||||
build = "";
|
build = "";
|
||||||
|
await this.handleEvent(temp);
|
||||||
if(temp.op === 0 && temp.t === "READY"){
|
if(temp.op === 0 && temp.t === "READY"){
|
||||||
returny();
|
returny();
|
||||||
}
|
}
|
||||||
await this.handleEvent(temp);
|
|
||||||
}catch{}
|
}catch{}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -249,10 +246,11 @@ class Localuser{
|
||||||
}else{
|
}else{
|
||||||
temp = JSON.parse(event.data);
|
temp = JSON.parse(event.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.handleEvent(temp as readyjson);
|
||||||
if(temp.op === 0 && temp.t === "READY"){
|
if(temp.op === 0 && temp.t === "READY"){
|
||||||
returny();
|
returny();
|
||||||
}
|
}
|
||||||
await this.handleEvent(temp as readyjson);
|
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}finally{
|
}finally{
|
||||||
|
@ -372,7 +370,7 @@ class Localuser{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case"READY":
|
case"READY":
|
||||||
this.gottenReady(temp as readyjson);
|
await this.gottenReady(temp as readyjson);
|
||||||
break;
|
break;
|
||||||
case"MESSAGE_UPDATE": {
|
case"MESSAGE_UPDATE": {
|
||||||
temp.d.guild_id ??= "@me";
|
temp.d.guild_id ??= "@me";
|
||||||
|
@ -486,8 +484,45 @@ class Localuser{
|
||||||
this.memberListUpdate(temp)
|
this.memberListUpdate(temp)
|
||||||
break;
|
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){
|
}else if(temp.op === 10){
|
||||||
if(!this.ws)return;
|
if(!this.ws)return;
|
||||||
console.log("heartbeat down");
|
console.log("heartbeat down");
|
||||||
|
@ -501,6 +536,30 @@ class Localuser{
|
||||||
}, this.heartbeat_interval);
|
}, 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;
|
heartbeat_interval: number = 0;
|
||||||
updateChannel(json: channeljson): void{
|
updateChannel(json: channeljson): void{
|
||||||
const guild = this.guildids.get(json.guild_id);
|
const guild = this.guildids.get(json.guild_id);
|
||||||
|
@ -603,11 +662,12 @@ class Localuser{
|
||||||
const memberdiv=document.createElement("div");
|
const memberdiv=document.createElement("div");
|
||||||
const pfp=await member.user.buildstatuspfp();
|
const pfp=await member.user.buildstatuspfp();
|
||||||
const username=document.createElement("span");
|
const username=document.createElement("span");
|
||||||
|
username.classList.add("ellipsis");
|
||||||
username.textContent=member.name;
|
username.textContent=member.name;
|
||||||
member.bind(username)
|
member.bind(username)
|
||||||
member.user.bind(memberdiv,member.guild,false);
|
member.user.bind(memberdiv,member.guild,false);
|
||||||
memberdiv.append(pfp,username);
|
memberdiv.append(pfp,username);
|
||||||
memberdiv.classList.add("flexltr");
|
memberdiv.classList.add("flexltr","liststyle");
|
||||||
membershtml.append(memberdiv);
|
membershtml.append(memberdiv);
|
||||||
}
|
}
|
||||||
category.append(membershtml);
|
category.append(membershtml);
|
||||||
|
@ -722,7 +782,7 @@ class Localuser{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("home", "servericon");
|
div.classList.add("home", "servericon");
|
||||||
|
|
||||||
home.classList.add("svgtheme", "svgicon", "svg-home");
|
home.classList.add("svgicon", "svg-home");
|
||||||
home.all = this.guildids.get("@me");
|
home.all = this.guildids.get("@me");
|
||||||
(this.guildids.get("@me") as Guild).html = outdiv;
|
(this.guildids.get("@me") as Guild).html = outdiv;
|
||||||
const unread = document.createElement("div");
|
const unread = document.createElement("div");
|
||||||
|
@ -760,19 +820,17 @@ class Localuser{
|
||||||
br.id = "bottomseparator";
|
br.id = "bottomseparator";
|
||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.textContent = "+";
|
const plus = document.createElement("span");
|
||||||
|
plus.classList.add("svgicon", "svg-plus");
|
||||||
div.classList.add("home", "servericon");
|
div.classList.add("home", "servericon");
|
||||||
|
div.appendChild(plus);
|
||||||
serverlist.appendChild(div);
|
serverlist.appendChild(div);
|
||||||
div.onclick = _=>{
|
div.onclick = _=>{
|
||||||
this.createGuild();
|
this.createGuild();
|
||||||
};
|
};
|
||||||
const guilddsdiv = document.createElement("div");
|
const guilddsdiv = document.createElement("div");
|
||||||
const guildDiscoveryContainer = document.createElement("span");
|
const guildDiscoveryContainer = document.createElement("span");
|
||||||
guildDiscoveryContainer.classList.add(
|
guildDiscoveryContainer.classList.add("svgicon", "svg-explore");
|
||||||
"svgtheme",
|
|
||||||
"svgicon",
|
|
||||||
"svg-explore"
|
|
||||||
);
|
|
||||||
guilddsdiv.classList.add("home", "servericon");
|
guilddsdiv.classList.add("home", "servericon");
|
||||||
guilddsdiv.appendChild(guildDiscoveryContainer);
|
guilddsdiv.appendChild(guildDiscoveryContainer);
|
||||||
serverlist.appendChild(guilddsdiv);
|
serverlist.appendChild(guilddsdiv);
|
||||||
|
@ -838,7 +896,7 @@ class Localuser{
|
||||||
["title", "Create a guild"],
|
["title", "Create a guild"],
|
||||||
[
|
[
|
||||||
"fileupload",
|
"fileupload",
|
||||||
"Icon:",
|
"Icon: ",
|
||||||
function(event: Event){
|
function(event: Event){
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
if(!target.files)return;
|
if(!target.files)return;
|
||||||
|
@ -861,7 +919,7 @@ class Localuser{
|
||||||
[
|
[
|
||||||
"button",
|
"button",
|
||||||
"",
|
"",
|
||||||
"submit",
|
"Submit",
|
||||||
()=>{
|
()=>{
|
||||||
this.makeGuild(fields).then(_=>{
|
this.makeGuild(fields).then(_=>{
|
||||||
if(_.message){
|
if(_.message){
|
||||||
|
@ -889,7 +947,7 @@ class Localuser{
|
||||||
}
|
}
|
||||||
async guildDiscovery(){
|
async guildDiscovery(){
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
content.classList.add("guildy");
|
content.classList.add("flexttb","guildy");
|
||||||
content.textContent = "Loading...";
|
content.textContent = "Loading...";
|
||||||
const full = new Dialog(["html", content]);
|
const full = new Dialog(["html", content]);
|
||||||
full.show();
|
full.show();
|
||||||
|
@ -1104,7 +1162,7 @@ class Localuser{
|
||||||
});
|
});
|
||||||
let changed = false;
|
let changed = false;
|
||||||
const pronounbox = settingsLeft.addTextInput(
|
const pronounbox = settingsLeft.addTextInput(
|
||||||
"Pronouns",
|
"Pronouns:",
|
||||||
_=>{
|
_=>{
|
||||||
if(newpronouns || newbio || changed){
|
if(newpronouns || newbio || changed){
|
||||||
this.updateProfile({
|
this.updateProfile({
|
||||||
|
@ -1136,7 +1194,7 @@ class Localuser{
|
||||||
color = "transparent";
|
color = "transparent";
|
||||||
}
|
}
|
||||||
const colorPicker = settingsLeft.addColorInput(
|
const colorPicker = settingsLeft.addColorInput(
|
||||||
"Profile color",
|
"Profile color:",
|
||||||
_=>{},
|
_=>{},
|
||||||
{ initColor: color }
|
{ initColor: color }
|
||||||
);
|
);
|
||||||
|
@ -1149,9 +1207,9 @@ class Localuser{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const tas = settings.addButton("Themes & sounds");
|
const tas = settings.addButton("Themes & Sounds");
|
||||||
{
|
{
|
||||||
const themes = ["Dark", "WHITE", "Light"];
|
const themes = ["Dark", "WHITE", "Light", "Dark-Accent"];
|
||||||
tas.addSelect(
|
tas.addSelect(
|
||||||
"Theme:",
|
"Theme:",
|
||||||
_=>{
|
_=>{
|
||||||
|
@ -1167,18 +1225,18 @@ class Localuser{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const sounds = Voice.sounds;
|
const sounds = AVoice.sounds;
|
||||||
tas
|
tas
|
||||||
.addSelect(
|
.addSelect(
|
||||||
"Notification sound:",
|
"Notification sound:",
|
||||||
_=>{
|
_=>{
|
||||||
Voice.setNotificationSound(sounds[_]);
|
AVoice.setNotificationSound(sounds[_]);
|
||||||
},
|
},
|
||||||
sounds,
|
sounds,
|
||||||
{ defaultIndex: sounds.indexOf(Voice.getNotificationSound()) }
|
{ defaultIndex: sounds.indexOf(AVoice.getNotificationSound()) }
|
||||||
)
|
)
|
||||||
.watchForChange(_=>{
|
.watchForChange(_=>{
|
||||||
Voice.noises(sounds[_]);
|
AVoice.noises(sounds[_]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1197,6 +1255,40 @@ class Localuser{
|
||||||
{ initColor: userinfos.accent_color }
|
{ initColor: userinfos.accent_color }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
const box=tas.addCheckboxInput("Enable experimental Voice support",()=>{},{initState:Boolean(localStorage.getItem("Voice enabled"))});
|
||||||
|
box.onchange=(e)=>{
|
||||||
|
if(e){
|
||||||
|
if(confirm("Are you sure you want to enable this, this is very experimental and is likely to cause issues. (this feature is for devs, please don't enable if you don't know what you're doing)")){
|
||||||
|
localStorage.setItem("Voice enabled","true")
|
||||||
|
|
||||||
|
}else{
|
||||||
|
box.value=true;
|
||||||
|
const checkbox=box.input.deref();
|
||||||
|
if(checkbox){
|
||||||
|
checkbox.checked=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
localStorage.removeItem("Voice enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const update=settings.addButton("Update settings")
|
||||||
|
const sw=update.addSelect("Service Worker setting",()=>{},["False","Offline only","True"],{
|
||||||
|
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("","Check for update",()=>{
|
||||||
|
SW.checkUpdate();
|
||||||
|
});
|
||||||
|
update.addButtonInput("","Clear cache",()=>{
|
||||||
|
SW.forceClear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const security = settings.addButton("Account Settings");
|
const security = settings.addButton("Account Settings");
|
||||||
|
@ -1427,9 +1519,9 @@ class Localuser{
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
form.addTextInput("Name", "name", { required: true });
|
form.addTextInput("Name:", "name", { required: true });
|
||||||
form.addSelect(
|
form.addSelect(
|
||||||
"Team",
|
"Team:",
|
||||||
"team_id",
|
"team_id",
|
||||||
["Personal", ...teams.map((team: { name: string })=>team.name)],
|
["Personal", ...teams.map((team: { name: string })=>team.name)],
|
||||||
{
|
{
|
||||||
|
@ -1545,7 +1637,7 @@ class Localuser{
|
||||||
});
|
});
|
||||||
form.addTextInput("Bot username:","username",{initText:bot.username});
|
form.addTextInput("Bot username:","username",{initText:bot.username});
|
||||||
form.addFileInput("Bot avatar:","avatar");
|
form.addFileInput("Bot avatar:","avatar");
|
||||||
form.addButtonInput("Reset Token:","Reset",async ()=>{
|
form.addButtonInput("","Reset Token",async ()=>{
|
||||||
if(!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")){
|
if(!confirm("Are you sure you want to reset the bot token? Your bot will stop working until you update it.")){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1585,7 +1677,7 @@ class Localuser{
|
||||||
this.userinfo.updateLocal();
|
this.userinfo.updateLocal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
form.addButtonInput("","Advanced bot settings",()=>{
|
form.addButtonInput("","Advanced Bot Settings",()=>{
|
||||||
const token=this.botTokens.get(appId);
|
const token=this.botTokens.get(appId);
|
||||||
if(token){
|
if(token){
|
||||||
const botc=new Bot(bot,token,this);
|
const botc=new Bot(bot,token,this);
|
||||||
|
@ -1758,16 +1850,8 @@ class Localuser{
|
||||||
this.pageTitle("Loading...");
|
this.pageTitle("Loading...");
|
||||||
}
|
}
|
||||||
pageTitle(channelName = "", guildName = ""){
|
pageTitle(channelName = "", guildName = ""){
|
||||||
(document.getElementById("channelname") as HTMLSpanElement).textContent =
|
(document.getElementById("channelname") as HTMLSpanElement).textContent = channelName;
|
||||||
channelName;
|
(document.getElementsByTagName("title")[0] as HTMLTitleElement).textContent = channelName + (guildName ? " | " + guildName : "") + " | " + this.instancePing.name +" | Jank Client";
|
||||||
(
|
|
||||||
document.getElementsByTagName("title")[0] as HTMLTitleElement
|
|
||||||
).textContent =
|
|
||||||
channelName +
|
|
||||||
(guildName ? " | " + guildName : "") +
|
|
||||||
" | " +
|
|
||||||
this.instancePing.name +
|
|
||||||
" | Jank Client";
|
|
||||||
}
|
}
|
||||||
async instanceStats(){
|
async instanceStats(){
|
||||||
const res = await fetch(this.info.api + "/policies/stats", {
|
const res = await fetch(this.info.api + "/policies/stats", {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<body class="Dark-theme">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -12,13 +13,13 @@
|
||||||
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
||||||
<link href="/style.css" rel="stylesheet">
|
<link href="/style.css" rel="stylesheet">
|
||||||
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
||||||
|
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body class="no-theme">
|
||||||
<div id="logindiv">
|
<div id="logindiv">
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<br >
|
|
||||||
<form id="form" submit="check(e)">
|
<form id="form" submit="check(e)">
|
||||||
<label for="instance"><b>Instance:</b></label
|
<label for="instance"><b>Instance:</b></label>
|
||||||
><br >
|
|
||||||
<p id="verify"></p>
|
<p id="verify"></p>
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
|
@ -27,29 +28,26 @@
|
||||||
name="instance"
|
name="instance"
|
||||||
id="instancein"
|
id="instancein"
|
||||||
value=""
|
value=""
|
||||||
id="instancein"
|
|
||||||
required
|
required
|
||||||
><br ><br >
|
>
|
||||||
|
|
||||||
<label for="uname"><b>Email:</b></label
|
<label for="uname"><b>Email:</b></label>
|
||||||
><br >
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter email address"
|
placeholder="Enter email address"
|
||||||
name="uname"
|
name="uname"
|
||||||
id="uname"
|
id="uname"
|
||||||
required
|
required
|
||||||
><br ><br >
|
>
|
||||||
|
|
||||||
<label for="psw"><b>Password:</b></label
|
<label for="psw"><b>Password:</b></label>
|
||||||
><br >
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Enter Password"
|
placeholder="Enter Password"
|
||||||
name="psw"
|
name="psw"
|
||||||
id="psw"
|
id="psw"
|
||||||
required
|
required
|
||||||
><br ><br ><br ><br >
|
>
|
||||||
<p class="wrongred" id="wrong"></p>
|
<p class="wrongred" id="wrong"></p>
|
||||||
|
|
||||||
<div id="h-captcha"></div>
|
<div id="h-captcha"></div>
|
||||||
|
@ -59,4 +57,5 @@
|
||||||
</div>
|
</div>
|
||||||
<datalist id="instances"></datalist>
|
<datalist id="instances"></datalist>
|
||||||
<script src="/login.js" type="module"></script>
|
<script src="/login.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
|
@ -47,7 +47,7 @@ function trimswitcher(){
|
||||||
if(wellknown.at(-1) !== "/"){
|
if(wellknown.at(-1) !== "/"){
|
||||||
wellknown += "/";
|
wellknown += "/";
|
||||||
}
|
}
|
||||||
wellknown += user.username;
|
wellknown =(user.id||user.email)+"@"+wellknown;
|
||||||
if(map.has(wellknown)){
|
if(map.has(wellknown)){
|
||||||
const otheruser = map.get(wellknown);
|
const otheruser = map.get(wellknown);
|
||||||
if(otheruser[1].serverurls.wellknown.at(-1) === "/"){
|
if(otheruser[1].serverurls.wellknown.at(-1) === "/"){
|
||||||
|
@ -95,7 +95,7 @@ function setDefaults(){
|
||||||
userinfos.users = {};
|
userinfos.users = {};
|
||||||
}
|
}
|
||||||
if(userinfos.accent_color === undefined){
|
if(userinfos.accent_color === undefined){
|
||||||
userinfos.accent_color = "#242443";
|
userinfos.accent_color = "#3096f7";
|
||||||
}
|
}
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
"--accent-color",
|
"--accent-color",
|
||||||
|
@ -116,12 +116,12 @@ function setDefaults(){
|
||||||
setDefaults();
|
setDefaults();
|
||||||
class Specialuser{
|
class Specialuser{
|
||||||
serverurls: {
|
serverurls: {
|
||||||
api: string;
|
api: string;
|
||||||
cdn: string;
|
cdn: string;
|
||||||
gateway: string;
|
gateway: string;
|
||||||
wellknown: string;
|
wellknown: string;
|
||||||
login: string;
|
login: string;
|
||||||
};
|
};
|
||||||
email: string;
|
email: string;
|
||||||
token: string;
|
token: string;
|
||||||
loggedin;
|
loggedin;
|
||||||
|
@ -178,6 +178,13 @@ login: string;
|
||||||
get localuserStore(){
|
get localuserStore(){
|
||||||
return this.json.localuserStore;
|
return this.json.localuserStore;
|
||||||
}
|
}
|
||||||
|
set id(e){
|
||||||
|
this.json.id = e;
|
||||||
|
this.updateLocal();
|
||||||
|
}
|
||||||
|
get id(){
|
||||||
|
return this.json.id;
|
||||||
|
}
|
||||||
get uid(){
|
get uid(){
|
||||||
return this.email + this.serverurls.wellknown;
|
return this.email + this.serverurls.wellknown;
|
||||||
}
|
}
|
||||||
|
@ -532,12 +539,34 @@ if(document.getElementById("form")){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//this currently does not work, and need to be implemented better at some time.
|
//this currently does not work, and need to be implemented better at some time.
|
||||||
/*
|
if(!localStorage.getItem("SWMode")){
|
||||||
if ("serviceWorker" in navigator){
|
localStorage.setItem("SWMode","true");
|
||||||
|
}
|
||||||
|
class SW{
|
||||||
|
static worker:undefined|ServiceWorker;
|
||||||
|
static setMode(mode:"false"|"offlineOnly"|"true"){
|
||||||
|
localStorage.setItem("SWMode",mode);
|
||||||
|
if(this.worker){
|
||||||
|
this.worker.postMessage({data:mode,code:"setMode"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static checkUpdate(){
|
||||||
|
if(this.worker){
|
||||||
|
this.worker.postMessage({code:"CheckUpdate"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static forceClear(){
|
||||||
|
if(this.worker){
|
||||||
|
this.worker.postMessage({code:"ForceClear"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {SW};
|
||||||
|
if ("serviceWorker" in navigator){
|
||||||
navigator.serviceWorker.register("/service.js", {
|
navigator.serviceWorker.register("/service.js", {
|
||||||
scope: "/",
|
scope: "/",
|
||||||
}).then((registration) => {
|
}).then((registration) => {
|
||||||
let serviceWorker:ServiceWorker;
|
let serviceWorker:ServiceWorker|undefined;
|
||||||
if (registration.installing) {
|
if (registration.installing) {
|
||||||
serviceWorker = registration.installing;
|
serviceWorker = registration.installing;
|
||||||
console.log("installing");
|
console.log("installing");
|
||||||
|
@ -548,15 +577,17 @@ if(document.getElementById("form")){
|
||||||
serviceWorker = registration.active;
|
serviceWorker = registration.active;
|
||||||
console.log("active");
|
console.log("active");
|
||||||
}
|
}
|
||||||
|
SW.worker=serviceWorker;
|
||||||
|
SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true");
|
||||||
if (serviceWorker) {
|
if (serviceWorker) {
|
||||||
console.log(serviceWorker.state);
|
console.log(serviceWorker.state);
|
||||||
serviceWorker.addEventListener("statechange", (e) => {
|
serviceWorker.addEventListener("statechange", (_) => {
|
||||||
console.log(serviceWorker.state);
|
console.log(serviceWorker.state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
const switchurl = document.getElementById("switch") as HTMLAreaElement;
|
const switchurl = document.getElementById("switch") as HTMLAreaElement;
|
||||||
if(switchurl){
|
if(switchurl){
|
||||||
switchurl.href += window.location.search;
|
switchurl.href += window.location.search;
|
||||||
|
|
|
@ -765,12 +765,12 @@ txt[j + 1] === undefined)
|
||||||
}else{
|
}else{
|
||||||
const full: Dialog = new Dialog([
|
const full: Dialog = new Dialog([
|
||||||
"vdiv",
|
"vdiv",
|
||||||
["title", "You're leaving spacebar"],
|
["title", "You're leaving Spacebar"],
|
||||||
[
|
[
|
||||||
"text",
|
"text",
|
||||||
"You're going to " +
|
"You're going to " +
|
||||||
Url.host +
|
Url.host +
|
||||||
" are you sure you want to go there?",
|
". Are you sure you want to go there?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"hdiv",
|
"hdiv",
|
||||||
|
|
|
@ -49,6 +49,32 @@ class Member extends SnowFlake{
|
||||||
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
update(memberjson: memberjson){
|
||||||
|
this.roles=[];
|
||||||
|
for(const key of Object.keys(memberjson)){
|
||||||
|
if(key === "guild" || key === "owner" || key === "user"){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key === "roles"){
|
||||||
|
for(const strrole of memberjson.roles){
|
||||||
|
const role = this.guild.roleids.get(strrole);
|
||||||
|
if(!role)continue;
|
||||||
|
this.roles.push(role);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(key === "presence"){
|
||||||
|
this.getPresence(memberjson.presence);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(this as any)[key] = (memberjson as any)[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roles.sort((a, b)=>{
|
||||||
|
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
get guild(){
|
get guild(){
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
@ -241,6 +267,24 @@ class Member extends SnowFlake{
|
||||||
]);
|
]);
|
||||||
menu.show();
|
menu.show();
|
||||||
}
|
}
|
||||||
|
addRole(role:Role){
|
||||||
|
const roles=this.roles.map(_=>_.id)
|
||||||
|
roles.push(role.id);
|
||||||
|
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
|
||||||
|
method:"PATCH",
|
||||||
|
headers:this.guild.headers,
|
||||||
|
body:JSON.stringify({roles})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
removeRole(role:Role){
|
||||||
|
let roles=this.roles.map(_=>_.id)
|
||||||
|
roles=roles.filter(_=>_!==role.id);
|
||||||
|
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
|
||||||
|
method:"PATCH",
|
||||||
|
headers:this.guild.headers,
|
||||||
|
body:JSON.stringify({roles})
|
||||||
|
})
|
||||||
|
}
|
||||||
banAPI(reason: string){
|
banAPI(reason: string){
|
||||||
const headers = structuredClone(this.guild.headers);
|
const headers = structuredClone(this.guild.headers);
|
||||||
(headers as any)["x-audit-log-reason"] = reason;
|
(headers as any)["x-audit-log-reason"] = reason;
|
||||||
|
|
|
@ -11,6 +11,8 @@ import{ SnowFlake }from"./snowflake.js";
|
||||||
import{ memberjson, messagejson }from"./jsontypes.js";
|
import{ memberjson, messagejson }from"./jsontypes.js";
|
||||||
import{ Emoji }from"./emoji.js";
|
import{ Emoji }from"./emoji.js";
|
||||||
import{ Dialog }from"./dialog.js";
|
import{ Dialog }from"./dialog.js";
|
||||||
|
import{ mobile }from"./login.js";
|
||||||
|
import { I18n } from "./i18n.js";
|
||||||
|
|
||||||
class Message extends SnowFlake{
|
class Message extends SnowFlake{
|
||||||
static contextmenu = new Contextmenu<Message, undefined>("message menu");
|
static contextmenu = new Contextmenu<Message, undefined>("message menu");
|
||||||
|
@ -44,9 +46,7 @@ class Message extends SnowFlake{
|
||||||
return this.weakdiv?.deref();
|
return this.weakdiv?.deref();
|
||||||
}
|
}
|
||||||
//*/
|
//*/
|
||||||
div:
|
div:(HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement })| undefined;
|
||||||
| (HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement })
|
|
||||||
| undefined;
|
|
||||||
member: Member | undefined;
|
member: Member | undefined;
|
||||||
reactions!: messagejson["reactions"];
|
reactions!: messagejson["reactions"];
|
||||||
static setup(){
|
static setup(){
|
||||||
|
@ -56,13 +56,13 @@ class Message extends SnowFlake{
|
||||||
Message.setupcmenu();
|
Message.setupcmenu();
|
||||||
}
|
}
|
||||||
static setupcmenu(){
|
static setupcmenu(){
|
||||||
Message.contextmenu.addbutton("Copy raw text", function(this: Message){
|
Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"copyrawtext"), function(this: Message){
|
||||||
navigator.clipboard.writeText(this.content.rawString);
|
navigator.clipboard.writeText(this.content.rawString);
|
||||||
});
|
});
|
||||||
Message.contextmenu.addbutton("Reply", function(this: Message){
|
Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"reply"), function(this: Message){
|
||||||
this.channel.setReplying(this);
|
this.channel.setReplying(this);
|
||||||
});
|
});
|
||||||
Message.contextmenu.addbutton("Copy message id", function(this: Message){
|
Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"copymessageid"), function(this: Message){
|
||||||
navigator.clipboard.writeText(this.id);
|
navigator.clipboard.writeText(this.id);
|
||||||
});
|
});
|
||||||
Message.contextmenu.addsubmenu(
|
Message.contextmenu.addsubmenu(
|
||||||
|
@ -404,22 +404,19 @@ class Message extends SnowFlake{
|
||||||
}
|
}
|
||||||
if(this.message_reference){
|
if(this.message_reference){
|
||||||
const replyline = document.createElement("div");
|
const replyline = document.createElement("div");
|
||||||
const line = document.createElement("hr");
|
|
||||||
const minipfp = document.createElement("img");
|
const minipfp = document.createElement("img");
|
||||||
minipfp.classList.add("replypfp");
|
minipfp.classList.add("replypfp");
|
||||||
replyline.appendChild(line);
|
|
||||||
replyline.appendChild(minipfp);
|
replyline.appendChild(minipfp);
|
||||||
const username = document.createElement("span");
|
const username = document.createElement("span");
|
||||||
replyline.appendChild(username);
|
replyline.appendChild(username);
|
||||||
const reply = document.createElement("div");
|
const reply = document.createElement("div");
|
||||||
username.classList.add("username");
|
username.classList.add("username");
|
||||||
reply.classList.add("replytext");
|
reply.classList.add("replytext","ellipsis");
|
||||||
replyline.appendChild(reply);
|
replyline.appendChild(reply);
|
||||||
const line2 = document.createElement("hr");
|
const line2 = document.createElement("hr");
|
||||||
replyline.appendChild(line2);
|
replyline.appendChild(line2);
|
||||||
line2.classList.add("reply");
|
line2.classList.add("reply");
|
||||||
line.classList.add("startreply");
|
replyline.classList.add("flexltr","replyflex");
|
||||||
replyline.classList.add("replyflex");
|
|
||||||
// TODO: Fix this
|
// TODO: Fix this
|
||||||
this.channel.getmessage(this.message_reference.message_id).then(message=>{
|
this.channel.getmessage(this.message_reference.message_id).then(message=>{
|
||||||
if(message.author.relationshipType === 2){
|
if(message.author.relationshipType === 2){
|
||||||
|
@ -432,6 +429,11 @@ class Message extends SnowFlake{
|
||||||
author.bind(minipfp, this.guild);
|
author.bind(minipfp, this.guild);
|
||||||
username.textContent = author.username;
|
username.textContent = author.username;
|
||||||
author.bind(username, this.guild);
|
author.bind(username, this.guild);
|
||||||
|
Member.resolveMember(author, this.guild).then(_=>{
|
||||||
|
if(_){
|
||||||
|
username.textContent=_.name;
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
reply.onclick = _=>{
|
reply.onclick = _=>{
|
||||||
// TODO: FIX this
|
// TODO: FIX this
|
||||||
|
@ -442,7 +444,6 @@ class Message extends SnowFlake{
|
||||||
div.appendChild(build);
|
div.appendChild(build);
|
||||||
if({ 0: true, 19: true }[this.type] || this.attachments.length !== 0){
|
if({ 0: true, 19: true }[this.type] || this.attachments.length !== 0){
|
||||||
const pfpRow = document.createElement("div");
|
const pfpRow = document.createElement("div");
|
||||||
pfpRow.classList.add("flexltr");
|
|
||||||
let pfpparent, current;
|
let pfpparent, current;
|
||||||
if(premessage != null){
|
if(premessage != null){
|
||||||
pfpparent ??= premessage;
|
pfpparent ??= premessage;
|
||||||
|
@ -466,18 +467,19 @@ class Message extends SnowFlake{
|
||||||
pfpRow.classList.add("pfprow");
|
pfpRow.classList.add("pfprow");
|
||||||
build.appendChild(pfpRow);
|
build.appendChild(pfpRow);
|
||||||
const text = document.createElement("div");
|
const text = document.createElement("div");
|
||||||
text.classList.add("flexttb");
|
text.classList.add("commentrow", "flexttb");
|
||||||
const texttxt = document.createElement("div");
|
|
||||||
texttxt.classList.add("commentrow", "flexttb");
|
|
||||||
text.appendChild(texttxt);
|
|
||||||
if(combine){
|
if(combine){
|
||||||
const username = document.createElement("span");
|
const username = document.createElement("span");
|
||||||
username.classList.add("username");
|
username.classList.add("username");
|
||||||
this.author.bind(username, this.guild);
|
this.author.bind(username, this.guild);
|
||||||
|
Member.resolveMember(this.author, this.guild).then(_=>{
|
||||||
|
if(_){
|
||||||
|
username.textContent=_.name;
|
||||||
|
}
|
||||||
|
})
|
||||||
div.classList.add("topMessage");
|
div.classList.add("topMessage");
|
||||||
username.textContent = this.author.username;
|
username.textContent = this.author.username;
|
||||||
const userwrap = document.createElement("div");
|
const userwrap = document.createElement("div");
|
||||||
userwrap.classList.add("flexltr");
|
|
||||||
userwrap.appendChild(username);
|
userwrap.appendChild(username);
|
||||||
if(this.author.bot){
|
if(this.author.bot){
|
||||||
const username = document.createElement("span");
|
const username = document.createElement("span");
|
||||||
|
@ -490,7 +492,7 @@ class Message extends SnowFlake{
|
||||||
time.classList.add("timestamp");
|
time.classList.add("timestamp");
|
||||||
userwrap.appendChild(time);
|
userwrap.appendChild(time);
|
||||||
|
|
||||||
texttxt.appendChild(userwrap);
|
text.appendChild(userwrap);
|
||||||
}else{
|
}else{
|
||||||
div.classList.remove("topMessage");
|
div.classList.remove("topMessage");
|
||||||
}
|
}
|
||||||
|
@ -499,13 +501,13 @@ class Message extends SnowFlake{
|
||||||
const messagedwrap = document.createElement("div");
|
const messagedwrap = document.createElement("div");
|
||||||
messagedwrap.classList.add("flexttb");
|
messagedwrap.classList.add("flexttb");
|
||||||
messagedwrap.appendChild(messaged);
|
messagedwrap.appendChild(messaged);
|
||||||
texttxt.appendChild(messagedwrap);
|
text.appendChild(messagedwrap);
|
||||||
|
|
||||||
build.appendChild(text);
|
build.appendChild(text);
|
||||||
if(this.attachments.length){
|
if(this.attachments.length){
|
||||||
console.log(this.attachments);
|
console.log(this.attachments);
|
||||||
const attach = document.createElement("div");
|
const attach = document.createElement("div");
|
||||||
attach.classList.add("flexltr");
|
attach.classList.add("flexltr","attachments");
|
||||||
for(const thing of this.attachments){
|
for(const thing of this.attachments){
|
||||||
attach.appendChild(thing.getHTML());
|
attach.appendChild(thing.getHTML());
|
||||||
}
|
}
|
||||||
|
@ -513,7 +515,6 @@ class Message extends SnowFlake{
|
||||||
}
|
}
|
||||||
if(this.embeds.length){
|
if(this.embeds.length){
|
||||||
const embeds = document.createElement("div");
|
const embeds = document.createElement("div");
|
||||||
embeds.classList.add("flexltr");
|
|
||||||
for(const thing of this.embeds){
|
for(const thing of this.embeds){
|
||||||
embeds.appendChild(thing.generateHTML());
|
embeds.appendChild(thing.generateHTML());
|
||||||
}
|
}
|
||||||
|
@ -522,27 +523,23 @@ class Message extends SnowFlake{
|
||||||
//
|
//
|
||||||
}else if(this.type === 7){
|
}else if(this.type === 7){
|
||||||
const text = document.createElement("div");
|
const text = document.createElement("div");
|
||||||
text.classList.add("flexttb");
|
|
||||||
const texttxt = document.createElement("div");
|
|
||||||
text.appendChild(texttxt);
|
|
||||||
build.appendChild(text);
|
build.appendChild(text);
|
||||||
texttxt.classList.add("flexltr");
|
|
||||||
const messaged = document.createElement("span");
|
const messaged = document.createElement("span");
|
||||||
div.txt = messaged;
|
div.txt = messaged;
|
||||||
messaged.textContent = "welcome: ";
|
messaged.textContent = "welcome: ";
|
||||||
texttxt.appendChild(messaged);
|
text.appendChild(messaged);
|
||||||
|
|
||||||
const username = document.createElement("span");
|
const username = document.createElement("span");
|
||||||
username.textContent = this.author.username;
|
username.textContent = this.author.username;
|
||||||
//this.author.profileclick(username);
|
//this.author.profileclick(username);
|
||||||
this.author.bind(username, this.guild);
|
this.author.bind(username, this.guild);
|
||||||
texttxt.appendChild(username);
|
text.appendChild(username);
|
||||||
username.classList.add("username");
|
username.classList.add("username");
|
||||||
|
|
||||||
const time = document.createElement("span");
|
const time = document.createElement("span");
|
||||||
time.textContent = " " + formatTime(new Date(this.timestamp));
|
time.textContent = " " + formatTime(new Date(this.timestamp));
|
||||||
time.classList.add("timestamp");
|
time.classList.add("timestamp");
|
||||||
texttxt.append(time);
|
text.append(time);
|
||||||
div.classList.add("topMessage");
|
div.classList.add("topMessage");
|
||||||
}
|
}
|
||||||
const reactions = document.createElement("div");
|
const reactions = document.createElement("div");
|
||||||
|
@ -557,6 +554,7 @@ class Message extends SnowFlake{
|
||||||
if(this.div){
|
if(this.div){
|
||||||
let buttons: HTMLDivElement | undefined;
|
let buttons: HTMLDivElement | undefined;
|
||||||
this.div.onmouseenter = _=>{
|
this.div.onmouseenter = _=>{
|
||||||
|
if(mobile)return;
|
||||||
if(buttons){
|
if(buttons){
|
||||||
buttons.remove();
|
buttons.remove();
|
||||||
buttons = undefined;
|
buttons = undefined;
|
||||||
|
@ -565,9 +563,9 @@ class Message extends SnowFlake{
|
||||||
buttons = document.createElement("div");
|
buttons = document.createElement("div");
|
||||||
buttons.classList.add("messageButtons", "flexltr");
|
buttons.classList.add("messageButtons", "flexltr");
|
||||||
if(this.channel.hasPermission("SEND_MESSAGES")){
|
if(this.channel.hasPermission("SEND_MESSAGES")){
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("button");
|
||||||
const reply = document.createElement("span");
|
const reply = document.createElement("span");
|
||||||
reply.classList.add("svgtheme", "svg-reply", "svgicon");
|
reply.classList.add("svg-reply", "svgicon");
|
||||||
container.append(reply);
|
container.append(reply);
|
||||||
buttons.append(container);
|
buttons.append(container);
|
||||||
container.onclick = _=>{
|
container.onclick = _=>{
|
||||||
|
@ -575,9 +573,9 @@ class Message extends SnowFlake{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if(this.author === this.localuser.user){
|
if(this.author === this.localuser.user){
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("button");
|
||||||
const edit = document.createElement("span");
|
const edit = document.createElement("span");
|
||||||
edit.classList.add("svgtheme", "svg-edit", "svgicon");
|
edit.classList.add("svg-edit", "svgicon");
|
||||||
container.append(edit);
|
container.append(edit);
|
||||||
buttons.append(container);
|
buttons.append(container);
|
||||||
container.onclick = _=>{
|
container.onclick = _=>{
|
||||||
|
@ -585,9 +583,9 @@ class Message extends SnowFlake{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if(this.canDelete()){
|
if(this.canDelete()){
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("button");
|
||||||
const reply = document.createElement("span");
|
const reply = document.createElement("span");
|
||||||
reply.classList.add("svgtheme", "svg-delete", "svgicon");
|
reply.classList.add("svg-delete", "svgicon");
|
||||||
container.append(reply);
|
container.append(reply);
|
||||||
buttons.append(container);
|
buttons.append(container);
|
||||||
container.onclick = _=>{
|
container.onclick = _=>{
|
||||||
|
@ -596,12 +594,14 @@ class Message extends SnowFlake{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const diaolog = new Dialog([
|
const diaolog = new Dialog([
|
||||||
|
"vdiv",
|
||||||
|
["title", "Are you sure you want to delete this?"],
|
||||||
|
[
|
||||||
"hdiv",
|
"hdiv",
|
||||||
["title", "are you sure you want to delete this?"],
|
|
||||||
[
|
[
|
||||||
"button",
|
"button",
|
||||||
"",
|
"",
|
||||||
"yes",
|
"Yes",
|
||||||
()=>{
|
()=>{
|
||||||
this.delete();
|
this.delete();
|
||||||
diaolog.hide();
|
diaolog.hide();
|
||||||
|
@ -610,11 +610,12 @@ class Message extends SnowFlake{
|
||||||
[
|
[
|
||||||
"button",
|
"button",
|
||||||
"",
|
"",
|
||||||
"no",
|
"No",
|
||||||
()=>{
|
()=>{
|
||||||
diaolog.hide();
|
diaolog.hide();
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
diaolog.show();
|
diaolog.show();
|
||||||
};
|
};
|
||||||
|
|
|
@ -102,18 +102,24 @@ type botjsonfetch={
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dialog=document.createElement("dialog");
|
const dialog=document.createElement("dialog");
|
||||||
dialog.classList.add("accountSwitcher");
|
dialog.classList.add("flexttb","accountSwitcher");
|
||||||
const h1=document.createElement("h1");
|
const h1=document.createElement("h1");
|
||||||
dialog.append(h1);
|
dialog.append(h1);
|
||||||
h1.textContent="Invite to server:";
|
h1.textContent="Invite to server:";
|
||||||
const select=document.createElement("select");
|
const select=document.createElement("select");
|
||||||
|
const selectSpan=document.createElement("span");
|
||||||
|
selectSpan.classList.add("selectspan");
|
||||||
|
const selectArrow = document.createElement("span");
|
||||||
|
selectArrow.classList.add("svgicon","svg-category","selectarrow");
|
||||||
for(const guild of guilds){
|
for(const guild of guilds){
|
||||||
const option=document.createElement("option");
|
const option=document.createElement("option");
|
||||||
option.textContent=guild.name;
|
option.textContent=guild.name;
|
||||||
option.value=guild.id;
|
option.value=guild.id;
|
||||||
select.append(option);
|
select.append(option);
|
||||||
}
|
}
|
||||||
dialog.append(select);
|
selectSpan.append(select);
|
||||||
|
selectSpan.append(selectArrow);
|
||||||
|
dialog.append(selectSpan);
|
||||||
const button=document.createElement("button");
|
const button=document.createElement("button");
|
||||||
button.textContent="Invite";
|
button.textContent="Invite";
|
||||||
dialog.append(button);
|
dialog.append(button);
|
||||||
|
@ -193,7 +199,7 @@ type botjsonfetch={
|
||||||
}
|
}
|
||||||
|
|
||||||
table.append(td);
|
table.append(td);
|
||||||
table.classList.add("accountSwitcher");
|
table.classList.add("flexttb","accountSwitcher");
|
||||||
console.log(table);
|
console.log(table);
|
||||||
document.body.append(table);
|
document.body.append(table);
|
||||||
}
|
}
|
||||||
|
@ -221,7 +227,7 @@ type botjsonfetch={
|
||||||
const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
|
const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
|
||||||
pfp.src=`${urls.cdn}/embed/avatars/${int}.png`;
|
pfp.src=`${urls.cdn}/embed/avatars/${int}.png`;
|
||||||
}
|
}
|
||||||
const perms=document.getElementById("permsions") as HTMLDivElement;
|
const perms=document.getElementById("permissions") as HTMLDivElement;
|
||||||
|
|
||||||
if(perms&&permstr){
|
if(perms&&permstr){
|
||||||
const permisions=new Permissions(permstr)
|
const permisions=new Permissions(permstr)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<body class="Dark-theme">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -9,15 +10,18 @@
|
||||||
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
||||||
<link href="/style.css" rel="stylesheet">
|
<link href="/style.css" rel="stylesheet">
|
||||||
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
||||||
|
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body class="no-theme">
|
||||||
<div>
|
<div>
|
||||||
<div id="invitebody">
|
<div id="invitebody">
|
||||||
<img id="inviteimg" class="pfp"/>
|
<img id="inviteimg" class="pfp"/>
|
||||||
<h1 id="invitename">Bot Name</h1>
|
<h1 id="invitename">Bot Name</h1>
|
||||||
<p id="invitedescription">Add Bot</p>
|
<p id="invitedescription">Add Bot</p>
|
||||||
<div id="permsions"><h1>This will allow the bot to:</h1></div>
|
<div id="permissions"><h1>This will allow the bot to:</h1></div>
|
||||||
<button id="AcceptInvite">Add to server</button>
|
<button id="AcceptInvite">Add to server</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/oauth2/auth.js"></script>
|
<script type="module" src="/oauth2/auth.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
|
@ -1,5 +1,5 @@
|
||||||
<body class="Dark-theme">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -10,37 +10,38 @@
|
||||||
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
|
||||||
<link href="/style.css" rel="stylesheet">
|
<link href="/style.css" rel="stylesheet">
|
||||||
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
<link href="/themes.css" rel="stylesheet" id="lightcss">
|
||||||
|
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style>
|
||||||
</head>
|
</head>
|
||||||
|
<body class="no-theme">
|
||||||
<div id="logindiv">
|
<div id="logindiv">
|
||||||
<h1>Create an account</h1><br>
|
<h1>Create an account</h1>
|
||||||
<form id="register" submit="registertry(e)">
|
<form id="register" submit="registertry(e)">
|
||||||
<div>
|
<div>
|
||||||
<label for="instance"><b>Instance:</b></label><br>
|
<label for="instance"><b>Instance:</b></label>
|
||||||
<p id="verify"></p>
|
<p id="verify"></p>
|
||||||
<input type="search" list="instances" placeholder="Instance URL" id="instancein" name="instance" value=""
|
<input type="search" list="instances" placeholder="Instance URL" id="instancein" name="instance" value="" required>
|
||||||
id="instancein" required>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="uname"><b>Email:</b></label><br>
|
<label for="uname"><b>Email:</b></label>
|
||||||
<input type="text" placeholder="Enter Email" name="uname" id="uname" required>
|
<input type="text" placeholder="Enter Email" name="uname" id="uname" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="uname"><b>Username:</b></label><br>
|
<label for="uname"><b>Username:</b></label>
|
||||||
<input type="text" placeholder="Enter Username" name="username" id="username" required>
|
<input type="text" placeholder="Enter Username" name="username" id="username" required>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="psw"><b>Password:</b></label><br>
|
<label for="psw"><b>Password:</b></label>
|
||||||
<input type="password" placeholder="Enter Password" name="psw" id="psw" required>
|
<input type="password" placeholder="Enter Password" name="psw" id="psw" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="psw2"><b>Enter password again:</b></label><br>
|
<label for="psw2"><b>Enter password again:</b></label>
|
||||||
<input type="password" placeholder="Enter Password Again" name="psw2" id="psw2" required>
|
<input type="password" placeholder="Enter Password Again" name="psw2" id="psw2" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="date"><b>Date of birth:</b></label><br>
|
<label for="date"><b>Date of birth:</b></label>
|
||||||
<input type="date" id="date" name="date">
|
<input type="date" id="date" name="date">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -59,4 +60,5 @@
|
||||||
</div>
|
</div>
|
||||||
<datalist id="instances"></datalist>
|
<datalist id="instances"></datalist>
|
||||||
<script src="/register.js" type="module"></script>
|
<script src="/register.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
|
@ -3,6 +3,7 @@ import{ Localuser }from"./localuser.js";
|
||||||
import{ Guild }from"./guild.js";
|
import{ Guild }from"./guild.js";
|
||||||
import{ SnowFlake }from"./snowflake.js";
|
import{ SnowFlake }from"./snowflake.js";
|
||||||
import{ rolesjson }from"./jsontypes.js";
|
import{ rolesjson }from"./jsontypes.js";
|
||||||
|
import{ Search }from"./search.js";
|
||||||
class Role extends SnowFlake{
|
class Role extends SnowFlake{
|
||||||
permissions: Permissions;
|
permissions: Permissions;
|
||||||
owner: Guild;
|
owner: Guild;
|
||||||
|
@ -13,6 +14,7 @@ class Role extends SnowFlake{
|
||||||
icon!: string;
|
icon!: string;
|
||||||
mentionable!: boolean;
|
mentionable!: boolean;
|
||||||
unicode_emoji!: string;
|
unicode_emoji!: string;
|
||||||
|
position!:number;
|
||||||
headers: Guild["headers"];
|
headers: Guild["headers"];
|
||||||
constructor(json: rolesjson, owner: Guild){
|
constructor(json: rolesjson, owner: Guild){
|
||||||
super(json.id);
|
super(json.id);
|
||||||
|
@ -27,6 +29,15 @@ class Role extends SnowFlake{
|
||||||
this.permissions = new Permissions(json.permissions);
|
this.permissions = new Permissions(json.permissions);
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
||||||
|
newJson(json: rolesjson){
|
||||||
|
for(const thing of Object.keys(json)){
|
||||||
|
if(thing === "id"||thing==="permissions"){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(this as any)[thing] = (json as any)[thing];
|
||||||
|
}
|
||||||
|
this.permissions.allow=BigInt(json.permissions);
|
||||||
|
}
|
||||||
get guild(): Guild{
|
get guild(): Guild{
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +50,14 @@ class Role extends SnowFlake{
|
||||||
}
|
}
|
||||||
return`#${this.color.toString(16)}`;
|
return`#${this.color.toString(16)}`;
|
||||||
}
|
}
|
||||||
|
canManage(){
|
||||||
|
if(this.guild.member.hasPermission("MANAGE_ROLES")){
|
||||||
|
let max=-Infinity;
|
||||||
|
this.guild.member.roles.forEach(r=>max=Math.max(max,r.position))
|
||||||
|
return this.position<=max||this.guild.properties.owner_id===this.guild.member.id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export{ Role };
|
export{ Role };
|
||||||
import{ Options }from"./settings.js";
|
import{ Options }from"./settings.js";
|
||||||
|
@ -121,22 +140,25 @@ class PermissionToggle implements OptionsElement<number>{
|
||||||
submit(){}
|
submit(){}
|
||||||
}
|
}
|
||||||
import{ OptionsElement, Buttons }from"./settings.js";
|
import{ OptionsElement, Buttons }from"./settings.js";
|
||||||
|
import { Contextmenu } from "./contextmenu.js";
|
||||||
|
import { Channel } from "./channel.js";
|
||||||
class RoleList extends Buttons{
|
class RoleList extends Buttons{
|
||||||
readonly permissions: [Role, Permissions][];
|
permissions: [Role, Permissions][];
|
||||||
permission: Permissions;
|
permission: Permissions;
|
||||||
readonly guild: Guild;
|
readonly guild: Guild;
|
||||||
readonly channel: boolean;
|
readonly channel: false|Channel;
|
||||||
declare readonly buttons: [string, string][];
|
declare buttons: [string, string][];
|
||||||
readonly options: Options;
|
readonly options: Options;
|
||||||
onchange: Function;
|
onchange: Function;
|
||||||
curid!: string;
|
curid?: string;
|
||||||
constructor(
|
get info(){
|
||||||
permissions: [Role, Permissions][],
|
return this.guild.info;
|
||||||
guild: Guild,
|
}
|
||||||
onchange: Function,
|
get headers(){
|
||||||
channel = false
|
return this.guild.headers;
|
||||||
){
|
}
|
||||||
super("Roles");
|
constructor(permissions:[Role, Permissions][], guild:Guild, onchange:Function, channel:false|Channel){
|
||||||
|
super("");
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
@ -147,16 +169,238 @@ class RoleList extends Buttons{
|
||||||
}else{
|
}else{
|
||||||
this.permission = new Permissions("0");
|
this.permission = new Permissions("0");
|
||||||
}
|
}
|
||||||
|
this.makeguildmenus(options);
|
||||||
for(const thing of Permissions.info){
|
for(const thing of Permissions.info){
|
||||||
options.options.push(
|
options.options.push(
|
||||||
new PermissionToggle(thing, this.permission, options)
|
new PermissionToggle(thing, this.permission, options)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for(const i of permissions){
|
for(const i of permissions){
|
||||||
console.log(i);
|
|
||||||
this.buttons.push([i[0].name, i[0].id]);
|
this.buttons.push([i[0].name, i[0].id]);
|
||||||
}
|
}
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
guild.roleUpdate=this.groleUpdate.bind(this);
|
||||||
|
if(channel){
|
||||||
|
channel.croleUpdate=this.croleUpdate.bind(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private groleUpdate(role:Role,added:1|0|-1){
|
||||||
|
if(!this.channel){
|
||||||
|
if(added===1){
|
||||||
|
this.permissions.push([role,role.permissions]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(added===-1){
|
||||||
|
this.permissions=this.permissions.filter(r=>r[0]!==role);
|
||||||
|
}
|
||||||
|
this.redoButtons();
|
||||||
|
}
|
||||||
|
private croleUpdate(role:Role,perm:Permissions,added:boolean){
|
||||||
|
if(added){
|
||||||
|
this.permissions.push([role,perm])
|
||||||
|
}else{
|
||||||
|
this.permissions=this.permissions.filter(r=>r[0]!==role);
|
||||||
|
}
|
||||||
|
this.redoButtons();
|
||||||
|
}
|
||||||
|
makeguildmenus(option:Options){
|
||||||
|
option.addButtonInput("","Display settings",()=>{
|
||||||
|
const role=this.guild.roleids.get(this.curid as string);
|
||||||
|
if(!role) return;
|
||||||
|
const form=option.addSubForm("Display settings",()=>{},{
|
||||||
|
fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid,
|
||||||
|
method:"PATCH",
|
||||||
|
headers:this.headers,
|
||||||
|
traditionalSubmit:true
|
||||||
|
});
|
||||||
|
form.addTextInput("Role Name:","name",{
|
||||||
|
initText:role.name
|
||||||
|
});
|
||||||
|
form.addCheckboxInput("Hoisted:","hoist",{
|
||||||
|
initState:role.hoist
|
||||||
|
});
|
||||||
|
form.addCheckboxInput("Allow anyone to ping this role:","mentionable",{
|
||||||
|
initState:role.mentionable
|
||||||
|
});
|
||||||
|
const color="#"+role.color.toString(16).padStart(6,"0");
|
||||||
|
form.addColorInput("Color","color",{
|
||||||
|
initColor:color
|
||||||
|
});
|
||||||
|
form.addPreprocessor((obj:any)=>{
|
||||||
|
obj.color=Number("0x"+obj.color.substring(1));
|
||||||
|
console.log(obj.color);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
static channelrolemenu=this.ChannelRoleMenu();
|
||||||
|
static guildrolemenu=this.GuildRoleMenu();
|
||||||
|
private static ChannelRoleMenu(){
|
||||||
|
const menu=new Contextmenu<RoleList,Role>("role settings");
|
||||||
|
menu.addbutton("Remove role",function(role){
|
||||||
|
if(!this.channel) return;
|
||||||
|
console.log(role);
|
||||||
|
fetch(this.info.api+"/channels/"+this.channel.id+"/permissions/"+role.id,{
|
||||||
|
method:"DELETE",
|
||||||
|
headers:this.headers
|
||||||
|
})
|
||||||
|
},null);
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
private static GuildRoleMenu(){
|
||||||
|
const menu=new Contextmenu<RoleList,Role>("role settings");
|
||||||
|
menu.addbutton("Delete Role",function(role){
|
||||||
|
if(!confirm("Are you sure you want to delete "+role.name+"?")) return;
|
||||||
|
console.log(role);
|
||||||
|
fetch(this.info.api+"/guilds/"+this.guild.id+"/roles/"+role.id,{
|
||||||
|
method:"DELETE",
|
||||||
|
headers:this.headers
|
||||||
|
})
|
||||||
|
},null);
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
redoButtons(){
|
||||||
|
this.buttons=[];
|
||||||
|
this.permissions.sort(([a],[b])=>b.position-a.position);
|
||||||
|
for(const i of this.permissions){
|
||||||
|
this.buttons.push([i[0].name, i[0].id]);
|
||||||
|
}
|
||||||
|
console.log("in here :P")
|
||||||
|
if(!this.buttonList)return;
|
||||||
|
console.log("in here :P");
|
||||||
|
const elms=Array.from(this.buttonList.children);
|
||||||
|
const div=elms[0] as HTMLDivElement;
|
||||||
|
const div2=elms[1] as HTMLDivElement;
|
||||||
|
console.log(div);
|
||||||
|
div.innerHTML="";
|
||||||
|
div.append(this.buttonListGen(div2));//not actually sure why the html is needed
|
||||||
|
}
|
||||||
|
buttonMap=new WeakMap<HTMLButtonElement,Role>();
|
||||||
|
dragged?:HTMLButtonElement;
|
||||||
|
buttonDragEvents(button:HTMLButtonElement,role:Role){
|
||||||
|
this.buttonMap.set(button,role);
|
||||||
|
button.addEventListener("dragstart", e=>{
|
||||||
|
this.dragged = button;
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("dragend", ()=>{
|
||||||
|
this.dragged = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("dragenter", event=>{
|
||||||
|
console.log("enter");
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("dragover", event=>{
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
button.addEventListener("drop", _=>{
|
||||||
|
const role2=this.buttonMap.get(this.dragged as HTMLButtonElement);
|
||||||
|
if(!role2) return;
|
||||||
|
const index2=this.guild.roles.indexOf(role2);
|
||||||
|
this.guild.roles.splice(index2,1);
|
||||||
|
const index=this.guild.roles.indexOf(role);
|
||||||
|
this.guild.roles.splice(index+1,0,role2);
|
||||||
|
this.guild.recalcRoles();
|
||||||
|
console.log(role);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buttonListGen(html:HTMLElement){
|
||||||
|
const buttonTable=document.createElement("div");
|
||||||
|
buttonTable.classList.add("flexttb");
|
||||||
|
|
||||||
|
const roleRow=document.createElement("div");
|
||||||
|
roleRow.classList.add("flexltr");
|
||||||
|
roleRow.append("Roles");
|
||||||
|
const add=document.createElement("span");
|
||||||
|
add.classList.add("svg-plus","svgicon","addrole");
|
||||||
|
add.onclick=async (e)=>{
|
||||||
|
const box=add.getBoundingClientRect();
|
||||||
|
e.stopPropagation();
|
||||||
|
if(this.channel){
|
||||||
|
const roles:[Role,string[]][]=[];
|
||||||
|
for(const role of this.guild.roles){
|
||||||
|
if(this.permissions.find(r=>r[0]==role)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
roles.push([role,[role.name]]);
|
||||||
|
}
|
||||||
|
const search=new Search(roles);
|
||||||
|
|
||||||
|
const found=await search.find(box.left,box.top);
|
||||||
|
|
||||||
|
|
||||||
|
if(!found) return;
|
||||||
|
console.log(found);
|
||||||
|
this.onchange(found.id,new Permissions("0","0"));
|
||||||
|
}else{
|
||||||
|
const bar=document.createElement("input");
|
||||||
|
bar.classList.add("fixedsearch");
|
||||||
|
bar.style.left=(box.left^0)+"px";
|
||||||
|
bar.style.top=(box.top^0)+"px";
|
||||||
|
document.body.append(bar);
|
||||||
|
if(Contextmenu.currentmenu != ""){
|
||||||
|
Contextmenu.currentmenu.remove();
|
||||||
|
}
|
||||||
|
Contextmenu.currentmenu=bar;
|
||||||
|
Contextmenu.keepOnScreen(bar);
|
||||||
|
bar.onchange=()=>{
|
||||||
|
bar.remove();
|
||||||
|
console.log(bar.value)
|
||||||
|
if(bar.value==="") return;
|
||||||
|
fetch(this.info.api+`/guilds/${this.guild.id}/roles`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:this.headers,
|
||||||
|
body:JSON.stringify({
|
||||||
|
color:0,
|
||||||
|
name:bar.value,
|
||||||
|
permissions:""
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roleRow.append(add);
|
||||||
|
|
||||||
|
buttonTable.append(roleRow);
|
||||||
|
for(const thing of this.buttons){
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.classList.add("SettingsButton");
|
||||||
|
button.textContent = thing[0];
|
||||||
|
const role=this.guild.roleids.get(thing[1]);
|
||||||
|
if(role){
|
||||||
|
if(!this.channel){
|
||||||
|
if(role.canManage()){
|
||||||
|
this.buttonDragEvents(button,role);
|
||||||
|
button.draggable=true;
|
||||||
|
RoleList.guildrolemenu.bindContextmenu(button,this,role)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if(role.canManage()){
|
||||||
|
RoleList.channelrolemenu.bindContextmenu(button,this,role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button.onclick = _=>{
|
||||||
|
this.generateHTMLArea(thing[1], html);
|
||||||
|
if(this.warndiv){
|
||||||
|
this.warndiv.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
buttonTable.append(button);
|
||||||
|
}
|
||||||
|
return buttonTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateButtons(html:HTMLElement):HTMLDivElement{
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("settingbuttons");
|
||||||
|
div.append(this.buttonListGen(html));
|
||||||
|
return div;
|
||||||
}
|
}
|
||||||
handleString(str: string): HTMLElement{
|
handleString(str: string): HTMLElement{
|
||||||
this.curid = str;
|
this.curid = str;
|
||||||
|
|
72
src/webpage/search.ts
Normal file
72
src/webpage/search.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { Contextmenu } from "./contextmenu.js";
|
||||||
|
|
||||||
|
class Search<E>{
|
||||||
|
options:Map<string,E>;
|
||||||
|
readonly keys:string[];
|
||||||
|
constructor(options:[E,string[]][]){
|
||||||
|
const map=options.flatMap(e=>{
|
||||||
|
const val=e[1].map(f=>[f,e[0]]);
|
||||||
|
return val as [string,E][];
|
||||||
|
})
|
||||||
|
this.options=new Map(map);
|
||||||
|
this.keys=[...this.options.keys()];
|
||||||
|
}
|
||||||
|
generateList(str:string,max:number,res:(e:E)=>void){
|
||||||
|
str=str.toLowerCase();
|
||||||
|
const options=this.keys.filter(e=>{
|
||||||
|
return e.toLowerCase().includes(str)
|
||||||
|
});
|
||||||
|
const div=document.createElement("div");
|
||||||
|
div.classList.add("OptionList","flexttb");
|
||||||
|
for(const option of options.slice(0, max)){
|
||||||
|
const hoption=document.createElement("span");
|
||||||
|
hoption.textContent=option;
|
||||||
|
hoption.onclick=()=>{
|
||||||
|
if(!this.options.has(option)) return;
|
||||||
|
res(this.options.get(option) as E)
|
||||||
|
}
|
||||||
|
div.append(hoption);
|
||||||
|
}
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
async find(x:number,y:number,max=4):Promise<E|undefined>{
|
||||||
|
return new Promise<E|undefined>((res)=>{
|
||||||
|
|
||||||
|
const container=document.createElement("div");
|
||||||
|
container.classList.add("fixedsearch");
|
||||||
|
console.log((x^0)+"",(y^0)+"");
|
||||||
|
container.style.left=(x^0)+"px";
|
||||||
|
container.style.top=(y^0)+"px";
|
||||||
|
const remove=container.remove;
|
||||||
|
container.remove=()=>{
|
||||||
|
remove.call(container);
|
||||||
|
res(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(e:E){
|
||||||
|
res(e);
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
const bar=document.createElement("input");
|
||||||
|
const options=document.createElement("div");
|
||||||
|
const keydown=()=>{
|
||||||
|
const html=this.generateList(bar.value,max,resolve);
|
||||||
|
options.innerHTML="";
|
||||||
|
options.append(html);
|
||||||
|
}
|
||||||
|
bar.oninput=keydown;
|
||||||
|
keydown();
|
||||||
|
bar.type="text";
|
||||||
|
container.append(bar);
|
||||||
|
container.append(options);
|
||||||
|
document.body.append(container);
|
||||||
|
if(Contextmenu.currentmenu != ""){
|
||||||
|
Contextmenu.currentmenu.remove();
|
||||||
|
}
|
||||||
|
Contextmenu.currentmenu=container;
|
||||||
|
Contextmenu.keepOnScreen(container);
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {Search};
|
|
@ -13,13 +13,13 @@ async function putInCache(request: URL | RequestInfo, response: Response){
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("test");
|
|
||||||
|
|
||||||
let lastcache: string;
|
let lastcache: string;
|
||||||
self.addEventListener("activate", async ()=>{
|
self.addEventListener("activate", async ()=>{
|
||||||
console.log("test2");
|
console.log("Service Worker activated");
|
||||||
checkCache();
|
checkCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function checkCache(){
|
async function checkCache(){
|
||||||
if(checkedrecently){
|
if(checkedrecently){
|
||||||
return;
|
return;
|
||||||
|
@ -34,7 +34,7 @@ async function checkCache(){
|
||||||
console.log(text, lastcache);
|
console.log(text, lastcache);
|
||||||
if(lastcache !== text){
|
if(lastcache !== text){
|
||||||
deleteoldcache();
|
deleteoldcache();
|
||||||
putInCache("/getupdates", data.clone());
|
putInCache("/getupdates", data);
|
||||||
}
|
}
|
||||||
checkedrecently = true;
|
checkedrecently = true;
|
||||||
setTimeout((_: any)=>{
|
setTimeout((_: any)=>{
|
||||||
|
@ -43,54 +43,99 @@ async function checkCache(){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var checkedrecently = false;
|
var checkedrecently = false;
|
||||||
|
|
||||||
function samedomain(url: string | URL){
|
function samedomain(url: string | URL){
|
||||||
return new URL(url).origin === self.origin;
|
return new URL(url).origin === self.origin;
|
||||||
}
|
}
|
||||||
function isindexhtml(url: string | URL){
|
|
||||||
console.log(url);
|
const htmlFiles=new Set(["/index","/login","/home","/register","/oauth2/auth"]);
|
||||||
if(new URL(url).pathname.startsWith("/channels")){
|
|
||||||
return true;
|
|
||||||
|
function isHtml(url:string):string|void{
|
||||||
|
const path=new URL(url).pathname;
|
||||||
|
if(htmlFiles.has(path)||htmlFiles.has(path+".html")){
|
||||||
|
return path+path.endsWith(".html")?"":".html";
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
async function getfile(event: {
|
let enabled="false";
|
||||||
request: { url: URL | RequestInfo; clone: () => string | URL | Request };
|
let offline=false;
|
||||||
}){
|
|
||||||
|
function toPath(url:string):string{
|
||||||
|
const Url= new URL(url);
|
||||||
|
let html=isHtml(url);
|
||||||
|
if(!html){
|
||||||
|
const path=Url.pathname;
|
||||||
|
if(path.startsWith("/channels")){
|
||||||
|
html="./index.html"
|
||||||
|
}else if(path.startsWith("/invite")){
|
||||||
|
html="./invite.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html||Url.pathname;
|
||||||
|
}
|
||||||
|
let fails=0;
|
||||||
|
async function getfile(event: FetchEvent):Promise<Response>{
|
||||||
checkCache();
|
checkCache();
|
||||||
if(!samedomain(event.request.url.toString())){
|
if(!samedomain(event.request.url)||enabled==="false"||(enabled==="offlineOnly"&&!offline)){
|
||||||
return await fetch(event.request.clone());
|
const responce=await fetch(event.request.clone());
|
||||||
|
if(samedomain(event.request.url)){
|
||||||
|
if(enabled==="offlineOnly"&&responce.ok){
|
||||||
|
putInCache(toPath(event.request.url),responce.clone());
|
||||||
}
|
}
|
||||||
const responseFromCache = await caches.match(event.request.url);
|
if(!responce.ok){
|
||||||
console.log(responseFromCache, caches);
|
fails++;
|
||||||
|
if(fails>5){
|
||||||
|
offline=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responce;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path=toPath(event.request.url);
|
||||||
|
if(path === "/instances.json"){
|
||||||
|
return await fetch(path);
|
||||||
|
}
|
||||||
|
console.log("Getting path: "+path);
|
||||||
|
const responseFromCache = await caches.match(path);
|
||||||
if(responseFromCache){
|
if(responseFromCache){
|
||||||
console.log("cache hit");
|
console.log("cache hit");
|
||||||
return responseFromCache;
|
return responseFromCache;
|
||||||
}
|
}
|
||||||
if(isindexhtml(event.request.url.toString())){
|
|
||||||
console.log("is index.html");
|
|
||||||
const responseFromCache = await caches.match("/index.html");
|
|
||||||
if(responseFromCache){
|
|
||||||
console.log("cache hit");
|
|
||||||
return responseFromCache;
|
|
||||||
}
|
|
||||||
const responseFromNetwork = await fetch("/index.html");
|
|
||||||
await putInCache("/index.html", responseFromNetwork.clone());
|
|
||||||
return responseFromNetwork;
|
|
||||||
}
|
|
||||||
const responseFromNetwork = await fetch(event.request.clone());
|
|
||||||
console.log(event.request.clone());
|
|
||||||
await putInCache(event.request.clone(), responseFromNetwork.clone());
|
|
||||||
try{
|
try{
|
||||||
|
const responseFromNetwork = await fetch(path);
|
||||||
|
if(responseFromNetwork.ok){
|
||||||
|
await putInCache(path, responseFromNetwork.clone());
|
||||||
|
}
|
||||||
return responseFromNetwork;
|
return responseFromNetwork;
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return e;
|
return new Response(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.addEventListener("fetch", (event: any)=>{
|
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (e)=>{
|
||||||
|
const event=e as FetchEvent;
|
||||||
try{
|
try{
|
||||||
event.respondWith(getfile(event));
|
event.respondWith(getfile(event));
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.addEventListener("message", (message)=>{
|
||||||
|
const data=message.data;
|
||||||
|
switch(data.code){
|
||||||
|
case "setMode":
|
||||||
|
enabled=data.data;
|
||||||
|
break;
|
||||||
|
case "CheckUpdate":
|
||||||
|
checkedrecently=false;
|
||||||
|
checkCache();
|
||||||
|
break;
|
||||||
|
case "ForceClear":
|
||||||
|
deleteoldcache();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -4,7 +4,7 @@ interface OptionsElement<x> {
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
readonly watchForChange: (func: (arg1: x) => void) => void;
|
readonly watchForChange: (func: (arg1: x) => void) => void;
|
||||||
value: x;
|
value: x;
|
||||||
}
|
}
|
||||||
//future me stuff
|
//future me stuff
|
||||||
class Buttons implements OptionsElement<unknown>{
|
class Buttons implements OptionsElement<unknown>{
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
@ -30,31 +30,37 @@ class Buttons implements OptionsElement<unknown>{
|
||||||
this.buttonList = buttonList;
|
this.buttonList = buttonList;
|
||||||
const htmlarea = document.createElement("div");
|
const htmlarea = document.createElement("div");
|
||||||
htmlarea.classList.add("flexgrow");
|
htmlarea.classList.add("flexgrow");
|
||||||
|
const buttonTable = this.generateButtons(htmlarea);
|
||||||
|
if(this.buttons[0]){
|
||||||
|
this.generateHTMLArea(this.buttons[0][1], htmlarea);
|
||||||
|
}
|
||||||
|
buttonList.append(buttonTable);
|
||||||
|
buttonList.append(htmlarea);
|
||||||
|
return buttonList;
|
||||||
|
}
|
||||||
|
generateButtons(optionsArea:HTMLElement){
|
||||||
const buttonTable = document.createElement("div");
|
const buttonTable = document.createElement("div");
|
||||||
buttonTable.classList.add("flexttb", "settingbuttons");
|
buttonTable.classList.add("settingbuttons");
|
||||||
for(const thing of this.buttons){
|
for(const thing of this.buttons){
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.classList.add("SettingsButton");
|
button.classList.add("SettingsButton");
|
||||||
button.textContent = thing[0];
|
button.textContent = thing[0];
|
||||||
button.onclick = _=>{
|
button.onclick = _=>{
|
||||||
this.generateHTMLArea(thing[1], htmlarea);
|
this.generateHTMLArea(thing[1], optionsArea);
|
||||||
if(this.warndiv){
|
if(this.warndiv){
|
||||||
this.warndiv.remove();
|
this.warndiv.remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
buttonTable.append(button);
|
buttonTable.append(button);
|
||||||
}
|
}
|
||||||
this.generateHTMLArea(this.buttons[0][1], htmlarea);
|
return buttonTable;
|
||||||
buttonList.append(buttonTable);
|
|
||||||
buttonList.append(htmlarea);
|
|
||||||
return buttonList;
|
|
||||||
}
|
}
|
||||||
handleString(str: string): HTMLElement{
|
handleString(str: string): HTMLElement{
|
||||||
const div = document.createElement("span");
|
const div = document.createElement("span");
|
||||||
div.textContent = str;
|
div.textContent = str;
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
private generateHTMLArea(
|
generateHTMLArea(
|
||||||
buttonInfo: Options | string,
|
buttonInfo: Options | string,
|
||||||
htmlarea: HTMLElement
|
htmlarea: HTMLElement
|
||||||
){
|
){
|
||||||
|
@ -202,8 +208,8 @@ class CheckboxInput implements OptionsElement<boolean>{
|
||||||
const input = this.input.deref();
|
const input = this.input.deref();
|
||||||
if(input){
|
if(input){
|
||||||
const value = input.checked as boolean;
|
const value = input.checked as boolean;
|
||||||
this.onchange(value);
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.onchange(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState(state:boolean){
|
setState(state:boolean){
|
||||||
|
@ -244,9 +250,12 @@ class ButtonInput implements OptionsElement<void>{
|
||||||
}
|
}
|
||||||
generateHTML(): HTMLDivElement{
|
generateHTML(): HTMLDivElement{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
if(this.label){
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
|
span.classList.add("inlinelabel");
|
||||||
span.textContent = this.label;
|
span.textContent = this.label;
|
||||||
div.append(span);
|
div.append(span);
|
||||||
|
}
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.textContent = this.textContent;
|
button.textContent = this.textContent;
|
||||||
button.onclick = this.onClickEvent.bind(this);
|
button.onclick = this.onClickEvent.bind(this);
|
||||||
|
@ -338,6 +347,8 @@ class SelectInput implements OptionsElement<number>{
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
span.textContent = this.label;
|
span.textContent = this.label;
|
||||||
div.append(span);
|
div.append(span);
|
||||||
|
const selectSpan = document.createElement("span");
|
||||||
|
selectSpan.classList.add("selectspan");
|
||||||
const select = document.createElement("select");
|
const select = document.createElement("select");
|
||||||
|
|
||||||
select.onchange = this.onChange.bind(this);
|
select.onchange = this.onChange.bind(this);
|
||||||
|
@ -348,7 +359,11 @@ class SelectInput implements OptionsElement<number>{
|
||||||
}
|
}
|
||||||
this.select = new WeakRef(select);
|
this.select = new WeakRef(select);
|
||||||
select.selectedIndex = this.index;
|
select.selectedIndex = this.index;
|
||||||
div.append(select);
|
selectSpan.append(select);
|
||||||
|
const selectArrow = document.createElement("span");
|
||||||
|
selectArrow.classList.add("svgicon","svg-category","selectarrow");
|
||||||
|
selectSpan.append(selectArrow);
|
||||||
|
div.append(selectSpan);
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
private onChange(){
|
private onChange(){
|
||||||
|
@ -438,11 +453,13 @@ class FileInput implements OptionsElement<FileList | null>{
|
||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
span.textContent = this.label;
|
span.textContent = this.label;
|
||||||
div.append(span);
|
div.append(span);
|
||||||
|
const innerDiv = document.createElement("div");
|
||||||
|
innerDiv.classList.add("flexltr","fileinputdiv");
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.oninput = this.onChange.bind(this);
|
input.oninput = this.onChange.bind(this);
|
||||||
this.input = new WeakRef(input);
|
this.input = new WeakRef(input);
|
||||||
div.append(input);
|
innerDiv.append(input);
|
||||||
if(this.clear){
|
if(this.clear){
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.textContent = "Clear";
|
button.textContent = "Clear";
|
||||||
|
@ -453,8 +470,9 @@ class FileInput implements OptionsElement<FileList | null>{
|
||||||
this.value = null;
|
this.value = null;
|
||||||
this.owner.changed();
|
this.owner.changed();
|
||||||
};
|
};
|
||||||
div.append(button);
|
innerDiv.append(button);
|
||||||
}
|
}
|
||||||
|
div.append(innerDiv);
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
onChange(){
|
onChange(){
|
||||||
|
@ -719,7 +737,7 @@ class Options implements OptionsElement<void>{
|
||||||
title: WeakRef<HTMLElement> = new WeakRef(document.createElement("h2"));
|
title: WeakRef<HTMLElement> = new WeakRef(document.createElement("h2"));
|
||||||
generateHTML(): HTMLElement{
|
generateHTML(): HTMLElement{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("titlediv");
|
div.classList.add("flexttb","titlediv");
|
||||||
const title = document.createElement("h2");
|
const title = document.createElement("h2");
|
||||||
title.textContent = this.name;
|
title.textContent = this.name;
|
||||||
div.append(title);
|
div.append(title);
|
||||||
|
@ -1054,6 +1072,10 @@ class Form implements OptionsElement<object>{
|
||||||
this.owner.changed();
|
this.owner.changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
preprocessor:(obj:Object)=>void=()=>{};
|
||||||
|
addPreprocessor(func:(obj:Object)=>void){
|
||||||
|
this.preprocessor=func;
|
||||||
|
}
|
||||||
async submit(){
|
async submit(){
|
||||||
if(this.options.subOptions){
|
if(this.options.subOptions){
|
||||||
this.options.subOptions.submit();
|
this.options.subOptions.submit();
|
||||||
|
@ -1118,6 +1140,7 @@ class Form implements OptionsElement<object>{
|
||||||
}
|
}
|
||||||
console.log("middle2");
|
console.log("middle2");
|
||||||
await Promise.allSettled(promises);
|
await Promise.allSettled(promises);
|
||||||
|
this.preprocessor(build);
|
||||||
if(this.fetchURL !== ""){
|
if(this.fetchURL !== ""){
|
||||||
fetch(this.fetchURL, {
|
fetch(this.fetchURL, {
|
||||||
method: this.method,
|
method: this.method,
|
||||||
|
@ -1199,7 +1222,7 @@ class Settings extends Buttons{
|
||||||
}
|
}
|
||||||
show(){
|
show(){
|
||||||
const background = document.createElement("div");
|
const background = document.createElement("div");
|
||||||
background.classList.add("background");
|
background.classList.add("flexttb","menu","background");
|
||||||
|
|
||||||
const title = document.createElement("h2");
|
const title = document.createElement("h2");
|
||||||
title.textContent = this.name;
|
title.textContent = this.name;
|
||||||
|
@ -1209,8 +1232,7 @@ class Settings extends Buttons{
|
||||||
background.append(this.generateHTML());
|
background.append(this.generateHTML());
|
||||||
|
|
||||||
const exit = document.createElement("span");
|
const exit = document.createElement("span");
|
||||||
exit.textContent = "✖";
|
exit.classList.add("exitsettings","svgicon","svg-x");
|
||||||
exit.classList.add("exitsettings");
|
|
||||||
background.append(exit);
|
background.append(exit);
|
||||||
exit.onclick = _=>{
|
exit.onclick = _=>{
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,178 +1,176 @@
|
||||||
:root {
|
:root {
|
||||||
--servertd-height: 0px;
|
--font: "acumin-pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
/* Default value */
|
--black: #000000;
|
||||||
--red: red;
|
--red: #ff5555;
|
||||||
--green: green;
|
--yellow: #ffc159;
|
||||||
--yellow:yellow;
|
--green: #1c907b;
|
||||||
--accent-color:#242443;
|
|
||||||
}
|
}
|
||||||
.Dark-theme { /* thanks to TomatoCake for the updated CSS vars and such*/
|
|
||||||
|
/* Themes. See themes.txt */
|
||||||
|
.Dark-theme {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
--primary-text: #FFF;
|
|
||||||
--primary-bg: color-mix(in srgb, #2f2f2f 70%, var(--accent-color));
|
--primary-bg: #303339;
|
||||||
--black: #000;
|
--primary-hover: #272b31;
|
||||||
--shadow: #000;
|
--primary-text: #dfdfdf;
|
||||||
--focus: #8888ff;
|
--primary-text-soft: #adb8b9;
|
||||||
--message-bg-hover: color-mix(in srgb, #191919 85%, var(--accent-color));
|
|
||||||
--typing-bg: #161616;
|
--secondary-bg: #16191b;
|
||||||
--timestamp-color: #a2a2a2;
|
--secondary-hover: #252b2c;
|
||||||
--code-bg: color-mix(in srgb, #121212 95%, var(--accent-color));
|
|
||||||
--user-info-bg: color-mix(in srgb,#383838 85%, var(--accent-color));
|
--servers-bg: #141718;
|
||||||
--user-dock-bg: color-mix(in srgb,#111111 90%, var(--accent-color));
|
--channels-bg: #25282b;
|
||||||
--channels-bg: color-mix(in srgb, #2a2a2a 90%, var(--accent-color));
|
--channel-selected: #3c4046;
|
||||||
--channel-hover: color-mix(in srgb, #121212 70%, var(--accent-color));
|
--typebox-bg: #3a3e45;
|
||||||
--blank-bg: #323232;
|
|
||||||
--light-border: #92929B;
|
--button-bg: #4e5457;
|
||||||
--settings-hover: color-mix(in srgb, #000000 95%, var(--accent-color) 5%);
|
--button-hover: #6b7174;
|
||||||
--quote-bg: #7a798e;
|
--spoiler-bg: #000000;
|
||||||
--button-bg: color-mix(in srgb, #191919 85%, var(--accent-color));
|
--link: #5ca9ed;
|
||||||
--button-hover: color-mix(in srgb, #2f2f2f 70%, var(--accent-color));
|
|
||||||
--textarea-bg: color-mix(in srgb, #484848 80%, var(--accent-color));
|
--primary-text-prominent: #efefef;
|
||||||
--filename: #47bbff;
|
--dock-bg: #1b1e20;
|
||||||
--mention-bg: #F00;
|
--card-bg: #000000;
|
||||||
--mention-md-bg: #3b588b;
|
|
||||||
--pronouns: #797979;
|
|
||||||
--profile-bg: color-mix(in srgb, #232323 90%, var(--accent-color));
|
|
||||||
--profile-info-bg: color-mix(in srgb, #121212 90%, var(--accent-color));
|
|
||||||
--server-border: color-mix(in srgb, #000000 80%, var(--accent-color));
|
|
||||||
--channel-name-bg: color-mix(in srgb, #2a2a2a 80%, var(--accent-color));
|
|
||||||
--server-name-bg: color-mix(in srgb, #232323 90%, var(--accent-color));
|
|
||||||
--reply-border: #474b76;
|
|
||||||
--reply-bg: #0b0d20;
|
|
||||||
--reply-text: #acacac;
|
|
||||||
--spoiler-hover: #111111;
|
|
||||||
--spoiler-open-bg: #1e1e1e;
|
|
||||||
--unknown-file-bg: #141316;
|
|
||||||
--unknown-file-border: #474555;
|
|
||||||
--login-border: #131315;
|
|
||||||
--loading-bg: #22232c;
|
|
||||||
--dialog-bg: #33363d;
|
|
||||||
--dialog-border: #1c1b31;
|
|
||||||
--scrollbar-track: #34313c;
|
|
||||||
--scrollbar-thumb: #201f29;
|
|
||||||
--scrollbar-thumb-hover: #16161f;
|
|
||||||
--markdown-timestamp: #2f2f33;
|
|
||||||
--embed: color-mix(in srgb, #131313 90%, var(--accent-color));
|
|
||||||
--link: #8888ff;
|
|
||||||
--discovery-bg: #37373b;
|
|
||||||
--message-jump:#7678b0;
|
|
||||||
--icon-color:white;
|
|
||||||
--server-list: color-mix(in srgb, #1d1d1d 90%, var(--accent-color));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.WHITE-theme {
|
.WHITE-theme {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
--primary-text: #000;
|
|
||||||
--primary-bg: #FFF;
|
--primary-bg: #fefefe;
|
||||||
--black: #FFF;
|
--primary-hover: #f6f6f9;
|
||||||
--red: #dd6c6c;
|
--primary-text: #4b4b59;
|
||||||
--green: #639d63;
|
--primary-text-soft: #656575;
|
||||||
--shadow: #777;
|
|
||||||
--focus: #47bbff;
|
--secondary-bg: #e0e0ea;
|
||||||
--message-bg-hover: #dedee2;
|
--secondary-hover: #d0d0dd;
|
||||||
--typing-bg: #dad8d8;
|
|
||||||
--timestamp-color: #929297;
|
--servers-bg: #b4b4ca;
|
||||||
--code-bg: #cbcbcc;
|
--channels-bg: #eaeaf0;
|
||||||
--user-info-bg: #b0abc2;
|
--channel-selected: #c7c7d9;
|
||||||
--user-dock-bg: #b2b2c4;
|
--typebox-bg: #ededf4;
|
||||||
--channels-bg: #cbcbd8;
|
|
||||||
--channel-hover: #b8b5cc;
|
--button-bg: #cacad8;
|
||||||
--blank-bg: #ceccdd;
|
--button-hover: #b3b3c4;
|
||||||
--light-border: #96959e;
|
--spoiler-bg: #dadada;
|
||||||
--settings-hover: #b5b1bb;
|
--link: #056cd9;
|
||||||
--quote-bg: #7a798e;
|
|
||||||
--button-bg: #b7b7cc;
|
--black: #4b4b59;
|
||||||
--button-hover: #FFF;
|
--green: #68d79d;
|
||||||
--textarea-bg: #b1b6ce;
|
--primary-text-prominent: #08080d;
|
||||||
--filename: #47bbff;
|
--secondary-text: #3c3c46;
|
||||||
--mention-bg: #F00;
|
--secondary-text-soft: #4c4c5a;
|
||||||
--mention-md-bg: #3b588b;
|
--dock-bg: #d1d1df;
|
||||||
--pronouns: #6a6a6d;
|
--dock-hover: #b8b8d0;
|
||||||
--profile-bg: #cacad8;
|
|
||||||
--profile-info-bg: #bbbbce;
|
|
||||||
--server-border: #bebed3;
|
|
||||||
--channel-name-bg: #c0c0d4;
|
|
||||||
--server-name-bg: #a3a3b5;
|
|
||||||
--reply-border: #b1b2bd;
|
|
||||||
--reply-bg: #d4d6e9;
|
|
||||||
--reply-text: #2e2e30;
|
|
||||||
--spoiler-hover: #b9b9b9;
|
|
||||||
--spoiler-open-bg: #dadada;
|
|
||||||
--unknown-file-bg: #bdbdbd;
|
|
||||||
--unknown-file-border: #adadad;
|
|
||||||
--login-border: #c3c0e0;
|
|
||||||
--loading-bg: #b5b7cc;
|
|
||||||
--dialog-bg: #c1c8d6;
|
|
||||||
--dialog-border: #b9b7db;
|
|
||||||
--scrollbar-track: #d5d1e2;
|
|
||||||
--scrollbar-thumb: #b0afc0;
|
|
||||||
--scrollbar-thumb-hover: #a5a5b8;
|
|
||||||
--markdown-timestamp: #c8c8da;
|
|
||||||
--embed: #f2f3f5;
|
|
||||||
--link: #3333ee;
|
|
||||||
--discovery-bg: #c6c6d8;
|
|
||||||
--message-jump:#ccceff;
|
|
||||||
--icon-color:black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Light-theme {
|
.Light-theme {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
--primary-text: #000;
|
--primary-bg: #aaafce;
|
||||||
--primary-bg: #8e90c3;
|
--primary-hover: #b1b6d4;
|
||||||
--black: #fff;
|
--primary-text: #060415;
|
||||||
--shadow: #000;
|
--primary-text-soft: #424268;
|
||||||
--focus: #5e50c5;
|
|
||||||
|
|
||||||
--message-bg-hover: #5757b5;
|
--secondary-bg: #9397bd;
|
||||||
--typing-bg: #d4d6e9;
|
--secondary-hover: #9ea5cc;
|
||||||
--profile-bg: #8075bf;
|
|
||||||
--profile-info-bg: #8075bf;
|
|
||||||
--timestamp-color: #000000;
|
|
||||||
--code-bg: #a89adf;
|
|
||||||
--info-bg: #6060a3;
|
|
||||||
--user-info-bg: #796f9a;
|
|
||||||
--user-dock-bg: #83839d;
|
|
||||||
--channels-bg: #c2c2d1;
|
|
||||||
--channel-hover: #726e88;
|
|
||||||
--blank-bg: #5e50c5;
|
|
||||||
--light-border: #000000;
|
|
||||||
--settings-hover: #b5b1bb;
|
|
||||||
--quote-bg: #7a798e;
|
|
||||||
--button-bg: #5757b5;
|
|
||||||
--button-hover: #8e90c3;
|
|
||||||
--textarea-bg: #abb1cd;
|
|
||||||
--filename: #47bbff;
|
|
||||||
--mention-bg: #F00;
|
|
||||||
--mention-md-bg: #3b588b;
|
|
||||||
--pronouns: #202020;
|
|
||||||
--channel-name-bg: #c0c0d4;
|
|
||||||
--server-name-bg: #a3a3b5;
|
|
||||||
|
|
||||||
--server-border: #aaaac4;
|
--servers-bg: #7a7aaa;
|
||||||
--server-hover: #7f7fa8;
|
--channels-bg: #babdd2;
|
||||||
|
--channel-selected: #9c9fbf;
|
||||||
|
--typebox-bg: #bac0df;
|
||||||
|
|
||||||
--reply-border: #474b76;
|
--button-bg: #babdd2;
|
||||||
--reply-bg: #d4d6e9;
|
--button-hover: #9c9fbf;
|
||||||
--reply-text: #38383d;
|
--spoiler-bg: #34333a;
|
||||||
|
--link: #283c8b;
|
||||||
|
|
||||||
--spoiler-hover: #34333a;
|
--black: #434392;
|
||||||
--spoiler-open-bg: #767587;
|
--red: #ca304d;
|
||||||
|
--secondary-text-soft: #211f2e;
|
||||||
--unknown-file-bg: #bdbdbd;
|
--blank-bg: #494985;
|
||||||
--unknown-file-border: #adadad;
|
--spoiler-text: #e4e6ed;
|
||||||
|
}
|
||||||
--login-border: #c3c0e0;
|
|
||||||
--loading-bg: #b5b7cc;
|
.Dark-Accent-theme {
|
||||||
|
color-scheme: dark;
|
||||||
--dialog-bg: #c1c8d6;
|
|
||||||
--dialog-border: #b9b7db;
|
--primary-bg: color-mix(in srgb, #3f3f3f 65%, var(--accent-color));
|
||||||
|
--primary-hover: color-mix(in srgb, #373737 68%, var(--accent-color));
|
||||||
--scrollbar-track: #d2cedf;
|
--primary-text: #ebebeb;
|
||||||
--scrollbar-thumb: #bdbcca;
|
--primary-text-soft: #ebebebb8;
|
||||||
--scrollbar-thumb-hover: #a7a7be;
|
|
||||||
--markdown-timestamp: #c8c8da;
|
--secondary-bg: color-mix(in srgb, #222222 72%, var(--accent-color));
|
||||||
--embed: #cdccd1;
|
--secondary-hover: color-mix(in srgb, #222222 65%, var(--accent-color));
|
||||||
--link: #5566cc;
|
|
||||||
--discovery-bg: #c6c6d8;
|
--servers-bg: color-mix(in srgb, #0b0b0b 70%, var(--accent-color));
|
||||||
--message-jump:#ccceff;
|
--channels-bg: color-mix(in srgb, #292929 68%, var(--accent-color));
|
||||||
--icon-color:black;
|
--channel-selected: color-mix(in srgb, #555555 65%, var(--accent-color));
|
||||||
|
--typebox-bg: color-mix(in srgb, #666666 60%, var(--accent-color));
|
||||||
|
|
||||||
|
--button-bg: color-mix(in srgb, #777777 56%, var(--accent-color));
|
||||||
|
--button-hover: color-mix(in srgb, #585858 58%, var(--accent-color));
|
||||||
|
|
||||||
|
--spoiler: color-mix(in srgb, #101010 72%, var(--accent-color));
|
||||||
|
--link: color-mix(in srgb, #99ccff 75%, var(--accent-color));
|
||||||
|
|
||||||
|
--black: color-mix(in srgb, #000000 90%, var(--accent-color));
|
||||||
|
--icon: color-mix(in srgb, #ffffff, var(--accent-color));
|
||||||
|
--dock-bg: color-mix(in srgb, #171717 68%, var(--accent-color));
|
||||||
|
--spoiler-hover: color-mix(in srgb, #111111 80%, var(--accent-color));
|
||||||
|
--card-bg: color-mix(in srgb, #0b0b0b 70%, var(--accent-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional Variables */
|
||||||
|
body {
|
||||||
|
--primary-text-prominent: var(--primary-text);
|
||||||
|
--secondary-text: var(--primary-text);
|
||||||
|
--secondary-text-soft: var(--primary-text-soft);
|
||||||
|
--text-input-bg: var(--secondary-bg);
|
||||||
|
--button-text: var(--primary-text);
|
||||||
|
--button-disabled-text: color-mix(in srgb, var(--button-text), transparent);
|
||||||
|
|
||||||
|
--icon: var(--accent-color);
|
||||||
|
--focus: var(--accent-color);
|
||||||
|
--shadow: color-mix(in srgb, var(--black) 30%, transparent);
|
||||||
|
--scrollbar: var(--primary-text-soft);
|
||||||
|
--scrollbar-track: var(--primary-hover);
|
||||||
|
|
||||||
|
--blank-bg: var(--channels-bg);
|
||||||
|
--divider: color-mix(in srgb, var(--primary-text), transparent);
|
||||||
|
--channels-header-bg: var(--channels-bg);
|
||||||
|
--channel-hover: color-mix(in srgb, var(--channel-selected) 60%, transparent);
|
||||||
|
--dock-bg: var(--secondary-bg);
|
||||||
|
--dock-hover: var(--secondary-hover);
|
||||||
|
--user-info-bg: var(--dock-bg);
|
||||||
|
--user-info-text: var(--secondary-text);
|
||||||
|
|
||||||
|
--main-header-bg: transparent;
|
||||||
|
--message-jump-bg: color-mix(in srgb, var(--accent-color) 20%, transparent);
|
||||||
|
--code-bg: var(--secondary-bg);
|
||||||
|
--code-text: var(--secondary-text);
|
||||||
|
--spoiler-text: var(--primary-text);
|
||||||
|
--spoiler-hover: color-mix(in srgb, var(--spoiler-bg), var(--primary-text-soft) 10%);
|
||||||
|
--quote-line: color-mix(in srgb, var(--primary-text-soft), transparent);
|
||||||
|
--reply-line: color-mix(in srgb, var(--primary-text-soft) 20%, transparent);
|
||||||
|
--reply-text: var(--primary-text-soft);
|
||||||
|
--reply-highlight: var(--accent-color);
|
||||||
|
--mention: color-mix(in srgb, var(--accent-color) 80%, transparent);
|
||||||
|
--mention-highlight: var(--yellow);
|
||||||
|
--reaction-bg: var(--secondary-bg);
|
||||||
|
--reaction-reacted-bg: var(--secondary-hover);
|
||||||
|
--filename: var(--link);
|
||||||
|
--embed-bg: var(--secondary-bg);
|
||||||
|
|
||||||
|
--sidebar-bg: var(--channels-bg);
|
||||||
|
--sidebar-hover: var(--channel-hover);
|
||||||
|
--card-bg: var(--primary-bg);
|
||||||
|
--role-bg: var(--primary-bg);
|
||||||
|
--role-text: var(--primary-text);
|
||||||
|
--settings-bg: var(--primary-bg);
|
||||||
|
--settings-header-bg: var(--main-header-bg);
|
||||||
|
--settings-panel-bg: var(--channels-bg);
|
||||||
|
--settings-panel-selected: var(--channel-selected);
|
||||||
|
--settings-panel-hover: color-mix(in srgb, var(--settings-panel-selected), transparent);
|
||||||
|
--loading-bg: var(--secondary-bg);
|
||||||
|
--loading-text: var(--secondary-text);
|
||||||
}
|
}
|
16
src/webpage/translations/en.json
Normal file
16
src/webpage/translations/en.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"@metadata": {
|
||||||
|
"authors": [
|
||||||
|
"MathMan05"
|
||||||
|
],
|
||||||
|
"last-updated": "2024/15/24",
|
||||||
|
"locale": "en",
|
||||||
|
"comment":"Don't know how often I'll update this top part lol"
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"reply": "Reply",
|
||||||
|
"copyrawtext":"Copy raw text",
|
||||||
|
"copymessageid":"Copy message id"
|
||||||
|
},
|
||||||
|
"ru": "./ru.json"
|
||||||
|
}
|
12
src/webpage/translations/ru.json
Normal file
12
src/webpage/translations/ru.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"@metadata": {
|
||||||
|
"authors": [
|
||||||
|
],
|
||||||
|
"last-updated": "2024/15/24",
|
||||||
|
"locale": "ru",
|
||||||
|
"comment":"I need some help with this :P"
|
||||||
|
},
|
||||||
|
"ru": {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import{ Localuser }from"./localuser.js";
|
||||||
import{ Guild }from"./guild.js";
|
import{ Guild }from"./guild.js";
|
||||||
import{ SnowFlake }from"./snowflake.js";
|
import{ SnowFlake }from"./snowflake.js";
|
||||||
import{ presencejson, userjson }from"./jsontypes.js";
|
import{ presencejson, userjson }from"./jsontypes.js";
|
||||||
|
import { Role } from "./role.js";
|
||||||
|
import { Search } from "./search.js";
|
||||||
|
|
||||||
class User extends SnowFlake{
|
class User extends SnowFlake{
|
||||||
owner: Localuser;
|
owner: Localuser;
|
||||||
|
@ -174,6 +176,58 @@ class User extends SnowFlake{
|
||||||
return us.hasPermission("BAN_MEMBERS") || false;
|
return us.hasPermission("BAN_MEMBERS") || false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.contextmenu.addbutton(
|
||||||
|
"Add roles",
|
||||||
|
async function(this: User, member: Member | undefined,e){
|
||||||
|
if(member){
|
||||||
|
e.stopPropagation();
|
||||||
|
const roles:[Role,string[]][]=[];
|
||||||
|
for(const role of member.guild.roles){
|
||||||
|
if(!role.canManage()||member.roles.indexOf(role)!==-1){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
roles.push([role,[role.name]]);
|
||||||
|
}
|
||||||
|
const search=new Search(roles);
|
||||||
|
const result=await search.find(e.x,e.y);
|
||||||
|
if(!result) return;
|
||||||
|
member.addRole(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
member=>{
|
||||||
|
if(!member)return false;
|
||||||
|
const us = member.guild.member;
|
||||||
|
console.log(us.hasPermission("MANAGE_ROLES"))
|
||||||
|
return us.hasPermission("MANAGE_ROLES") || false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.contextmenu.addbutton(
|
||||||
|
"Remove roles",
|
||||||
|
async function(this: User, member: Member | undefined,e){
|
||||||
|
if(member){
|
||||||
|
e.stopPropagation();
|
||||||
|
const roles:[Role,string[]][]=[];
|
||||||
|
for(const role of member.roles){
|
||||||
|
if(!role.canManage()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
roles.push([role,[role.name]]);
|
||||||
|
}
|
||||||
|
const search=new Search(roles);
|
||||||
|
const result=await search.find(e.x,e.y);
|
||||||
|
if(!result) return;
|
||||||
|
member.removeRole(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
member=>{
|
||||||
|
if(!member)return false;
|
||||||
|
const us = member.guild.member;
|
||||||
|
console.log(us.hasPermission("MANAGE_ROLES"))
|
||||||
|
return us.hasPermission("MANAGE_ROLES") || false;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkuser(user: User | userjson, owner: Localuser): User{
|
static checkuser(user: User | userjson, owner: Localuser): User{
|
||||||
|
@ -245,7 +299,7 @@ class User extends SnowFlake{
|
||||||
|
|
||||||
async buildstatuspfp(): Promise<HTMLDivElement>{
|
async buildstatuspfp(): Promise<HTMLDivElement>{
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.style.position = "relative";
|
div.classList.add("pfpDiv")
|
||||||
const pfp = this.buildpfp();
|
const pfp = this.buildpfp();
|
||||||
div.append(pfp);
|
div.append(pfp);
|
||||||
const status = document.createElement("div");
|
const status = document.createElement("div");
|
||||||
|
@ -301,7 +355,7 @@ class User extends SnowFlake{
|
||||||
localuser.info.api.toString() + "/users/" + id + "/profile",
|
localuser.info.api.toString() + "/users/" + id + "/profile",
|
||||||
{ headers: localuser.headers }
|
{ headers: localuser.headers }
|
||||||
).then(res=>res.json());
|
).then(res=>res.json());
|
||||||
return new User(json, localuser);
|
return new User(json.user, localuser);
|
||||||
}
|
}
|
||||||
|
|
||||||
changepfp(update: string | null): void{
|
changepfp(update: string | null): void{
|
||||||
|
@ -399,7 +453,7 @@ class User extends SnowFlake{
|
||||||
div.classList.add("profile", "flexttb");
|
div.classList.add("profile", "flexttb");
|
||||||
}else{
|
}else{
|
||||||
this.setstatus("online");
|
this.setstatus("online");
|
||||||
div.classList.add("hypoprofile", "flexttb");
|
div.classList.add("hypoprofile", "profile", "flexttb");
|
||||||
}
|
}
|
||||||
const badgediv = document.createElement("div");
|
const badgediv = document.createElement("div");
|
||||||
badgediv.classList.add("badges");
|
badgediv.classList.add("badges");
|
||||||
|
@ -426,7 +480,7 @@ class User extends SnowFlake{
|
||||||
const pfp = await this.buildstatuspfp();
|
const pfp = await this.buildstatuspfp();
|
||||||
div.appendChild(pfp);
|
div.appendChild(pfp);
|
||||||
const userbody = document.createElement("div");
|
const userbody = document.createElement("div");
|
||||||
userbody.classList.add("infosection");
|
userbody.classList.add("flexttb","infosection");
|
||||||
div.appendChild(userbody);
|
div.appendChild(userbody);
|
||||||
const usernamehtml = document.createElement("h2");
|
const usernamehtml = document.createElement("h2");
|
||||||
usernamehtml.textContent = this.username;
|
usernamehtml.textContent = this.username;
|
||||||
|
@ -449,8 +503,9 @@ class User extends SnowFlake{
|
||||||
if(guild){
|
if(guild){
|
||||||
Member.resolveMember(this, guild).then(member=>{
|
Member.resolveMember(this, guild).then(member=>{
|
||||||
if(!member)return;
|
if(!member)return;
|
||||||
|
usernamehtml.textContent=member.name;
|
||||||
const roles = document.createElement("div");
|
const roles = document.createElement("div");
|
||||||
roles.classList.add("rolesbox");
|
roles.classList.add("flexltr","rolesbox");
|
||||||
for(const role of member.roles){
|
for(const role of member.roles){
|
||||||
const roleDiv = document.createElement("div");
|
const roleDiv = document.createElement("div");
|
||||||
roleDiv.classList.add("rolediv");
|
roleDiv.classList.add("rolediv");
|
||||||
|
|
652
src/webpage/voice.ts
Normal file
652
src/webpage/voice.ts
Normal file
|
@ -0,0 +1,652 @@
|
||||||
|
import { memberjson, sdpback, voiceserverupdate, voiceupdate, webRTCSocket } from "./jsontypes.js";
|
||||||
|
|
||||||
|
class VoiceFactory{
|
||||||
|
settings:{id:string};
|
||||||
|
constructor(usersettings:VoiceFactory["settings"]){
|
||||||
|
this.settings=usersettings;
|
||||||
|
}
|
||||||
|
voices=new Map<string,Map<string,Voice>>();
|
||||||
|
voiceChannels=new Map<string,Voice>();
|
||||||
|
currentVoice?:Voice;
|
||||||
|
guildUrlMap=new Map<string,{url?:string,geturl:Promise<void>,gotUrl:()=>void}>();
|
||||||
|
makeVoice(guildid:string,channelId:string,settings:Voice["settings"]){
|
||||||
|
let guild=this.voices.get(guildid);
|
||||||
|
if(!guild){
|
||||||
|
this.setUpGuild(guildid);
|
||||||
|
guild=new Map();
|
||||||
|
this.voices.set(guildid,guild);
|
||||||
|
}
|
||||||
|
const urlobj=this.guildUrlMap.get(guildid);
|
||||||
|
if(!urlobj) throw new Error("url Object doesn't exist (InternalError)");
|
||||||
|
const voice=new Voice(this.settings.id,settings,urlobj);
|
||||||
|
this.voiceChannels.set(channelId,voice);
|
||||||
|
guild.set(channelId,voice);
|
||||||
|
return voice;
|
||||||
|
}
|
||||||
|
onJoin=(_voice:Voice)=>{};
|
||||||
|
onLeave=(_voice:Voice)=>{};
|
||||||
|
joinVoice(channelId:string,guildId:string){
|
||||||
|
if(this.currentVoice){
|
||||||
|
this.currentVoice.leave();
|
||||||
|
}
|
||||||
|
const voice=this.voiceChannels.get(channelId);
|
||||||
|
if(!voice) throw new Error(`Voice ${channelId} does not exist`);
|
||||||
|
voice.join();
|
||||||
|
this.currentVoice=voice;
|
||||||
|
this.onJoin(voice);
|
||||||
|
return {
|
||||||
|
d:{
|
||||||
|
guild_id: guildId,
|
||||||
|
channel_id: channelId,
|
||||||
|
self_mute: true,//todo
|
||||||
|
self_deaf: false,//todo
|
||||||
|
self_video: false,//What is this? I have some guesses
|
||||||
|
flags: 2//?????
|
||||||
|
},
|
||||||
|
op:4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userMap=new Map<string,Voice>();
|
||||||
|
voiceStateUpdate(update:voiceupdate){
|
||||||
|
|
||||||
|
const prev=this.userMap.get(update.d.user_id);
|
||||||
|
console.log(prev,this.userMap);
|
||||||
|
if(prev){
|
||||||
|
prev.disconnect(update.d.user_id);
|
||||||
|
this.onLeave(prev);
|
||||||
|
}
|
||||||
|
const voice=this.voiceChannels.get(update.d.channel_id);
|
||||||
|
if(voice){
|
||||||
|
this.userMap.set(update.d.user_id,voice);
|
||||||
|
voice.voiceupdate(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private setUpGuild(id:string){
|
||||||
|
const obj:{url?:string,geturl?:Promise<void>,gotUrl?:()=>void}={};
|
||||||
|
obj.geturl=new Promise<void>(res=>{obj.gotUrl=res});
|
||||||
|
this.guildUrlMap.set(id,obj as {geturl:Promise<void>,gotUrl:()=>void});
|
||||||
|
}
|
||||||
|
voiceServerUpdate(update:voiceserverupdate){
|
||||||
|
const obj=this.guildUrlMap.get(update.d.guild_id);
|
||||||
|
if(!obj) return;
|
||||||
|
obj.url=update.d.endpoint;
|
||||||
|
obj.gotUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Voice{
|
||||||
|
private pstatus:string="not connected";
|
||||||
|
public onSatusChange:(e:string)=>unknown=()=>{};
|
||||||
|
set status(e:string){
|
||||||
|
this.pstatus=e;
|
||||||
|
this.onSatusChange(e);
|
||||||
|
}
|
||||||
|
get status(){
|
||||||
|
return this.pstatus;
|
||||||
|
}
|
||||||
|
readonly userid:string;
|
||||||
|
settings:{bitrate:number};
|
||||||
|
urlobj:{url?:string,geturl:Promise<void>,gotUrl:()=>void};
|
||||||
|
constructor(userid:string,settings:Voice["settings"],urlobj:Voice["urlobj"]){
|
||||||
|
this.userid=userid;
|
||||||
|
this.settings=settings;
|
||||||
|
this.urlobj=urlobj;
|
||||||
|
}
|
||||||
|
pc?:RTCPeerConnection;
|
||||||
|
ws?:WebSocket;
|
||||||
|
timeout:number=30000;
|
||||||
|
interval:NodeJS.Timeout=0 as unknown as NodeJS.Timeout;
|
||||||
|
time:number=0;
|
||||||
|
seq:number=0;
|
||||||
|
sendAlive(){
|
||||||
|
if(this.ws){
|
||||||
|
this.ws.send(JSON.stringify({ op: 3,d:10}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readonly users= new Map<number,string>();
|
||||||
|
readonly speakingMap= new Map<string,number>();
|
||||||
|
onSpeakingChange=(_userid:string,_speaking:number)=>{};
|
||||||
|
disconnect(userid:string){
|
||||||
|
console.warn(userid);
|
||||||
|
if(userid===this.userid){
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
|
const ssrc=this.speakingMap.get(userid);
|
||||||
|
|
||||||
|
if(ssrc){
|
||||||
|
this.users.delete(ssrc);
|
||||||
|
for(const thing of this.ssrcMap){
|
||||||
|
if(thing[1]===ssrc){
|
||||||
|
this.ssrcMap.delete(thing[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.speakingMap.delete(userid);
|
||||||
|
this.userids.delete(userid);
|
||||||
|
console.log(this.userids,userid);
|
||||||
|
//there's more for sure, but this is "good enough" for now
|
||||||
|
this.onMemberChange(userid,false);
|
||||||
|
}
|
||||||
|
packet(message:MessageEvent){
|
||||||
|
const data=message.data
|
||||||
|
if(typeof data === "string"){
|
||||||
|
const json:webRTCSocket = JSON.parse(data);
|
||||||
|
switch(json.op){
|
||||||
|
case 2:
|
||||||
|
this.startWebRTC();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.continueWebRTC(json);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.speakingMap.set(json.d.user_id,json.d.speaking);
|
||||||
|
this.onSpeakingChange(json.d.user_id,json.d.speaking);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
this.time=json.d.t;
|
||||||
|
setTimeout(this.sendAlive.bind(this), this.timeout);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
this.timeout=json.d.heartbeat_interval;
|
||||||
|
setTimeout(this.sendAlive.bind(this), 1000);
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
this.figureRecivers();
|
||||||
|
if(!this.users.has(json.d.audio_ssrc)){
|
||||||
|
console.log("redo 12!");
|
||||||
|
this.makeOp12();
|
||||||
|
}
|
||||||
|
this.users.set(json.d.audio_ssrc,json.d.user_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offer?:string;
|
||||||
|
cleanServerSDP(sdp:string):string{
|
||||||
|
const pc=this.pc;
|
||||||
|
if(!pc) throw new Error("pc isn't defined")
|
||||||
|
const ld=pc.localDescription;
|
||||||
|
if(!ld) throw new Error("localDescription isn't defined");
|
||||||
|
const parsed = Voice.parsesdp(ld.sdp);
|
||||||
|
const group=parsed.atr.get("group");
|
||||||
|
if(!group) throw new Error("group isn't in sdp");
|
||||||
|
const [_,...bundles]=(group.entries().next().value as [string, string])[0].split(" ");
|
||||||
|
bundles[bundles.length-1]=bundles[bundles.length-1].replace("\r","");
|
||||||
|
console.log(bundles);
|
||||||
|
|
||||||
|
if(!this.offer) throw new Error("Offer is missing :P");
|
||||||
|
let cline=sdp.split("\n").find(line=>line.startsWith("c="));
|
||||||
|
if(!cline) throw new Error("c line wasn't found");
|
||||||
|
const parsed1=Voice.parsesdp(sdp).medias[0];
|
||||||
|
//const parsed2=Voice.parsesdp(this.offer);
|
||||||
|
const rtcport=(parsed1.atr.get("rtcp") as Set<string>).values().next().value as string;
|
||||||
|
const ICE_UFRAG=(parsed1.atr.get("ice-ufrag") as Set<string>).values().next().value as string;
|
||||||
|
const ICE_PWD=(parsed1.atr.get("ice-pwd") as Set<string>).values().next().value as string;
|
||||||
|
const FINGERPRINT=(parsed1.atr.get("fingerprint") as Set<string>).values().next().value as string;
|
||||||
|
const candidate=(parsed1.atr.get("candidate") as Set<string>).values().next().value as string;
|
||||||
|
let build=`v=0\r
|
||||||
|
o=- 1420070400000 0 IN IP4 127.0.0.1\r
|
||||||
|
s=-\r
|
||||||
|
t=0 0\r
|
||||||
|
a=msid-semantic: WMS *\r
|
||||||
|
a=group:BUNDLE ${bundles.join(" ")}\r`
|
||||||
|
let i=0;
|
||||||
|
for(const grouping of parsed.medias){
|
||||||
|
let mode="recvonly";
|
||||||
|
for(const _ of this.senders){
|
||||||
|
if(i<2){
|
||||||
|
mode="sendrecv";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(grouping.media==="audio"){
|
||||||
|
build+=`
|
||||||
|
m=audio ${parsed1.port} UDP/TLS/RTP/SAVPF 111\r
|
||||||
|
${cline}\r
|
||||||
|
a=rtpmap:111 opus/48000/2\r
|
||||||
|
a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r
|
||||||
|
a=rtcp:${rtcport}\r
|
||||||
|
a=rtcp-fb:111 transport-cc\r
|
||||||
|
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r
|
||||||
|
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01/r/n
|
||||||
|
a=setup:passive\r
|
||||||
|
a=mid:${bundles[i]}\r
|
||||||
|
a=maxptime:60\r
|
||||||
|
a=${mode}\r
|
||||||
|
a=ice-ufrag:${ICE_UFRAG}\r
|
||||||
|
a=ice-pwd:${ICE_PWD}\r
|
||||||
|
a=fingerprint:${FINGERPRINT}\r
|
||||||
|
a=candidate:${candidate}\r
|
||||||
|
a=rtcp-mux\r`
|
||||||
|
}else{
|
||||||
|
build+=`
|
||||||
|
m=video ${rtcport} UDP/TLS/RTP/SAVPF 102 103\r
|
||||||
|
${cline}\r
|
||||||
|
a=rtpmap:102 H264/90000\r
|
||||||
|
a=rtpmap:103 rtx/90000\r
|
||||||
|
a=fmtp:102 x-google-max-bitrate=2500;level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r
|
||||||
|
a=fmtp:103 apt=102\r
|
||||||
|
a=rtcp:${rtcport}\r
|
||||||
|
a=rtcp-fb:102 ccm fir\r
|
||||||
|
a=rtcp-fb:102 nack\r
|
||||||
|
a=rtcp-fb:102 nack pli\r
|
||||||
|
a=rtcp-fb:102 goog-remb\r
|
||||||
|
a=rtcp-fb:102 transport-cc\r
|
||||||
|
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time/r/n
|
||||||
|
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01/r/n
|
||||||
|
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r
|
||||||
|
a=extmap:13 urn:3gpp:video-orientation\r
|
||||||
|
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay/r/na=setup:passive/r/n
|
||||||
|
a=mid:${bundles[i]}\r
|
||||||
|
a=${mode}\r
|
||||||
|
a=ice-ufrag:${ICE_UFRAG}\r
|
||||||
|
a=ice-pwd:${ICE_PWD}\r
|
||||||
|
a=fingerprint:${FINGERPRINT}\r
|
||||||
|
a=candidate:${candidate}\r
|
||||||
|
a=rtcp-mux\r`;
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
build+="\n";
|
||||||
|
return build;
|
||||||
|
}
|
||||||
|
counter?:string;
|
||||||
|
negotationneeded(){
|
||||||
|
if(this.pc&&this.offer){
|
||||||
|
const pc=this.pc;
|
||||||
|
pc.addEventListener("negotiationneeded", async ()=>{
|
||||||
|
this.offer=(await pc.createOffer({
|
||||||
|
offerToReceiveAudio: true,
|
||||||
|
offerToReceiveVideo: true
|
||||||
|
})).sdp;
|
||||||
|
await pc.setLocalDescription({sdp:this.offer});
|
||||||
|
|
||||||
|
if(!this.counter) throw new Error("counter isn't defined");
|
||||||
|
const counter=this.counter;
|
||||||
|
const remote:{sdp:string,type:RTCSdpType}={sdp:this.cleanServerSDP(counter),type:"answer"};
|
||||||
|
console.log(remote);
|
||||||
|
await pc.setRemoteDescription(remote);
|
||||||
|
const senders=this.senders.difference(this.ssrcMap);
|
||||||
|
for(const sender of senders){
|
||||||
|
for(const thing of (await sender.getStats() as Map<string, any>)){
|
||||||
|
if(thing[1].ssrc){
|
||||||
|
this.ssrcMap.set(sender,thing[1].ssrc);
|
||||||
|
this.makeOp12(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(this.ssrcMap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async makeOp12(sender:RTCRtpSender|undefined|[RTCRtpSender,number]=(this.ssrcMap.entries().next().value)){
|
||||||
|
if(!sender) throw new Error("sender doesn't exist");
|
||||||
|
if(sender instanceof Array){
|
||||||
|
sender=sender[0];
|
||||||
|
}
|
||||||
|
if(this.ws){
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
op: 12,
|
||||||
|
d: {
|
||||||
|
audio_ssrc: this.ssrcMap.get(sender),
|
||||||
|
video_ssrc: 0,
|
||||||
|
rtx_ssrc: 0,
|
||||||
|
streams: [
|
||||||
|
{
|
||||||
|
type: "video",
|
||||||
|
rid: "100",
|
||||||
|
ssrc: 0,//TODO
|
||||||
|
active: false,
|
||||||
|
quality: 100,
|
||||||
|
rtx_ssrc: 0,//TODO
|
||||||
|
max_bitrate: 2500000,//TODO
|
||||||
|
max_framerate: 0,//TODO
|
||||||
|
max_resolution: {
|
||||||
|
type: "fixed",
|
||||||
|
width: 0,//TODO
|
||||||
|
height: 0//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.status="Sending audio streams";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
senders:Set<RTCRtpSender>=new Set();
|
||||||
|
recivers=new Set<RTCRtpReceiver>();
|
||||||
|
ssrcMap:Map<RTCRtpSender,number>=new Map();
|
||||||
|
speaking=false;
|
||||||
|
async setupMic(audioStream:MediaStream){
|
||||||
|
const audioContext = new AudioContext();
|
||||||
|
const analyser = audioContext.createAnalyser();
|
||||||
|
const microphone = audioContext.createMediaStreamSource(audioStream);
|
||||||
|
|
||||||
|
analyser.smoothingTimeConstant = 0;
|
||||||
|
analyser.fftSize = 32;
|
||||||
|
|
||||||
|
microphone.connect(analyser);
|
||||||
|
const array=new Float32Array(1);
|
||||||
|
const interval=setInterval(()=>{
|
||||||
|
if(!this.ws){
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
analyser.getFloatFrequencyData(array);
|
||||||
|
const value=array[0]+65;
|
||||||
|
if(value<0){
|
||||||
|
if(this.speaking){
|
||||||
|
this.speaking=false;
|
||||||
|
this.sendSpeaking();
|
||||||
|
console.log("not speaking")
|
||||||
|
}
|
||||||
|
}else if(!this.speaking){
|
||||||
|
console.log("speaking");
|
||||||
|
this.speaking=true;
|
||||||
|
this.sendSpeaking();
|
||||||
|
}
|
||||||
|
},500);
|
||||||
|
}
|
||||||
|
async sendSpeaking(){
|
||||||
|
if(!this.ws) return;
|
||||||
|
const pair=this.ssrcMap.entries().next().value;
|
||||||
|
if(!pair) return
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
op:5,
|
||||||
|
d:{
|
||||||
|
speaking:+this.speaking,
|
||||||
|
delay:5,//not sure
|
||||||
|
ssrc:pair[1]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
async continueWebRTC(data:sdpback){
|
||||||
|
if(this.pc&&this.offer){
|
||||||
|
const pc=this.pc;
|
||||||
|
this.negotationneeded();
|
||||||
|
this.status="Starting Audio streams";
|
||||||
|
const audioStream = await navigator.mediaDevices.getUserMedia({video: false, audio: true} );
|
||||||
|
for (const track of audioStream.getAudioTracks()){
|
||||||
|
//Add track
|
||||||
|
|
||||||
|
this.setupMic(audioStream);
|
||||||
|
const sender = pc.addTrack(track);
|
||||||
|
this.senders.add(sender);
|
||||||
|
console.log(sender)
|
||||||
|
}
|
||||||
|
for(let i=0;i<10;i++){
|
||||||
|
pc.addTransceiver("audio",{
|
||||||
|
direction:"recvonly",
|
||||||
|
streams:[],
|
||||||
|
sendEncodings:[{active:true,maxBitrate:this.settings.bitrate}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for(let i=0;i<10;i++){
|
||||||
|
pc.addTransceiver("video",{
|
||||||
|
direction:"recvonly",
|
||||||
|
streams:[],
|
||||||
|
sendEncodings:[{active:true,maxBitrate:this.settings.bitrate}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.counter=data.d.sdp;
|
||||||
|
pc.ontrack = async (e) => {
|
||||||
|
this.status="Done";
|
||||||
|
if(e.track.kind==="video"){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const media=e.streams[0];
|
||||||
|
console.log("got audio:",e);
|
||||||
|
for(const track of media.getTracks()){
|
||||||
|
console.log(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
const context= new AudioContext();
|
||||||
|
await context.resume();
|
||||||
|
const ss=context.createMediaStreamSource(media);
|
||||||
|
console.log(media);
|
||||||
|
ss.connect(context.destination);
|
||||||
|
new Audio().srcObject = media;//weird I know, but it's for chromium/webkit bug
|
||||||
|
this.recivers.add(e.receiver)
|
||||||
|
};
|
||||||
|
|
||||||
|
}else{
|
||||||
|
this.status="Connection failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reciverMap=new Map<number,RTCRtpReceiver>()
|
||||||
|
async figureRecivers(){
|
||||||
|
await new Promise(res=>setTimeout(res,500));
|
||||||
|
for(const reciver of this.recivers){
|
||||||
|
const stats=await reciver.getStats() as Map<string,any>;
|
||||||
|
for(const thing of (stats)){
|
||||||
|
if(thing[1].ssrc){
|
||||||
|
this.reciverMap.set(thing[1].ssrc,reciver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(this.reciverMap);
|
||||||
|
}
|
||||||
|
async startWebRTC(){
|
||||||
|
this.status="Making offer";
|
||||||
|
const pc = new RTCPeerConnection();
|
||||||
|
this.pc=pc;
|
||||||
|
const offer = await pc.createOffer({
|
||||||
|
offerToReceiveAudio: true,
|
||||||
|
offerToReceiveVideo: true
|
||||||
|
});
|
||||||
|
this.status="Starting RTC connection";
|
||||||
|
const sdp=offer.sdp;
|
||||||
|
this.offer=sdp;
|
||||||
|
|
||||||
|
if(!sdp){
|
||||||
|
this.status="No SDP";
|
||||||
|
this.ws?.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsed=Voice.parsesdp(sdp);
|
||||||
|
const video=new Map<string,[number,number]>();
|
||||||
|
const audio=new Map<string,number>();
|
||||||
|
let cur:[number,number]|undefined;
|
||||||
|
let i=0;
|
||||||
|
for(const thing of parsed.medias){
|
||||||
|
try{
|
||||||
|
if(thing.media==="video"){
|
||||||
|
const rtpmap=thing.atr.get("rtpmap");
|
||||||
|
if(!rtpmap) continue;
|
||||||
|
for(const codecpair of rtpmap){
|
||||||
|
|
||||||
|
const [port, codec]=codecpair.split(" ");
|
||||||
|
if(cur&&codec.split("/")[0]==="rtx"){
|
||||||
|
cur[1]=Number(port);
|
||||||
|
cur=undefined;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if(video.has(codec.split("/")[0])) continue;
|
||||||
|
cur=[Number(port),-1];
|
||||||
|
video.set(codec.split("/")[0],cur);
|
||||||
|
}
|
||||||
|
}else if(thing.media==="audio"){
|
||||||
|
const rtpmap=thing.atr.get("rtpmap");
|
||||||
|
if(!rtpmap) continue;
|
||||||
|
for(const codecpair of rtpmap){
|
||||||
|
const [port, codec]=codecpair.split(" ");
|
||||||
|
if(audio.has(codec.split("/")[0])) { continue};
|
||||||
|
audio.set(codec.split("/")[0],Number(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}finally{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const codecs:{
|
||||||
|
name: string,
|
||||||
|
type: "video"|"audio",
|
||||||
|
priority: number,
|
||||||
|
payload_type: number,
|
||||||
|
rtx_payload_type: number|null
|
||||||
|
}[]=[];
|
||||||
|
const include=new Set<string>();
|
||||||
|
const audioAlloweds=new Map([["opus",{priority:1000,}]]);
|
||||||
|
for(const thing of audio){
|
||||||
|
if(audioAlloweds.has(thing[0])){
|
||||||
|
include.add(thing[0]);
|
||||||
|
codecs.push({
|
||||||
|
name:thing[0],
|
||||||
|
type:"audio",
|
||||||
|
priority:audioAlloweds.get(thing[0])?.priority as number,
|
||||||
|
payload_type:thing[1],
|
||||||
|
rtx_payload_type:null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const videoAlloweds=new Map([["H264",{priority:1000}],["VP8",{priority:2000}],["VP9",{priority:3000}]]);
|
||||||
|
for(const thing of video){
|
||||||
|
if(videoAlloweds.has(thing[0])){
|
||||||
|
include.add(thing[0]);
|
||||||
|
codecs.push({
|
||||||
|
name:thing[0],
|
||||||
|
type:"video",
|
||||||
|
priority:videoAlloweds.get(thing[0])?.priority as number,
|
||||||
|
payload_type:thing[1][0],
|
||||||
|
rtx_payload_type:thing[1][1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let sendsdp="a=extmap-allow-mixed";
|
||||||
|
let first=true;
|
||||||
|
for(const media of parsed.medias){
|
||||||
|
|
||||||
|
for(const thing of first?["ice-ufrag","ice-pwd","ice-options","fingerprint","extmap","rtpmap"]:["extmap","rtpmap"]){
|
||||||
|
const thing2=media.atr.get(thing);
|
||||||
|
if(!thing2) continue;
|
||||||
|
for(const thing3 of thing2){
|
||||||
|
if(thing === "rtpmap"){
|
||||||
|
const name=thing3.split(" ")[1].split("/")[0];
|
||||||
|
if(include.has(name)){
|
||||||
|
include.delete(name);
|
||||||
|
}else{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendsdp+=`\na=${thing}:${thing3}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first=false;
|
||||||
|
}
|
||||||
|
if(this.ws){
|
||||||
|
this.ws.send(JSON.stringify({
|
||||||
|
d:{
|
||||||
|
codecs,
|
||||||
|
protocol:"webrtc",
|
||||||
|
data:sendsdp,
|
||||||
|
sdp:sendsdp
|
||||||
|
},
|
||||||
|
op:1
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static parsesdp(sdp:string){
|
||||||
|
let currentA=new Map<string,Set<string>>();
|
||||||
|
const out:{version?:number,medias:{media:string,port:number,proto:string,ports:number[],atr:Map<string,Set<string>>}[],atr:Map<string,Set<string>>}={medias:[],atr:currentA};
|
||||||
|
for(const line of sdp.split("\n")){
|
||||||
|
const [code,setinfo]=line.split("=");
|
||||||
|
switch(code){
|
||||||
|
case "v":
|
||||||
|
out.version=Number(setinfo);
|
||||||
|
break;
|
||||||
|
case "o":
|
||||||
|
case "s":
|
||||||
|
case "t":
|
||||||
|
break;
|
||||||
|
case "m":
|
||||||
|
currentA=new Map();
|
||||||
|
const [media,port,proto,...ports]=setinfo.split(" ");
|
||||||
|
const portnums=ports.map(Number);
|
||||||
|
out.medias.push({media,port:Number(port),proto,ports:portnums,atr:currentA});
|
||||||
|
break;
|
||||||
|
case "a":
|
||||||
|
const [key, ...value] = setinfo.split(":");
|
||||||
|
if(!currentA.has(key)){
|
||||||
|
currentA.set(key,new Set());
|
||||||
|
}
|
||||||
|
currentA.get(key)?.add(value.join(":"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
open=false;
|
||||||
|
async join(){
|
||||||
|
console.warn("Joining");
|
||||||
|
this.open=true
|
||||||
|
this.status="waiting for main WS";
|
||||||
|
}
|
||||||
|
onMemberChange=(_member:memberjson|string,_joined:boolean)=>{};
|
||||||
|
userids=new Map<string,{}>();
|
||||||
|
async voiceupdate(update:voiceupdate){
|
||||||
|
console.log("Update!");
|
||||||
|
this.userids.set(update.d.member.id,{deaf:update.d.deaf,muted:update.d.mute});
|
||||||
|
this.onMemberChange(update.d.member,true);
|
||||||
|
if(update.d.member.id===this.userid&&this.open){
|
||||||
|
if(!update) {
|
||||||
|
this.status="bad responce from WS";
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if(!this.urlobj.url){
|
||||||
|
this.status="waiting for Voice URL";
|
||||||
|
await this.urlobj.geturl;
|
||||||
|
if(!this.open){this.leave();return}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws=new WebSocket("ws://"+this.urlobj.url as string);
|
||||||
|
this.ws=ws;
|
||||||
|
ws.onclose=()=>{
|
||||||
|
this.leave();
|
||||||
|
}
|
||||||
|
this.status="waiting for WS to open";
|
||||||
|
ws.addEventListener("message",(m)=>{
|
||||||
|
this.packet(m);
|
||||||
|
})
|
||||||
|
await new Promise<void>(res=>{
|
||||||
|
ws.addEventListener("open",()=>{
|
||||||
|
res()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if(!this.ws){
|
||||||
|
this.leave();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.status="waiting for WS to authorize";
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
"op": 0,
|
||||||
|
"d": {
|
||||||
|
server_id: update.d.guild_id,
|
||||||
|
user_id: update.d.user_id,
|
||||||
|
session_id: update.d.session_id,
|
||||||
|
token: update.d.token,
|
||||||
|
video: false,
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
type: "video",
|
||||||
|
rid: "100",
|
||||||
|
quality: 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async leave(){
|
||||||
|
this.open=false;
|
||||||
|
this.status="Left voice chat";
|
||||||
|
if(this.ws){
|
||||||
|
this.ws.close();
|
||||||
|
this.ws=undefined;
|
||||||
|
}
|
||||||
|
if(this.pc){
|
||||||
|
this.pc.close();
|
||||||
|
this.pc=undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {Voice,VoiceFactory};
|
|
@ -9,7 +9,8 @@
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext",
|
"esnext",
|
||||||
"DOM"
|
"DOM",
|
||||||
|
"webworker"
|
||||||
],
|
],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue