formatting updates

This commit is contained in:
MathMan05
2024-12-20 19:28:08 -06:00
parent ffe21e6d6c
commit d2d0f45c81
50 changed files with 7783 additions and 7432 deletions

View File

@@ -19,6 +19,12 @@
"prettier": "^3.4.2", "prettier": "^3.4.2",
"rimraf": "^6.0.1" "rimraf": "^6.0.1"
}, },
"prettier":{
"useTabs":true,
"printWidth":100,
"semi":true,
"bracketSpacing":false
},
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.10.0", "@eslint/js": "^9.10.0",
"@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/eslint-plugin": "^0.25.0",

View File

@@ -22,31 +22,33 @@ interface Instance {
const app = express(); const app = express();
type instace = { type instace = {
name:string, name: string;
description?:string, description?: string;
descriptionLong?:string, descriptionLong?: string;
image?:string, image?: string;
url?:string, url?: string;
language:string, language: string;
country:string, country: string;
display:boolean, display: boolean;
urls?: { urls?: {
wellknown:string, wellknown: string;
api:string, api: string;
cdn:string, cdn: string;
gateway:string, gateway: string;
login?:string login?: string;
}, };
contactInfo?: { contactInfo?: {
discord?:string, discord?: string;
github?:string, github?: string;
email?:string, email?: string;
spacebar?:string, spacebar?: string;
matrix?:string, matrix?: string;
mastodon?:string mastodon?: string;
} };
} };
const instances=JSON.parse(readFileSync(process.env.JANK_INSTANCES_PATH||(__dirname+"/webpage/instances.json")).toString()) as instace[]; const instances = JSON.parse(
readFileSync(process.env.JANK_INSTANCES_PATH || __dirname + "/webpage/instances.json").toString(),
) as instace[];
const instanceNames = new Map<string, Instance>(); const instanceNames = new Map<string, Instance>();
@@ -58,7 +60,9 @@ app.use(compression());
async function updateInstances(): Promise<void> { async function updateInstances(): Promise<void> {
try { try {
const response = await fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"); const response = await fetch(
"https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json",
);
const json = (await response.json()) as Instance[]; const json = (await response.json()) as Instance[];
for (const instance of json) { for (const instance of json) {
if (instanceNames.has(instance.name)) { if (instanceNames.has(instance.name)) {
@@ -110,7 +114,7 @@ app.use("/", async (req: Request, res: Response)=>{
const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`;
res.set( res.set(
"Link", "Link",
`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`,
); );
} }
@@ -166,7 +170,7 @@ app.use("/", async (req: Request, res: Response)=>{
} }
}); });
app.set('trust proxy', (ip:string) => ip.startsWith("127.")); app.set("trust proxy", (ip: string) => ip.startsWith("127."));
const PORT = process.env.PORT || Number(process.argv[2]) || 8080; const PORT = process.env.PORT || Number(process.argv[2]) || 8080;
app.listen(PORT, () => { app.listen(PORT, () => {

View File

@@ -52,11 +52,11 @@ function saveUptimeObject(): void{
fs.writeFile( fs.writeFile(
process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json"), process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json"),
JSON.stringify(data), JSON.stringify(data),
error=>{ (error) => {
if (error) { if (error) {
console.error("Error saving uptime.json:", error); console.error("Error saving uptime.json:", error);
} }
} },
); );
}, 5000); // Batch updates every 5 seconds }, 5000); // Batch updates every 5 seconds
} }
@@ -72,16 +72,12 @@ removeUndefinedKey();
export async function observe(instances: Instance[]): Promise<void> { export async function observe(instances: Instance[]): Promise<void> {
const activeInstances = new Set<string>(); const activeInstances = new Set<string>();
const instancePromises = instances.map(instance=>resolveInstance(instance, activeInstances) const instancePromises = instances.map((instance) => resolveInstance(instance, activeInstances));
);
await Promise.allSettled(instancePromises); await Promise.allSettled(instancePromises);
updateInactiveInstances(activeInstances); updateInactiveInstances(activeInstances);
} }
async function resolveInstance( async function resolveInstance(instance: Instance, activeInstances: Set<string>): Promise<void> {
instance: Instance,
activeInstances: Set<string>
): Promise<void>{
try { try {
calcStats(instance); calcStats(instance);
const api = await getApiUrl(instance); const api = await getApiUrl(instance);
@@ -123,11 +119,7 @@ function scheduleHealthCheck(instance: Instance, api: string): void{
}, initialDelay); }, initialDelay);
} }
async function checkHealth( async function checkHealth(instance: Instance, api: string, tries = 0): Promise<void> {
instance: Instance,
api: string,
tries = 0
): Promise<void>{
try { try {
const response = await fetch(`${api}/ping`, {method: "HEAD"}); const response = await fetch(`${api}/ping`, {method: "HEAD"});
console.log(`Checking health for ${instance.name}: ${response.status}`); console.log(`Checking health for ${instance.name}: ${response.status}`);
@@ -146,11 +138,7 @@ async function checkHealth(
} }
} }
function retryHealthCheck( function retryHealthCheck(instance: Instance, api: string, tries: number): void {
instance: Instance,
api: string,
tries: number
): void{
setTimeout(() => checkHealth(instance, api, tries + 1), 30000); setTimeout(() => checkHealth(instance, api, tries + 1), 30000);
} }
@@ -198,13 +186,7 @@ function calcStats(instance: Instance): void{
} }
instance.online = online; instance.online = online;
instance.uptime = calculateUptimeStats( instance.uptime = calculateUptimeStats(totalTimePassed, alltime, daytime, weektime, online);
totalTimePassed,
alltime,
daytime,
weektime,
online
);
} }
function calculateUptimeStats( function calculateUptimeStats(
@@ -212,7 +194,7 @@ function calculateUptimeStats(
alltime: number, alltime: number,
daytime: number, daytime: number,
weektime: number, weektime: number,
online: boolean online: boolean,
): {daytime: number; weektime: number; alltime: number} { ): {daytime: number; weektime: number; alltime: number} {
const dayInMs = 1000 * 60 * 60 * 24; const dayInMs = 1000 * 60 * 60 * 24;
const weekInMs = dayInMs * 7; const weekInMs = dayInMs * 7;

View File

@@ -24,12 +24,12 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
url += "/"; url += "/";
} }
try { try {
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json()); const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then((res) => res.json());
const api = info.api; const api = info.api;
const apiUrl = new URL(api); const apiUrl = new URL(api);
const policies: any = await fetch( const policies: any = await fetch(
`${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains` `${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
).then(res=>res.json()); ).then((res) => res.json());
return { return {
api: policies.apiEndpoint, api: policies.apiEndpoint,
gateway: policies.gateway, gateway: policies.gateway,
@@ -68,7 +68,9 @@ export async function inviteResponse(req: Request, res: Response): Promise<void>
throw new Error("Failed to get API URLs"); throw new Error("Failed to get API URLs");
} }
const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise<Invite>); const invite = await fetch(`${urls.api}/invites/${code}`).then(
(json) => json.json() as Promise<Invite>,
);
const title = invite.guild.name; const title = invite.guild.name;
const description = invite.inviter const description = invite.inviter
? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}` ? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`

View File

@@ -11,7 +11,7 @@ export class Audio{
static parse(read: BinRead, trackarr: Track[]): Audio { static parse(read: BinRead, trackarr: Track[]): Audio {
const name = read.readString8(); const name = read.readString8();
const length = read.read16(); const length = read.read16();
const tracks:(Track|number)[]=[] const tracks: (Track | number)[] = [];
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
let index = read.read16(); let index = read.read16();
if (index === 0) { if (index === 0) {
@@ -20,7 +20,7 @@ export class Audio{
tracks.push(trackarr[index - 1]); tracks.push(trackarr[index - 1]);
} }
} }
return new Audio(name,tracks) return new Audio(name, tracks);
} }
async play() { async play() {
for (const thing of this.tracks) { for (const thing of this.tracks) {

View File

@@ -1,26 +1,39 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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" />
<title>Jank Audio</title> <title>Jank Audio</title>
<meta content="Jank Sound" property="og:title"> <meta content="Jank Sound" property="og:title" />
<meta content="A sound editor for jank clients sound format .jasf" property="og:description"> <meta content="A sound editor for jank clients sound format .jasf" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<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> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme" style="overflow-y: scroll;"> <body class="no-theme" style="overflow-y: scroll">
<h1>This will eventually be something</h1> <h1>This will eventually be something</h1>
<p>I want to let the sound system of jank not be so hard coded, but I still need to work on everything a bit before that can happen. Thanks for your patience.</p> <p>
I want to let the sound system of jank not be so hard coded, but I still need to work on
everything a bit before that can happen. Thanks for your patience.
</p>
<h3>why does this tool need to exist?</h3> <h3>why does this tool need to exist?</h3>
<p>For size reasons jank does not use normal sound files, so I need to make this whole format to be more adaptable</p> <p>
For size reasons jank does not use normal sound files, so I need to make this whole format to
be more adaptable
</p>
<button id="download">Download the sounds</button> <button id="download">Download the sounds</button>
</body> </body>
<script src="/audio/page.js" type="module"></script> <script src="/audio/page.js" type="module"></script>
</html> </html>

View File

@@ -19,25 +19,28 @@ w.write32Float(150);
//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);
//Math.sin((((t+2)**Math.cos((t*4)))*((Math.PI*2)*f))) //Math.sin((((t+2)**Math.cos((t*4)))*((Math.PI*2)*f)))
w.write8(4); //sin w.write8(4); //sin
w.write8(5)//times w.write8(5); //times
{ {
w.write8(9); //Power w.write8(9); //Power
{ {
w.write8(6); //adding w.write8(6); //adding
w.write8(1); //t w.write8(1); //t
w.write8(0);w.write32Float(2);//2 w.write8(0);
w.write32Float(2); //2
} }
w.write8(13); //cos w.write8(13); //cos
w.write8(5); // times w.write8(5); // times
w.write8(1); //t w.write8(1); //t
w.write8(0);w.write32Float(4);//4 w.write8(0);
w.write32Float(4); //4
} }
{ {
w.write8(5)//times w.write8(5); //times
w.write8(5)//times w.write8(5); //times
w.write8(3); //PI w.write8(3); //PI
w.write8(0);w.write32Float(2);//2 w.write8(0);
w.write32Float(2); //2
w.write8(2); //freq w.write8(2); //freq
} }
@@ -45,13 +48,13 @@ w.write16(4);//3 tracks
w.write16(1); //zip w.write16(1); //zip
w.write8(4); w.write8(4);
w.write32Float(1) w.write32Float(1);
w.write32Float(700) w.write32Float(700);
w.write16(3); //beep w.write16(3); //beep
{ {
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(700); w.write32Float(700);
w.write32Float(50); w.write32Float(50);
@@ -59,7 +62,7 @@ w.write16(3);//beep
w.write32Float(100); w.write32Float(100);
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(700); w.write32Float(700);
w.write32Float(50); w.write32Float(50);
} }
@@ -67,7 +70,7 @@ w.write16(3);//beep
w.write16(5); //three w.write16(5); //three
{ {
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(800); w.write32Float(800);
w.write32Float(50); w.write32Float(50);
@@ -75,7 +78,7 @@ w.write16(5);//three
w.write32Float(50); w.write32Float(50);
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(1000); w.write32Float(1000);
w.write32Float(50); w.write32Float(50);
@@ -83,7 +86,7 @@ w.write16(5);//three
w.write32Float(50); w.write32Float(50);
w.write8(1); w.write8(1);
w.write32Float(1) w.write32Float(1);
w.write32Float(1300); w.write32Float(1300);
w.write32Float(50); w.write32Float(50);
} }
@@ -91,7 +94,7 @@ w.write16(5);//three
w.write16(5); //square w.write16(5); //square
{ {
w.write8(3); w.write8(3);
w.write32Float(1) w.write32Float(1);
w.write32Float(600); w.write32Float(600);
w.write32Float(50); w.write32Float(50);
@@ -99,7 +102,7 @@ w.write16(5);//square
w.write32Float(50); w.write32Float(50);
w.write8(3); w.write8(3);
w.write32Float(1) w.write32Float(1);
w.write32Float(800); w.write32Float(800);
w.write32Float(50); w.write32Float(50);
@@ -107,7 +110,7 @@ w.write16(5);//square
w.write32Float(50); w.write32Float(50);
w.write8(3); w.write8(3);
w.write32Float(1) w.write32Float(1);
w.write32Float(1000); w.write32Float(1000);
w.write32Float(50); w.write32Float(50);
} }
@@ -147,11 +150,11 @@ if(download){
download.onclick = () => { download.onclick = () => {
const blob = new Blob([buff], {type: "binary"}); const blob = new Blob([buff], {type: "binary"});
const downloadUrl = URL.createObjectURL(blob); const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement("a");
a.href = downloadUrl; a.href = downloadUrl;
a.download = "sounds.jasf"; a.download = "sounds.jasf";
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
URL.revokeObjectURL(downloadUrl); URL.revokeObjectURL(downloadUrl);
} };
} }

View File

@@ -3,8 +3,8 @@ import { Track } from "./track.js";
import {AVoice} from "./voice.js"; import {AVoice} from "./voice.js";
import {Audio} from "./audio.js"; import {Audio} from "./audio.js";
export class Play { export class Play {
voices:[AVoice,string][] voices: [AVoice, string][];
tracks:Track[] tracks: Track[];
audios: Map<string, Audio>; audios: Map<string, Audio>;
constructor(voices: [AVoice, string][], tracks: Track[], audios: Map<string, Audio>) { constructor(voices: [AVoice, string][], tracks: Track[], audios: Map<string, Audio>) {
this.voices = voices; this.voices = voices;
@@ -35,7 +35,7 @@ export class Play{
const audioArr = new Map<string, Audio>(); const audioArr = new Map<string, Audio>();
for (let i = 0; i < audios; i++) { for (let i = 0; i < audios; i++) {
const a = Audio.parse(read, trackArr); const a = Audio.parse(read, trackArr);
audioArr.set(a.name,a) audioArr.set(a.name, a);
} }
return new Play(voiceArr, trackArr, audioArr); return new Play(voiceArr, trackArr, audioArr);

View File

@@ -24,13 +24,12 @@ export class Track{
if (!play[index]) throw new Error("voice not found"); if (!play[index]) throw new Error("voice not found");
const [voice] = play[index]; const [voice] = play[index];
let temp: AVoice; let temp: AVoice;
if((voice.info.wave instanceof Function)){ if (voice.info.wave instanceof Function) {
temp = voice.clone(read.readFloat32(), read.readFloat32()); temp = voice.clone(read.readFloat32(), read.readFloat32());
} else { } else {
temp = voice.clone(read.readFloat32(), read.readFloat32(), read.readFloat32()); temp = voice.clone(read.readFloat32(), read.readFloat32(), read.readFloat32());
} }
play2.push(temp); play2.push(temp);
} }
return new Track(play2); return new Track(play2);
} }

View File

@@ -16,8 +16,8 @@ class AVoice{
this.playing = false; this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer( this.myArrayBuffer = this.audioCtx.createBuffer(
1, 1,
this.audioCtx.sampleRate*length/1000, (this.audioCtx.sampleRate * length) / 1000,
this.audioCtx.sampleRate this.audioCtx.sampleRate,
); );
this.gainNode = this.audioCtx.createGain(); this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume; this.gainNode.gain.value = volume;
@@ -104,13 +104,13 @@ class AVoice{
case "three": { case "three": {
const voicy = new AVoice("sin", 800); const voicy = new AVoice("sin", 800);
voicy.play(); voicy.play();
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 1000; voicy.freq = 1000;
}, 50); }, 50);
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 1300; voicy.freq = 1300;
}, 100); }, 100);
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
@@ -120,7 +120,7 @@ class AVoice{
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();
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
@@ -128,13 +128,13 @@ class AVoice{
case "square": { case "square": {
const voicy = new AVoice("square", 600, 0.4); const voicy = new AVoice("square", 600, 0.4);
voicy.play(); voicy.play();
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 800; voicy.freq = 800;
}, 50); }, 50);
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 1000; voicy.freq = 1000;
}, 100); }, 100);
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
@@ -142,45 +142,45 @@ class AVoice{
case "beep": { case "beep": {
const voicy = new AVoice("sin", 800); const voicy = new AVoice("sin", 800);
voicy.play(); voicy.play();
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 50); }, 50);
setTimeout(_=>{ setTimeout((_) => {
voicy.play(); voicy.play();
}, 100); }, 100);
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
} }
case "join": { case "join": {
const voicy = new AVoice("triangle", 600,.1); const voicy = new AVoice("triangle", 600, 0.1);
voicy.play(); voicy.play();
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 800; voicy.freq = 800;
}, 75); }, 75);
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 1000; voicy.freq = 1000;
}, 150); }, 150);
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 200); }, 200);
break; break;
} }
case "leave": { case "leave": {
const voicy = new AVoice("triangle", 850,.5); const voicy = new AVoice("triangle", 850, 0.5);
voicy.play(); voicy.play();
setTimeout(_=>{ setTimeout((_) => {
voicy.freq = 700; voicy.freq = 700;
}, 100); }, 100);
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
voicy.freq = 400; voicy.freq = 400;
}, 180); }, 180);
setTimeout(_=>{ setTimeout((_) => {
voicy.play(); voicy.play();
}, 200); }, 200);
setTimeout(_=>{ setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 250); }, 250);
break; break;
@@ -193,14 +193,14 @@ class AVoice{
static getVoice(read: BinRead): [AVoice, string] { static getVoice(read: BinRead): [AVoice, string] {
const name = read.readString8(); const name = read.readString8();
let length = read.readFloat32(); let length = read.readFloat32();
let special:Function|string let special: Function | string;
if (length !== 0) { if (length !== 0) {
special = this.parseExpression(read); special = this.parseExpression(read);
} else { } else {
special = name; special = name;
length = 1; length = 1;
} }
return [new AVoice(special,0,0,length),name] return [new AVoice(special, 0, 0, length), name];
} }
static parseExpression(read: BinRead): Function { static parseExpression(read: BinRead): Function {
return new Function("t", "f", `return ${this.PEHelper(read)};`); return new Function("t", "f", `return ${this.PEHelper(read)};`);
@@ -215,7 +215,7 @@ class AVoice{
case 2: case 2:
return "f"; return "f";
case 3: case 3:
return `Math.PI` return `Math.PI`;
case 4: case 4:
return `Math.sin(${this.PEHelper(read)})`; return `Math.sin(${this.PEHelper(read)})`;
case 5: case 5:
@@ -238,7 +238,6 @@ class AVoice{
return `Math.cos(${this.PEHelper(read)})`; return `Math.cos(${this.PEHelper(read)})`;
default: default:
throw new Error("unexpected case found!"); throw new Error("unexpected case found!");
} }
} }
} }

View File

@@ -9,7 +9,14 @@ import{ Dialog, Float, 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{channeljson,embedjson,messageCreateJson,messagejson,readyjson,startTypingjson}from"./jsontypes.js"; import {
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 {Voice} from "./voice.js";
@@ -54,26 +61,42 @@ class Channel extends SnowFlake{
bitrate: number = 128000; bitrate: number = 128000;
muted: boolean = false; muted: boolean = false;
mute_config= {selected_time_window: -1,end_time: 0} mute_config = {selected_time_window: -1, end_time: 0};
handleUserOverrides(settings:{message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}){ handleUserOverrides(settings: {
message_notifications: number;
muted: boolean;
mute_config: {selected_time_window: number; end_time: number};
channel_id: string;
}) {
this.message_notifications = settings.message_notifications; this.message_notifications = settings.message_notifications;
this.muted = settings.muted; this.muted = settings.muted;
this.mute_config = settings.mute_config; this.mute_config = settings.mute_config;
} }
static setupcontextmenu() { static setupcontextmenu() {
this.contextmenu.addbutton(()=>I18n.getTranslation("channel.copyId"), function(this: Channel){ this.contextmenu.addbutton(
() => I18n.getTranslation("channel.copyId"),
function (this: Channel) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); },
);
this.contextmenu.addbutton(()=>I18n.getTranslation("channel.markRead"), function(this: Channel){ this.contextmenu.addbutton(
() => I18n.getTranslation("channel.markRead"),
function (this: Channel) {
this.readbottom(); this.readbottom();
}); },
);
this.contextmenu.addbutton(()=>I18n.getTranslation("channel.settings"), function(this: Channel){ this.contextmenu.addbutton(
() => I18n.getTranslation("channel.settings"),
function (this: Channel) {
this.generateSettings(); this.generateSettings();
},null,function(){ },
null,
function () {
return this.hasPermission("MANAGE_CHANNELS"); return this.hasPermission("MANAGE_CHANNELS");
}); },
);
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("channel.delete"), () => I18n.getTranslation("channel.delete"),
@@ -83,14 +106,14 @@ class Channel extends SnowFlake{
null, null,
function () { function () {
return this.isAdmin(); return this.isAdmin();
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("guild.notifications"), () => I18n.getTranslation("guild.notifications"),
function () { function () {
this.setnotifcation(); this.setnotifcation();
} },
) );
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("channel.makeInvite"), () => I18n.getTranslation("channel.makeInvite"),
@@ -100,7 +123,7 @@ class Channel extends SnowFlake{
null, null,
function () { function () {
return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4;
} },
); );
} }
createInvite() { createInvite() {
@@ -116,7 +139,7 @@ class Channel extends SnowFlake{
const copy = document.createElement("span"); const copy = document.createElement("span");
copy.classList.add("copybutton", "svgicon", "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) {
navigator.clipboard.writeText(text.textContent); navigator.clipboard.writeText(text.textContent);
} }
@@ -135,8 +158,8 @@ class Channel extends SnowFlake{
temporary: uses !== 0, temporary: uses !== 0,
}), }),
}) })
.then(_=>_.json()) .then((_) => _.json())
.then(json=>{ .then((json) => {
const params = new URLSearchParams(""); const params = new URLSearchParams("");
params.set("instance", this.info.wellknown); params.set("instance", this.info.wellknown);
const encoded = params.toString(); const encoded = params.toString();
@@ -146,16 +169,33 @@ class Channel extends SnowFlake{
update(); update();
const inviteOptions = new Dialog("", {noSubmit: true}); const inviteOptions = new Dialog("", {noSubmit: true});
inviteOptions.options.addTitle(I18n.getTranslation("inviteOptions.title")); inviteOptions.options.addTitle(I18n.getTranslation("inviteOptions.title"));
inviteOptions.options.addText(I18n.getTranslation("invite.subtext",this.name,this.guild.properties.name)); inviteOptions.options.addText(
I18n.getTranslation("invite.subtext", this.name, this.guild.properties.name),
);
inviteOptions.options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{}, inviteOptions.options.addSelect(
["30m","1h","6h","12h","1d","7d","30d","never"].map((e)=>I18n.getTranslation("inviteOptions."+e)) I18n.getTranslation("invite.expireAfter"),
).onchange=(e)=>{expires=[1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];update()}; () => {},
["30m", "1h", "6h", "12h", "1d", "7d", "30d", "never"].map((e) =>
I18n.getTranslation("inviteOptions." + e),
),
).onchange = (e) => {
expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];
update();
};
const timeOptions=["1","5","10","25","50","100"].map((e)=>I18n.getTranslation("inviteOptions.limit",e)) const timeOptions = ["1", "5", "10", "25", "50", "100"].map((e) =>
timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")) I18n.getTranslation("inviteOptions.limit", e),
inviteOptions.options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{},timeOptions) );
.onchange=(e)=>{uses=[0, 1, 5, 10, 25, 50, 100][e];update()}; timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit"));
inviteOptions.options.addSelect(
I18n.getTranslation("invite.expireAfter"),
() => {},
timeOptions,
).onchange = (e) => {
uses = [0, 1, 5, 10, 25, 50, 100][e];
update();
};
inviteOptions.options.addHTMLArea(div); inviteOptions.options.addHTMLArea(div);
inviteOptions.show(); inviteOptions.show();
@@ -170,19 +210,32 @@ class Channel extends SnowFlake{
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
}); });
form.addTextInput(I18n.getTranslation("channel.name:"),"name",{initText:this.name}); form.addTextInput(I18n.getTranslation("channel.name:"), "name", {
form.addMDInput(I18n.getTranslation("channel.topic:"),"topic",{initText:this.topic}); initText: this.name,
form.addCheckboxInput(I18n.getTranslation("channel.nsfw:"),"nsfw",{initState:this.nsfw}); });
form.addMDInput(I18n.getTranslation("channel.topic:"), "topic", {
initText: this.topic,
});
form.addCheckboxInput(I18n.getTranslation("channel.nsfw:"), "nsfw", {
initState: this.nsfw,
});
if (this.type !== 4) { if (this.type !== 4) {
const options = ["voice", "text", "announcement"]; const options = ["voice", "text", "announcement"];
form.addSelect("Type:","type",options.map(e=>I18n.getTranslation("channel."+e)),{ form.addSelect(
defaultIndex:options.indexOf({0:"text", 2:"voice", 5:"announcement", 4:"category" }[this.type] as string) "Type:",
},options); "type",
options.map((e) => I18n.getTranslation("channel." + e)),
{
defaultIndex: options.indexOf(
{0: "text", 2: "voice", 5: "announcement", 4: "category"}[this.type] as string,
),
},
options,
);
form.addPreprocessor((obj: any) => { form.addPreprocessor((obj: any) => {
obj.type={text: 0, voice: 2, announcement: 5, category: 4 }[obj.type as string] obj.type = {text: 0, voice: 2, announcement: 5, category: 4}[obj.type as string];
}) });
} }
} }
const s1 = settings.addButton("Permissions"); const s1 = settings.addButton("Permissions");
s1.options.push( s1.options.push(
@@ -190,17 +243,14 @@ class Channel extends SnowFlake{
this.permission_overwritesar, this.permission_overwritesar,
this.guild, this.guild,
this.updateRolePermissions.bind(this), this.updateRolePermissions.bind(this),
this this,
) ),
); );
settings.show(); settings.show();
} }
sortPerms() { sortPerms() {
this.permission_overwritesar.sort((a, b) => { this.permission_overwritesar.sort((a, b) => {
return( return this.guild.roles.indexOf(a[0]) - this.guild.roles.indexOf(b[0]);
this.guild.roles.indexOf(a[0]) -
this.guild.roles.indexOf(b[0])
);
}); });
} }
setUpInfiniteScroller() { setUpInfiniteScroller() {
@@ -220,7 +270,6 @@ class Channel extends SnowFlake{
await this.grabAfter(id); await this.grabAfter(id);
return this.idToNext.get(id); return this.idToNext.get(id);
} else { } else {
} }
} }
return undefined; return undefined;
@@ -252,14 +301,10 @@ class Channel extends SnowFlake{
} }
return false; return false;
}, },
this.readbottom.bind(this) this.readbottom.bind(this),
); );
} }
constructor( constructor(json: channeljson | -1, owner: Guild, id: string = json === -1 ? "" : json.id) {
json: channeljson | -1,
owner: Guild,
id: string = json === -1 ? "" : json.id
){
super(id); super(id);
if (json === -1) { if (json === -1) {
return; return;
@@ -283,10 +328,7 @@ class Channel extends SnowFlake{
} }
if (!this.permission_overwrites.has(thing.id)) { if (!this.permission_overwrites.has(thing.id)) {
//either a bug in the server requires this, or the API is cursed //either a bug in the server requires this, or the API is cursed
this.permission_overwrites.set( this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny));
thing.id,
new Permissions(thing.allow, thing.deny)
);
const permission = this.permission_overwrites.get(thing.id); const permission = this.permission_overwrites.get(thing.id);
if (permission) { if (permission) {
const role = this.guild.roleids.get(thing.id); const role = this.guild.roleids.get(thing.id);
@@ -309,7 +351,9 @@ class Channel extends SnowFlake{
this.setUpInfiniteScroller(); this.setUpInfiniteScroller();
this.perminfo ??= {}; this.perminfo ??= {};
if (this.type === 2 && this.localuser.voiceFactory) { if (this.type === 2 && this.localuser.voiceFactory) {
this.voice=this.localuser.voiceFactory.makeVoice(this.guild.id,this.id,{bitrate:this.bitrate}); this.voice = this.localuser.voiceFactory.makeVoice(this.guild.id, this.id, {
bitrate: this.bitrate,
});
this.setUpVoice(); this.setUpVoice();
} }
} }
@@ -356,7 +400,7 @@ class Channel extends SnowFlake{
const roles = new Set(member.roles); const roles = new Set(member.roles);
const everyone = this.guild.roles[this.guild.roles.length - 1]; const everyone = this.guild.roles[this.guild.roles.length - 1];
if (!member.user.bot || true) { if (!member.user.bot || true) {
roles.add(everyone) roles.add(everyone);
} }
for (const thing of roles) { for (const thing of roles) {
const premission = this.permission_overwrites.get(thing.id); const premission = this.permission_overwrites.get(thing.id);
@@ -374,7 +418,7 @@ class Channel extends SnowFlake{
} }
get canMessage(): boolean { get canMessage(): boolean {
if (this.permission_overwritesar.length === 0 && this.hasPermission("MANAGE_CHANNELS")) { if (this.permission_overwritesar.length === 0 && this.hasPermission("MANAGE_CHANNELS")) {
const role = this.guild.roles.find(_=>_.name === "@everyone"); const role = this.guild.roles.find((_) => _.name === "@everyone");
if (role) { if (role) {
this.addRoleToPerms(role); this.addRoleToPerms(role);
} }
@@ -449,7 +493,7 @@ 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;
div.draggable = admin; div.draggable = admin;
div.addEventListener("dragstart", e=>{ div.addEventListener("dragstart", (e) => {
Channel.dragged = [this, div]; Channel.dragged = [this, div];
e.stopImmediatePropagation(); e.stopImmediatePropagation();
}); });
@@ -475,7 +519,7 @@ class Channel extends SnowFlake{
const addchannel = document.createElement("span"); const addchannel = document.createElement("span");
addchannel.classList.add("addchannel", "svgicon", "svg-plus"); addchannel.classList.add("addchannel", "svgicon", "svg-plus");
caps.appendChild(addchannel); caps.appendChild(addchannel);
addchannel.onclick = _=>{ addchannel.onclick = (_) => {
this.guild.createchannels(this.createChannel.bind(this)); this.guild.createchannels(this.createChannel.bind(this));
}; };
this.coatDropDiv(decdiv, childrendiv); this.coatDropDiv(decdiv, childrendiv);
@@ -544,12 +588,16 @@ class Channel extends SnowFlake{
// //
const decoration = document.createElement("span"); const decoration = document.createElement("span");
button.appendChild(decoration); button.appendChild(decoration);
decoration.classList.add("space", "svgicon", this.nsfw?"svg-announcensfw":"svg-announce"); decoration.classList.add(
"space",
"svgicon",
this.nsfw ? "svg-announcensfw" : "svg-announce",
);
} else { } else {
console.log(this.type); console.log(this.type);
} }
button.appendChild(myhtml); button.appendChild(myhtml);
button.onclick = _=>{ button.onclick = (_) => {
this.getHTML(); this.getHTML();
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
toggle.checked = true; toggle.checked = true;
@@ -572,7 +620,7 @@ class Channel extends SnowFlake{
return; return;
} }
mainarea.style.left = x + "px"; mainarea.style.left = x + "px";
mainarea.style.transition="left 0s" mainarea.style.transition = "left 0s";
} }
async setUpVoice() { async setUpVoice() {
if (!this.voice) return; if (!this.voice) return;
@@ -585,20 +633,27 @@ class Channel extends SnowFlake{
if (this.voice === this.localuser.currentVoice) { if (this.voice === this.localuser.currentVoice) {
AVoice.noises("join"); AVoice.noises("join");
} }
} };
} }
async updateVoiceUsers() { async updateVoiceUsers() {
const voiceUsers = this.voiceUsers.deref(); const voiceUsers = this.voiceUsers.deref();
if (!voiceUsers || !this.voice) return; if (!voiceUsers || !this.voice) return;
console.warn(this.voice.userids) console.warn(this.voice.userids);
const html=(await Promise.all(this.voice.userids.entries().toArray().map(async _=>{ const html = (
await Promise.all(
this.voice.userids
.entries()
.toArray()
.map(async (_) => {
const user = await User.resolve(_[0], this.localuser); const user = await User.resolve(_[0], this.localuser);
console.log(user); console.log(user);
const member = await Member.resolveMember(user, this.guild); const member = await Member.resolveMember(user, this.guild);
const array=[member,_[1]] as [Member, typeof _[1]]; const array = [member, _[1]] as [Member, (typeof _)[1]];
return array; return array;
}))).flatMap(([member,_obj])=>{ }),
)
).flatMap(([member, _obj]) => {
if (!member) { if (!member) {
console.warn("This is weird, member doesn't exist :P"); console.warn("This is weird, member doesn't exist :P");
return []; return [];
@@ -627,30 +682,27 @@ class Channel extends SnowFlake{
this.guild.unreads(); this.guild.unreads();
return; return;
} }
fetch( fetch(this.info.api + "/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack", {
this.info.api +"/channels/" + this.id + "/messages/" + this.lastmessageid + "/ack",
{
method: "POST", method: "POST",
headers: this.headers, headers: this.headers,
body: JSON.stringify({}), body: JSON.stringify({}),
} });
);
this.lastreadmessageid = this.lastmessageid; this.lastreadmessageid = this.lastmessageid;
this.guild.unreads(); this.guild.unreads();
this.unreads(); this.unreads();
} }
coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false) { coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false) {
div.addEventListener("dragenter", event=>{ div.addEventListener("dragenter", (event) => {
console.log("enter"); console.log("enter");
event.preventDefault(); event.preventDefault();
}); });
div.addEventListener("dragover", event=>{ div.addEventListener("dragover", (event) => {
event.preventDefault(); event.preventDefault();
}); });
div.addEventListener("drop", event=>{ div.addEventListener("drop", (event) => {
const that = Channel.dragged[0]; const that = Channel.dragged[0];
if (!that) return; if (!that) return;
event.preventDefault(); event.preventDefault();
@@ -660,9 +712,7 @@ class Channel extends SnowFlake{
that.parent.children.splice(that.parent.children.indexOf(that), 1); that.parent.children.splice(that.parent.children.indexOf(that), 1);
} }
that.parent = this; that.parent = this;
(container as HTMLElement).prepend( (container as HTMLElement).prepend(Channel.dragged[1] as HTMLDivElement);
Channel.dragged[1] as HTMLDivElement
);
this.children.unshift(that); this.children.unshift(that);
} else { } else {
console.log(this, Channel.dragged); console.log(this, Channel.dragged);
@@ -670,10 +720,7 @@ class Channel extends SnowFlake{
if (that.parent) { if (that.parent) {
that.parent.children.splice(that.parent.children.indexOf(that), 1); that.parent.children.splice(that.parent.children.indexOf(that), 1);
} else { } else {
this.guild.headchannels.splice( this.guild.headchannels.splice(this.guild.headchannels.indexOf(that), 1);
this.guild.headchannels.indexOf(that),
1
);
} }
that.parent = this.parent; that.parent = this.parent;
if (that.parent) { if (that.parent) {
@@ -742,7 +789,7 @@ class Channel extends SnowFlake{
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = I18n.getTranslation("replyingTo", this.replyingto.author.username); span.textContent = I18n.getTranslation("replyingTo", this.replyingto.author.username);
const X = document.createElement("button"); const X = document.createElement("button");
X.onclick = _=>{ X.onclick = (_) => {
if (this.replyingto?.div) { if (this.replyingto?.div) {
this.replyingto.div.classList.remove("replying"); this.replyingto.div.classList.remove("replying");
} }
@@ -768,7 +815,7 @@ class Channel extends SnowFlake{
} else { } else {
const gety = await fetch( const gety = await fetch(
this.info.api + "/channels/" + this.id + "/messages?limit=1&around=" + id, this.info.api + "/channels/" + this.id + "/messages?limit=1&around=" + id,
{ headers: this.headers } {headers: this.headers},
); );
const json = await gety.json(); const json = await gety.json();
if (json.length === 0) { if (json.length === 0) {
@@ -778,10 +825,10 @@ class Channel extends SnowFlake{
} }
} }
async focus(id: string) { async focus(id: string) {
console.time() console.time();
console.log(await this.getmessage(id)); console.log(await this.getmessage(id));
await this.getHTML(); await this.getHTML();
console.timeEnd() console.timeEnd();
console.warn(id); console.warn(id);
this.infinite.focus(id); this.infinite.focus(id);
} }
@@ -800,9 +847,7 @@ class Channel extends SnowFlake{
(document.getElementById("upload") as HTMLElement).style.visibility = "hidden"; (document.getElementById("upload") as HTMLElement).style.visibility = "hidden";
(document.getElementById("typediv") as HTMLElement).style.visibility = "hidden"; (document.getElementById("typediv") as HTMLElement).style.visibility = "hidden";
const messages = document.getElementById("channelw") as HTMLDivElement; const messages = document.getElementById("channelw") as HTMLDivElement;
const messageContainers = Array.from( const messageContainers = Array.from(messages.getElementsByClassName("messagecontainer"));
messages.getElementsByClassName("messagecontainer")
);
for (const thing of messageContainers) { for (const thing of messageContainers) {
thing.remove(); thing.remove();
} }
@@ -828,17 +873,15 @@ class Channel extends SnowFlake{
}); });
buttons.addButtonInput("", "No", () => { buttons.addButtonInput("", "No", () => {
window.history.back(); window.history.back();
}) });
} else { } else {
options.addTitle("You are not allowed in this channel."); options.addTitle("You are not allowed in this channel.");
} }
const html = float.generateHTML(); const html = float.generateHTML();
html.classList.add("messagecontainer") html.classList.add("messagecontainer");
messages.append(html); messages.append(html);
} }
async getHTML(addstate = true) { async getHTML(addstate = true) {
if (addstate) { if (addstate) {
history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id); history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id);
} }
@@ -846,10 +889,7 @@ class Channel extends SnowFlake{
const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement; const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement;
if (this.topic) { if (this.topic) {
channelTopic.innerHTML = ""; channelTopic.innerHTML = "";
channelTopic.append(new MarkDown( channelTopic.append(new MarkDown(this.topic, this).makeHTML());
this.topic,
this
).makeHTML());
channelTopic.removeAttribute("hidden"); channelTopic.removeAttribute("hidden");
} else channelTopic.setAttribute("hidden", ""); } else channelTopic.setAttribute("hidden", "");
if (this.guild !== this.localuser.lookingguild) { if (this.guild !== this.localuser.lookingguild) {
@@ -869,8 +909,11 @@ class Channel extends SnowFlake{
this.guild.perminfo.prevchannel = this.id; this.guild.perminfo.prevchannel = this.id;
this.localuser.userinfo.updateLocal(); this.localuser.userinfo.updateLocal();
this.localuser.channelfocus = this; this.localuser.channelfocus = this;
//@ts-ignore another hack
if(this.nsfw&&(!this.perminfo.nsfwOk||!this.localuser.user.nsfw_allowed)){ if (
this.nsfw && //@ts-ignore another hack
(!this.perminfo.nsfwOk || !this.localuser.user.nsfw_allowed)
) {
this.nsfwPannel(); this.nsfwPannel();
return; return;
} }
@@ -886,7 +929,9 @@ class Channel extends SnowFlake{
this.localuser.joinVoice(this); this.localuser.joinVoice(this);
} }
(document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + this.canMessage; (document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + this.canMessage;
(document.getElementById("upload") as HTMLElement).style.visibility=this.canMessage?"visible":"hidden"; (document.getElementById("upload") as HTMLElement).style.visibility = this.canMessage
? "visible"
: "hidden";
(document.getElementById("typediv") as HTMLElement).style.visibility = "visible"; (document.getElementById("typediv") as HTMLElement).style.visibility = "visible";
(document.getElementById("typebox") as HTMLDivElement).focus(); (document.getElementById("typebox") as HTMLDivElement).focus();
await this.putmessages(); await this.putmessages();
@@ -898,7 +943,6 @@ class Channel extends SnowFlake{
await this.buildmessages(); await this.buildmessages();
//loading.classList.remove("loading"); //loading.classList.remove("loading");
} }
typingmap: Map<Member, number> = new Map(); typingmap: Map<Member, number> = new Map();
async typingStart(typing: startTypingjson): Promise<void> { async typingStart(typing: startTypingjson): Promise<void> {
@@ -915,7 +959,7 @@ class Channel extends SnowFlake{
} }
similar(str: string) { similar(str: string) {
if (this.type === 4) return -1; if (this.type === 4) return -1;
const strl=Math.max(str.length,1) const strl = Math.max(str.length, 1);
if (this.name.includes(str)) { if (this.name.includes(str)) {
return strl / this.name.length; return strl / this.name.length;
} else if (this.name.toLowerCase().includes(str.toLowerCase())) { } else if (this.name.toLowerCase().includes(str.toLowerCase())) {
@@ -949,9 +993,7 @@ class Channel extends SnowFlake{
if (this.localuser.channelfocus === this) { if (this.localuser.channelfocus === this) {
if (showing) { if (showing) {
typingtext.classList.remove("hidden"); typingtext.classList.remove("hidden");
const typingtext2 = document.getElementById( const typingtext2 = document.getElementById("typingtext") as HTMLDivElement;
"typingtext"
) as HTMLDivElement;
typingtext2.textContent = build; typingtext2.textContent = build;
} else { } else {
typingtext.classList.add("hidden"); typingtext.classList.add("hidden");
@@ -982,22 +1024,36 @@ class Channel extends SnowFlake{
} }
lastmessage: Message | undefined; lastmessage: Message | undefined;
setnotifcation() { setnotifcation() {
const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) const defualt = I18n.getTranslation(
const options=["all", "onlyMentions", "none","default"].map(e=>I18n.getTranslation("guild."+e,defualt)); "guild." + ["all", "onlyMentions", "none", "default"][this.guild.message_notifications],
);
const options = ["all", "onlyMentions", "none", "default"].map((e) =>
I18n.getTranslation("guild." + e, defualt),
);
const notiselect = new Dialog(""); const notiselect = new Dialog("");
const form=notiselect.options.addForm("",(_,sent:any)=>{ const form = notiselect.options.addForm(
"",
(_, sent: any) => {
notiselect.hide(); notiselect.hide();
console.log(sent); console.log(sent);
this.message_notifications = sent.channel_overrides[this.id].message_notifications; this.message_notifications = sent.channel_overrides[this.id].message_notifications;
},{ },
{
fetchURL: `${this.info.api}/users/@me/guilds/${this.guild.id}/settings/`, fetchURL: `${this.info.api}/users/@me/guilds/${this.guild.id}/settings/`,
method: "PATCH", method: "PATCH",
headers:this.headers headers: this.headers,
}); },
form.addSelect(I18n.getTranslation("guild.selectnoti"),"message_notifications",options,{ );
form.addSelect(
I18n.getTranslation("guild.selectnoti"),
"message_notifications",
options,
{
radio: true, radio: true,
defaultIndex:this.message_notifications defaultIndex: this.message_notifications,
},[0,1,2,3]); },
[0, 1, 2, 3],
);
form.addPreprocessor((e: any) => { form.addPreprocessor((e: any) => {
const message_notifications = e.message_notifications; const message_notifications = e.message_notifications;
@@ -1007,10 +1063,10 @@ class Channel extends SnowFlake{
message_notifications, message_notifications,
muted: this.muted, muted: this.muted,
mute_config: this.mute_config, mute_config: this.mute_config,
channel_id:this.id channel_id: this.id,
} },
} };
}) });
/* /*
let noti = this.message_notifications; let noti = this.message_notifications;
const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications]) const defualt=I18n.getTranslation("guild."+["all", "onlyMentions", "none","default"][this.guild.message_notifications])
@@ -1064,12 +1120,9 @@ class Channel extends SnowFlake{
if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) {
return; return;
} }
const j = await fetch( const j = await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100", {
this.info.api + "/channels/" + this.id + "/messages?limit=100",
{
headers: this.headers, headers: this.headers,
} });
);
const response = await j.json(); const response = await j.json();
if (response.length !== 100) { if (response.length !== 100) {
@@ -1101,15 +1154,13 @@ class Channel extends SnowFlake{
if (id === this.lastmessage?.id) { if (id === this.lastmessage?.id) {
return; return;
} }
await fetch( await fetch(this.info.api + "/channels/" + this.id + "/messages?limit=100&after=" + id, {
this.info.api + "/channels/" +this.id +"/messages?limit=100&after=" +id,{
headers: this.headers, headers: this.headers,
} })
) .then((j) => {
.then(j=>{
return j.json(); return j.json();
}) })
.then(response=>{ .then((response) => {
let previd: string = id; let previd: string = id;
for (const i in response) { for (const i in response) {
let messager: Message; let messager: Message;
@@ -1136,13 +1187,10 @@ class Channel extends SnowFlake{
return; return;
} }
await fetch( await fetch(this.info.api + "/channels/" + this.id + "/messages?before=" + id + "&limit=100", {
this.info.api + "/channels/" + this.id +"/messages?before=" + id + "&limit=100",
{
headers: this.headers, headers: this.headers,
} })
) .then((j) => {
.then(j=>{
return j.json(); return j.json();
}) })
.then((response: messagejson[]) => { .then((response: messagejson[]) => {
@@ -1195,9 +1243,7 @@ class Channel extends SnowFlake{
if (this.infinitefocus) return; if (this.infinitefocus) return;
this.infinitefocus = true; this.infinitefocus = true;
const messages = document.getElementById("channelw") as HTMLDivElement; const messages = document.getElementById("channelw") as HTMLDivElement;
const messageContainers = Array.from( const messageContainers = Array.from(messages.getElementsByClassName("messagecontainer"));
messages.getElementsByClassName("messagecontainer")
);
for (const thing of messageContainers) { for (const thing of messageContainers) {
thing.remove(); thing.remove();
} }
@@ -1208,7 +1254,6 @@ class Channel extends SnowFlake{
if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) {
id = this.lastreadmessageid; id = this.lastreadmessageid;
} else if (this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))) { } else if (this.lastreadmessageid && (id = this.findClosest(this.lastreadmessageid))) {
} else if (this.lastmessageid && this.messages.has(this.lastmessageid)) { } else if (this.lastmessageid && this.messages.has(this.lastmessageid)) {
id = this.goBackIds(this.lastmessageid, 50); id = this.goBackIds(this.lastmessageid, 50);
} }
@@ -1236,18 +1281,14 @@ class Channel extends SnowFlake{
} }
messages.append(await this.infinite.getDiv(id)); messages.append(await this.infinite.getDiv(id));
this.infinite.updatestuff(); this.infinite.updatestuff();
this.infinite.watchForChange().then(async _=>{ this.infinite.watchForChange().then(async (_) => {
//await new Promise(resolve => setTimeout(resolve, 0)); //await new Promise(resolve => setTimeout(resolve, 0));
this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P
loading.classList.remove("loading"); loading.classList.remove("loading");
}); });
//this.infinite.focus(id.id,false); //this.infinite.focus(id.id,false);
} }
private goBackIds( private goBackIds(id: string, back: number, returnifnotexistant = true): string | undefined {
id: string,
back: number,
returnifnotexistant = true
): string | undefined{
while (back !== 0) { while (back !== 0) {
const nextid = this.idToPrev.get(id); const nextid = this.idToPrev.get(id);
if (nextid) { if (nextid) {
@@ -1299,10 +1340,7 @@ class Channel extends SnowFlake{
if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") { if (thing.id === "1182819038095799904" || thing.id === "1182820803700625444") {
continue; continue;
} }
this.permission_overwrites.set( this.permission_overwrites.set(thing.id, new Permissions(thing.allow, thing.deny));
thing.id,
new Permissions(thing.allow, thing.deny)
);
const permisions = this.permission_overwrites.get(thing.id); const permisions = this.permission_overwrites.get(thing.id);
if (permisions) { if (permisions) {
const role = this.guild.roleids.get(thing.id); const role = this.guild.roleids.get(thing.id);
@@ -1316,7 +1354,7 @@ class Channel extends SnowFlake{
for (const thing of nchange) { for (const thing of nchange) {
const role = this.guild.roleids.get(thing); const role = this.guild.roleids.get(thing);
if (role) { if (role) {
this.croleUpdate(role,new Permissions("0"),false) this.croleUpdate(role, new Permissions("0"), false);
} }
} }
for (const thing of pchange) { for (const thing of pchange) {
@@ -1365,7 +1403,7 @@ class Channel extends SnowFlake{
{ {
attachments = [], attachments = [],
replyingto = null, replyingto = null,
}: { attachments: Blob[]; embeds: embedjson; replyingto: Message | null } }: {attachments: Blob[]; embeds: embedjson; replyingto: Message | null},
) { ) {
let replyjson: any; let replyjson: any;
if (replyingto) { if (replyingto) {
@@ -1420,7 +1458,7 @@ class Channel extends SnowFlake{
this.myhtml.classList.add("cunread"); this.myhtml.classList.add("cunread");
} }
if (this.mentions !== 0) { if (this.mentions !== 0) {
this.myhtml?.classList.add("mentioned") this.myhtml?.classList.add("mentioned");
} }
} }
} }
@@ -1453,23 +1491,17 @@ class Channel extends SnowFlake{
if (messagez.author === this.localuser.user) { if (messagez.author === this.localuser.user) {
return; return;
} }
if( if (this.localuser.lookingguild?.prevchannel === this && document.hasFocus()) {
this.localuser.lookingguild?.prevchannel === this && 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" && messagez.mentionsuser(this.localuser.user)) {
this.notification === "mentions" && messagez.mentionsuser(this.localuser.user)
){
this.notify(messagez); this.notify(messagez);
} }
} }
notititle(message: Message): string { notititle(message: Message): string {
return( return message.author.username + " > " + this.guild.properties.name + " > " + this.name;
message.author.username + " > " + this.guild.properties.name + " > " + this.name
);
} }
notify(message: Message, deep = 0) { notify(message: Message, deep = 0) {
if (this.localuser.play) { if (this.localuser.play) {
@@ -1500,7 +1532,7 @@ class Channel extends SnowFlake{
icon: message.author.getpfpsrc(this.guild), icon: message.author.getpfpsrc(this.guild),
image: imgurl, image: imgurl,
}); });
notification.addEventListener("click", _=>{ notification.addEventListener("click", (_) => {
window.focus(); window.focus();
this.getHTML(); this.getHTML();
}); });
@@ -1514,9 +1546,7 @@ class Channel extends SnowFlake{
} }
} }
async addRoleToPerms(role: Role) { async addRoleToPerms(role: Role) {
await fetch( await fetch(this.info.api + "/channels/" + this.id + "/permissions/" + role.id, {
this.info.api + "/channels/" + this.id + "/permissions/" + role.id,
{
method: "PUT", method: "PUT",
headers: this.headers, headers: this.headers,
body: JSON.stringify({ body: JSON.stringify({
@@ -1525,8 +1555,7 @@ class Channel extends SnowFlake{
id: role.id, id: role.id,
type: 0, type: 0,
}), }),
} });
);
const perm = new Permissions("0", "0"); const perm = new Permissions("0", "0");
this.permission_overwrites.set(role.id, perm); this.permission_overwrites.set(role.id, perm);
this.permission_overwritesar.push([role, perm]); this.permission_overwritesar.push([role, perm]);
@@ -1539,9 +1568,7 @@ class Channel extends SnowFlake{
} else { } else {
//this.permission_overwrites.set(id,perms); //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({
@@ -1550,11 +1577,8 @@ class Channel extends SnowFlake{
id, id,
type: 0, type: 0,
}), }),
} });
);
} }
} }
Channel.setupcontextmenu(); Channel.setupcontextmenu();
export {Channel}; export {Channel};

View File

@@ -8,12 +8,12 @@ class Contextmenu<x, y>{
string | null, string | null,
(this: x, arg: y) => boolean, (this: x, arg: y) => boolean,
(this: x, arg: y) => boolean, (this: x, arg: y) => boolean,
string string,
][]; ][];
div!: HTMLDivElement; div!: HTMLDivElement;
static setup() { static setup() {
Contextmenu.currentmenu = ""; Contextmenu.currentmenu = "";
document.addEventListener("click", event=>{ document.addEventListener("click", (event) => {
if (Contextmenu.currentmenu === "") { if (Contextmenu.currentmenu === "") {
return; return;
} }
@@ -31,8 +31,8 @@ class Contextmenu<x, y>{
text: string | (() => 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,
enabled: (this: x, arg: y) => boolean = _=>true enabled: (this: x, arg: y) => boolean = (_) => true,
) { ) {
this.buttons.push([text, onclick, img, shown, enabled, "button"]); this.buttons.push([text, onclick, img, shown, enabled, "button"]);
return {}; return {};
@@ -41,8 +41,8 @@ class Contextmenu<x, y>{
text: string | (() => string), text: string | (() => string),
onclick: (this: x, arg: y, e: MouseEvent) => void, onclick: (this: x, arg: y, e: MouseEvent) => void,
img = null, img = null,
shown: (this: x, arg: y) => boolean = _=>true, shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = _=>true enabled: (this: x, arg: y) => boolean = (_) => true,
) { ) {
this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); this.buttons.push([text, onclick, img, shown, enabled, "submenu"]);
return {}; return {};
@@ -68,7 +68,7 @@ class Contextmenu<x, y>{
if (thing[5] === "button" || thing[5] === "submenu") { if (thing[5] === "button" || thing[5] === "submenu") {
intext.onclick = (e) => { intext.onclick = (e) => {
div.remove(); div.remove();
thing[1].call(addinfo, other,e) thing[1].call(addinfo, other, e);
}; };
} }
@@ -87,7 +87,13 @@ class Contextmenu<x, y>{
Contextmenu.currentmenu = div; Contextmenu.currentmenu = div;
return this.div; return this.div;
} }
bindContextmenu(obj: HTMLElement, addinfo: x, other: y,touchDrag:(x:number,y:number)=>unknown=()=>{},touchEnd:(x:number,y:number)=>unknown=()=>{}){ bindContextmenu(
obj: HTMLElement,
addinfo: x,
other: y,
touchDrag: (x: number, y: number) => unknown = () => {},
touchEnd: (x: number, y: number) => unknown = () => {},
) {
const func = (event: MouseEvent) => { const func = (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
@@ -98,7 +104,9 @@ class Contextmenu<x, y>{
let hold: NodeJS.Timeout | undefined; let hold: NodeJS.Timeout | undefined;
let x!: number; let x!: number;
let y!: number; let y!: number;
obj.addEventListener("touchstart",(event: TouchEvent)=>{ obj.addEventListener(
"touchstart",
(event: TouchEvent) => {
x = event.touches[0].pageX; x = event.touches[0].pageX;
y = event.touches[0].pageY; y = event.touches[0].pageY;
if (event.touches.length > 1) { if (event.touches.length > 1) {
@@ -112,9 +120,11 @@ class Contextmenu<x, y>{
if (lastx ** 2 + lasty ** 2 > 10 ** 2) return; if (lastx ** 2 + lasty ** 2 > 10 ** 2) return;
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other); this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
console.log(obj); console.log(obj);
},500) }, 500);
} }
},{passive: false}); },
{passive: false},
);
let lastx = 0; let lastx = 0;
let lasty = 0; let lasty = 0;
obj.addEventListener("touchend", () => { obj.addEventListener("touchend", () => {

View File

@@ -67,7 +67,7 @@ class Direct extends Guild{
ddiv.append(freindDiv); ddiv.append(freindDiv);
freindDiv.onclick = () => { freindDiv.onclick = () => {
this.loadChannel(null); this.loadChannel(null);
} };
ddiv.append(build); ddiv.append(build);
return ddiv; return ddiv;
@@ -90,14 +90,14 @@ class Direct extends Guild{
thing.remove(); thing.remove();
} }
const container = document.createElement("div"); const container = document.createElement("div");
container.classList.add("messagecontainer","flexttb","friendcontainer") container.classList.add("messagecontainer", "flexttb", "friendcontainer");
messages.append(container); messages.append(container);
const checkVoid = () => { const checkVoid = () => {
if (this.localuser.channelfocus !== undefined || this.localuser.lookingguild !== this) { if (this.localuser.channelfocus !== undefined || this.localuser.lookingguild !== this) {
this.localuser.relationshipsUpdate = () => {}; this.localuser.relationshipsUpdate = () => {};
} }
} };
function genuserstrip(user: User, icons: HTMLElement): HTMLElement { function genuserstrip(user: User, icons: HTMLElement): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("flexltr", "liststyle"); div.classList.add("flexltr", "liststyle");
@@ -136,11 +136,11 @@ class Direct extends Guild{
buttonc.onclick = (e) => { buttonc.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.opendm(); user.opendm();
} };
container.append(genuserstrip(user, buttonc)); container.append(genuserstrip(user, buttonc));
} }
} }
} };
online.onclick = genOnline; online.onclick = genOnline;
genOnline(); genOnline();
} }
@@ -162,11 +162,11 @@ class Direct extends Guild{
buttonc.onclick = (e) => { buttonc.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.opendm(); user.opendm();
} };
container.append(genuserstrip(user, buttonc)); container.append(genuserstrip(user, buttonc));
} }
} }
} };
all.onclick = genAll; all.onclick = genAll;
channelTopic.append(all); channelTopic.append(all);
} }
@@ -198,7 +198,7 @@ class Direct extends Guild{
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.changeRelationship(1); user.changeRelationship(1);
outerDiv.remove(); outerDiv.remove();
} };
} }
buttonc.append(button1); buttonc.append(button1);
buttonc.classList.add("friendlyButton"); buttonc.classList.add("friendlyButton");
@@ -206,13 +206,13 @@ class Direct extends Guild{
e.stopImmediatePropagation(); e.stopImmediatePropagation();
user.changeRelationship(0); user.changeRelationship(0);
outerDiv.remove(); outerDiv.remove();
} };
buttons.append(buttonc); buttons.append(buttonc);
const outerDiv = genuserstrip(user, buttons); const outerDiv = genuserstrip(user, buttons);
container.append(outerDiv); container.append(outerDiv);
} }
} }
} };
pending.onclick = genPending; pending.onclick = genPending;
channelTopic.append(pending); channelTopic.append(pending);
} }
@@ -236,12 +236,12 @@ class Direct extends Guild{
user.changeRelationship(0); user.changeRelationship(0);
e.stopImmediatePropagation(); e.stopImmediatePropagation();
outerDiv.remove(); outerDiv.remove();
} };
const outerDiv = genuserstrip(user, buttonc); const outerDiv = genuserstrip(user, buttonc);
container.append(outerDiv); container.append(outerDiv);
} }
} }
} };
blocked.onclick = genBlocked; blocked.onclick = genBlocked;
channelTopic.append(blocked); channelTopic.append(blocked);
} }
@@ -253,7 +253,9 @@ class Direct extends Guild{
container.innerHTML = ""; container.innerHTML = "";
const float = new Float(""); const float = new Float("");
const options = float.options; const options = float.options;
const form=options.addForm("",(e:any)=>{ const form = options.addForm(
"",
(e: any) => {
console.log(e); console.log(e);
if (e.code === 404) { if (e.code === 404) {
throw new FormError(text, I18n.getTranslation("friends.notfound")); throw new FormError(text, I18n.getTranslation("friends.notfound"));
@@ -264,11 +266,13 @@ class Direct extends Guild{
if (!box) return; if (!box) return;
box.value = ""; box.value = "";
} }
},{ },
{
method: "POST", method: "POST",
fetchURL: this.info.api + "/users/@me/relationships", fetchURL: this.info.api + "/users/@me/relationships",
headers:this.headers headers: this.headers,
}); },
);
const text = form.addTextInput(I18n.getTranslation("friends.addfriendpromt"), "username"); const text = form.addTextInput(I18n.getTranslation("friends.addfriendpromt"), "username");
form.addPreprocessor((obj: any) => { form.addPreprocessor((obj: any) => {
const [username, discriminator] = obj.username.split("#"); const [username, discriminator] = obj.username.split("#");
@@ -279,7 +283,7 @@ class Direct extends Guild{
} }
}); });
container.append(float.generateHTML()); container.append(float.generateHTML());
} };
channelTopic.append(add); channelTopic.append(add);
} }
} }
@@ -339,21 +343,33 @@ class Group extends Channel{
user: User; user: User;
static contextmenu = new Contextmenu<Group, undefined>("channel menu"); static contextmenu = new Contextmenu<Group, undefined>("channel menu");
static setupcontextmenu() { static setupcontextmenu() {
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.copyId"), function(this: Group){ this.contextmenu.addbutton(
() => I18n.getTranslation("DMs.copyId"),
function (this: Group) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); },
);
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.markRead"), function(this: Group){ this.contextmenu.addbutton(
() => I18n.getTranslation("DMs.markRead"),
function (this: Group) {
this.readbottom(); this.readbottom();
}); },
);
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.close"), function(this: Group){ this.contextmenu.addbutton(
() => I18n.getTranslation("DMs.close"),
function (this: Group) {
this.deleteChannel(); this.deleteChannel();
}); },
);
this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(){ this.contextmenu.addbutton(
() => I18n.getTranslation("user.copyId"),
function () {
navigator.clipboard.writeText(this.user.id); navigator.clipboard.writeText(this.user.id);
}); },
);
} }
constructor(json: dirrectjson, owner: Direct) { constructor(json: dirrectjson, owner: Direct) {
super(-1, owner, json.id); super(-1, owner, json.id);
@@ -395,7 +411,7 @@ class Group extends Channel{
div.appendChild(this.user.buildpfp()); div.appendChild(this.user.buildpfp());
div.appendChild(myhtml); div.appendChild(myhtml);
(div as any).myinfo = this; (div as any).myinfo = this;
div.onclick = _=>{ div.onclick = (_) => {
this.getHTML(); this.getHTML();
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
toggle.checked = true; toggle.checked = true;
@@ -436,7 +452,6 @@ class Group extends Channel{
return; return;
} }
this.buildmessages(); this.buildmessages();
} }
messageCreate(messagep: {d: messagejson}) { messageCreate(messagep: {d: messagejson}) {
this.mentions++; this.mentions++;
@@ -525,7 +540,7 @@ class Group extends Channel{
buildpfp.classList.add("mentioned"); buildpfp.classList.add("mentioned");
div.append(buildpfp); div.append(buildpfp);
sentdms.append(div); sentdms.append(div);
div.onclick = _=>{ div.onclick = (_) => {
this.guild.loadGuild(); this.guild.loadGuild();
this.getHTML(); this.getHTML();
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
@@ -545,4 +560,4 @@ class Group extends Channel{
} }
export {Direct, Group}; export {Direct, Group};
Group.setupcontextmenu() Group.setupcontextmenu();

View File

@@ -23,7 +23,7 @@ class ImagesDisplay{
this.background = document.createElement("div"); this.background = document.createElement("div");
this.background.classList.add("background"); this.background.classList.add("background");
this.background.appendChild(this.makeHTML()); this.background.appendChild(this.makeHTML());
this.background.onclick = _=>{ this.background.onclick = (_) => {
this.hide(); this.hide();
}; };
document.body.append(this.background); document.body.append(this.background);
@@ -34,4 +34,4 @@ class ImagesDisplay{
} }
} }
} }
export{ImagesDisplay} export {ImagesDisplay};

View File

@@ -17,12 +17,7 @@ class Embed{
} }
getType(json: embedjson) { getType(json: embedjson) {
const instances = getInstances(); const instances = getInstances();
if( if (instances && json.type === "link" && json.url && URL.canParse(json.url)) {
instances &&
json.type === "link" &&
json.url &&
URL.canParse(json.url)
){
const Url = new URL(json.url); const Url = new URL(json.url);
for (const instance of instances) { for (const instance of instances) {
if (instance.url && URL.canParse(instance.url)) { if (instance.url && URL.canParse(instance.url)) {
@@ -40,8 +35,7 @@ URL.canParse(json.url)
host = Url.host; host = Url.host;
} }
if (IUrl.host === host) { if (IUrl.host === host) {
const code = const code = Url.pathname.split("/")[Url.pathname.split("/").length - 1];
Url.pathname.split("/")[Url.pathname.split("/").length - 1];
json.invite = { json.invite = {
url: instance.url, url: instance.url,
code, code,
@@ -67,10 +61,7 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
case "article": case "article":
return this.generateArticle(); return this.generateArticle();
default: default:
console.warn( console.warn(`unsupported embed type ${this.type}, please add support dev :3`, this.json);
`unsupported embed type ${this.type}, please add support dev :3`,
this.json
);
return document.createElement("div"); //prevent errors by giving blank div return document.createElement("div"); //prevent errors by giving blank div
} }
} }
@@ -246,21 +237,21 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
let info: {cdn: string; api: string}; let info: {cdn: string; api: string};
if (!this.invcache) { if (!this.invcache) {
if (!json1) { if (!json1) {
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
const tempinfo = await getapiurls(json1.url); const tempinfo = await getapiurls(json1.url);
if (!tempinfo) { if (!tempinfo) {
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
info = tempinfo; info = tempinfo;
const res = await fetch(info.api + "/invites/" + json1.code); const res = await fetch(info.api + "/invites/" + json1.code);
if (!res.ok) { if (!res.ok) {
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
@@ -271,21 +262,24 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
} }
if (!json) { if (!json) {
div.append(this.generateLink()); div.append(this.generateLink());
div.classList.remove("embed", "inviteEmbed", "flexttb") div.classList.remove("embed", "inviteEmbed", "flexttb");
return; return;
} }
if (json.guild.banner) { if (json.guild.banner) {
const banner = document.createElement("img"); const banner = document.createElement("img");
banner.src = this.localuser.info.cdn + "/icons/" + json.guild.id + "/" + json.guild.banner + ".png?size=256"; banner.src =
this.localuser.info.cdn +
"/icons/" +
json.guild.id +
"/" +
json.guild.banner +
".png?size=256";
banner.classList.add("banner"); banner.classList.add("banner");
div.append(banner); div.append(banner);
} }
const guild: invitejson["guild"] & { info?: { cdn: string } } = const guild: invitejson["guild"] & {info?: {cdn: string}} = json.guild;
json.guild;
guild.info = info; guild.info = info;
const icon = Guild.generateGuildIcon( const icon = Guild.generateGuildIcon(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"); iconrow.classList.add("flexltr");
iconrow.append(icon); iconrow.append(icon);
@@ -315,14 +309,14 @@ guild as invitejson["guild"] & { info: { cdn: string } }
} }
button.classList.add("acceptinvbutton"); button.classList.add("acceptinvbutton");
div.append(button); div.append(button);
button.onclick = _=>{ button.onclick = (_) => {
if (this.localuser.info.api.startsWith(info.api)) { if (this.localuser.info.api.startsWith(info.api)) {
fetch(this.localuser.info.api + "/invites/" + json.code, { fetch(this.localuser.info.api + "/invites/" + json.code, {
method: "POST", method: "POST",
headers: this.localuser.headers, headers: this.localuser.headers,
}) })
.then(r=>r.json()) .then((r) => r.json())
.then(_=>{ .then((_) => {
if (_.message) { if (_.message) {
alert(_.message); alert(_.message);
} }

View File

@@ -34,10 +34,7 @@ class Emoji{
get info() { get info() {
return this.owner.info; return this.owner.info;
} }
constructor( constructor(json: emojijson, owner: Guild | Localuser) {
json: emojijson,
owner: Guild | Localuser
){
this.name = json.name; this.name = json.name;
this.id = json.id; this.id = json.id;
this.animated = json.animated || false; this.animated = json.animated || false;
@@ -50,7 +47,8 @@ class Emoji{
emojiElem.classList.add("md-emoji"); emojiElem.classList.add("md-emoji");
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji"); emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
emojiElem.crossOrigin = "anonymous"; emojiElem.crossOrigin = "anonymous";
emojiElem.src =this.info.cdn+"/emojis/"+this.id+"."+(this.animated ? "gif" : "png")+"?size=32"; emojiElem.src =
this.info.cdn + "/emojis/" + this.id + "." + (this.animated ? "gif" : "png") + "?size=32";
emojiElem.alt = this.name; emojiElem.alt = this.name;
emojiElem.loading = "lazy"; emojiElem.loading = "lazy";
return emojiElem; return emojiElem;
@@ -65,7 +63,7 @@ class Emoji{
} }
} }
static decodeEmojiList(buffer: ArrayBuffer) { static decodeEmojiList(buffer: ArrayBuffer) {
const reader=new BinRead(buffer) const reader = new BinRead(buffer);
const build: {name: string; emojis: {name: string; emoji: string}[]}[] = []; const build: {name: string; emojis: {name: string; emoji: string}[]}[] = [];
let cats = reader.read16(); let cats = reader.read16();
@@ -98,20 +96,16 @@ class Emoji{
} }
static grabEmoji() { static grabEmoji() {
fetch("/emoji.bin") fetch("/emoji.bin")
.then(e=>{ .then((e) => {
return e.arrayBuffer(); return e.arrayBuffer();
}) })
.then(e=>{ .then((e) => {
Emoji.decodeEmojiList(e); Emoji.decodeEmojiList(e);
}); });
} }
static async emojiPicker( static async emojiPicker(x: number, y: number, localuser: Localuser): Promise<Emoji | string> {
x: number,
y: number,
localuser: Localuser
): Promise<Emoji | string>{
let res: (r: Emoji | string) => void; let res: (r: Emoji | string) => void;
const promise: Promise<Emoji | string> = new Promise(r=>{ const promise: Promise<Emoji | string> = new Promise((r) => {
res = r; res = r;
}); });
const menu = document.createElement("div"); const menu = document.createElement("div");
@@ -130,8 +124,8 @@ class Emoji{
let isFirst = true; let isFirst = true;
localuser.guilds localuser.guilds
.filter(guild=>guild.id != "@me" && guild.emojis.length > 0) .filter((guild) => guild.id != "@me" && guild.emojis.length > 0)
.forEach(guild=>{ .forEach((guild) => {
const select = document.createElement("div"); const select = document.createElement("div");
select.classList.add("emojiSelect"); select.classList.add("emojiSelect");
@@ -139,14 +133,20 @@ class Emoji{
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("pfp", "servericon", "emoji-server"); img.classList.add("pfp", "servericon", "emoji-server");
img.crossOrigin = "anonymous"; img.crossOrigin = "anonymous";
img.src = localuser.info.cdn+"/icons/"+guild.properties.id+"/"+guild.properties.icon+".png?size=48"; img.src =
localuser.info.cdn +
"/icons/" +
guild.properties.id +
"/" +
guild.properties.icon +
".png?size=48";
img.alt = "Server: " + guild.properties.name; img.alt = "Server: " + guild.properties.name;
select.appendChild(img); select.appendChild(img);
} else { } else {
const div = document.createElement("span"); const div = document.createElement("span");
div.textContent = guild.properties.name div.textContent = guild.properties.name
.replace(/'s /g, " ") .replace(/'s /g, " ")
.replace(/\w+/g, word=>word[0]) .replace(/\w+/g, (word) => word[0])
.replace(/\s/g, ""); .replace(/\s/g, "");
select.append(div); select.append(div);
} }
@@ -166,7 +166,7 @@ class Emoji{
name: emojit.name, name: emojit.name,
animated: emojit.animated as boolean, animated: emojit.animated as boolean,
}, },
localuser localuser,
); );
emojiElem.append(emojiClass.getHTML()); emojiElem.append(emojiClass.getHTML());
body.append(emojiElem); body.append(emojiElem);
@@ -210,7 +210,7 @@ class Emoji{
emoji.classList.add("emojiSelect"); emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji; emoji.textContent = emojit.emoji;
body.append(emoji); body.append(emoji);
emoji.onclick = _=>{ emoji.onclick = (_) => {
res(emojit.emoji); res(emojit.emoji);
if (Contextmenu.currentmenu !== "") { if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
@@ -243,7 +243,7 @@ class Emoji{
} }
for (const group of this.emojis) { for (const group of this.emojis) {
for (const emoji of group.emojis) { for (const emoji of group.emojis) {
similar(emoji) similar(emoji);
} }
} }
const weakGuild = new WeakMap<emojijson, Guild>(); const weakGuild = new WeakMap<emojijson, Guild>();
@@ -252,15 +252,14 @@ class Emoji{
for (const emoji of guild.emojis) { for (const emoji of guild.emojis) {
if (similar(emoji)) { if (similar(emoji)) {
weakGuild.set(emoji, guild); weakGuild.set(emoji, guild);
}; }
} }
} }
} }
ranked.sort((a, b) => b[1] - a[1]); ranked.sort((a, b) => b[1] - a[1]);
return ranked.splice(0,results).map(a=>{ return ranked.splice(0, results).map((a) => {
return [new Emoji(a[0], weakGuild.get(a[0]) || localuser), a[1]]; return [new Emoji(a[0], weakGuild.get(a[0]) || localuser), a[1]];
});
})
} }
} }
Emoji.grabEmoji(); Emoji.grabEmoji();

View File

@@ -84,7 +84,7 @@ class File{
const icon = document.createElement("span"); const icon = document.createElement("span");
icon.classList.add("svgicon", "svg-delete"); icon.classList.add("svgicon", "svg-delete");
garbage.append(icon); garbage.append(icon);
garbage.onclick = _=>{ garbage.onclick = (_) => {
div.remove(); div.remove();
files.splice(files.indexOf(file), 1); files.splice(files.indexOf(file), 1);
}; };
@@ -105,7 +105,7 @@ class File{
url: URL.createObjectURL(file), url: URL.createObjectURL(file),
proxy_url: undefined, proxy_url: undefined,
}, },
null null,
); );
} }
createunknown(): HTMLElement { createunknown(): HTMLElement {
@@ -143,7 +143,9 @@ class File{
static filesizehuman(fsize: number) { static filesizehuman(fsize: number) {
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
return ( return (
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + " " + ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 +
" " +
["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] // I don't think this changes across languages, correct me if I'm wrong
); );
} }
} }

View File

@@ -6,7 +6,14 @@ import{ Member }from"./member.js";
import {Dialog, Options, Settings} from "./settings.js"; import {Dialog, Options, Settings} from "./settings.js";
import {Permissions} from "./permissions.js"; import {Permissions} from "./permissions.js";
import {SnowFlake} from "./snowflake.js"; import {SnowFlake} from "./snowflake.js";
import{channeljson,guildjson,emojijson,memberjson,invitejson,rolesjson, emojipjson,}from"./jsontypes.js"; import {
channeljson,
guildjson,
memberjson,
invitejson,
rolesjson,
emojipjson,
} from "./jsontypes.js";
import {User} from "./user.js"; import {User} from "./user.js";
import {I18n} from "./i18n.js"; import {I18n} from "./i18n.js";
import {Emoji} from "./emoji.js"; import {Emoji} from "./emoji.js";
@@ -32,23 +39,32 @@ class Guild extends SnowFlake{
members = new Set<Member>(); members = new Set<Member>();
static contextmenu = new Contextmenu<Guild, undefined>("guild menu"); static contextmenu = new Contextmenu<Guild, undefined>("guild menu");
static setupcontextmenu() { static setupcontextmenu() {
Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.copyId"), function(this: Guild){ Guild.contextmenu.addbutton(
() => I18n.getTranslation("guild.copyId"),
function (this: Guild) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); },
);
Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.markRead"), function(this: Guild){ Guild.contextmenu.addbutton(
() => I18n.getTranslation("guild.markRead"),
function (this: Guild) {
this.markAsRead(); this.markAsRead();
}); },
);
Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.notifications"), function(this: Guild){ Guild.contextmenu.addbutton(
() => I18n.getTranslation("guild.notifications"),
function (this: Guild) {
this.setnotifcation(); this.setnotifcation();
}); },
);
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("user.editServerProfile"), () => I18n.getTranslation("user.editServerProfile"),
function () { function () {
this.member.showEditProfile(); this.member.showEditProfile();
} },
); );
Guild.contextmenu.addbutton( Guild.contextmenu.addbutton(
@@ -59,7 +75,7 @@ class Guild extends SnowFlake{
null, null,
function (_) { function (_) {
return this.properties.owner_id !== this.member.user.id; return this.properties.owner_id !== this.member.user.id;
} },
); );
Guild.contextmenu.addbutton( Guild.contextmenu.addbutton(
@@ -70,7 +86,7 @@ class Guild extends SnowFlake{
null, null,
function (_) { function (_) {
return this.properties.owner_id === this.member.user.id; return this.properties.owner_id === this.member.user.id;
} },
); );
Guild.contextmenu.addbutton( Guild.contextmenu.addbutton(
@@ -81,16 +97,21 @@ class Guild extends SnowFlake{
d.show(); d.show();
}, },
null, null,
_=>true, (_) => true,
function () { function () {
return this.member.hasPermission("CREATE_INSTANT_INVITE"); return this.member.hasPermission("CREATE_INSTANT_INVITE");
} },
); );
Guild.contextmenu.addbutton(()=>I18n.getTranslation("guild.settings"), function(this: Guild){ Guild.contextmenu.addbutton(
() => I18n.getTranslation("guild.settings"),
function (this: Guild) {
this.generateSettings(); this.generateSettings();
},null,function(){ },
null,
function () {
return this.member.hasPermission("MANAGE_GUILD"); return this.member.hasPermission("MANAGE_GUILD");
}); },
);
/* -----things left for later----- /* -----things left for later-----
guild.contextmenu.addbutton("Leave Guild",function(){ guild.contextmenu.addbutton("Leave Guild",function(){
console.log(this) console.log(this)
@@ -104,19 +125,21 @@ class Guild extends SnowFlake{
} }
generateSettings() { generateSettings() {
const settings = new Settings(I18n.getTranslation("guild.settingsFor", this.properties.name)); const settings = new Settings(I18n.getTranslation("guild.settingsFor", this.properties.name));
const textChannels=this.channels.filter(e=>{ const textChannels = this.channels.filter((e) => {
//TODO there are almost certainly more types. is Voice valid? //TODO there are almost certainly more types. is Voice valid?
return new Set([0, 5]).has(e.type); return new Set([0, 5]).has(e.type);
}); });
{ {
const overview = settings.addButton(I18n.getTranslation("guild.overview")); const overview = settings.addButton(I18n.getTranslation("guild.overview"));
const form = overview.addForm("", _=>{}, { const form = overview.addForm("", (_) => {}, {
headers: this.headers, headers: this.headers,
traditionalSubmit: true, traditionalSubmit: true,
fetchURL: this.info.api + "/guilds/" + this.id, fetchURL: this.info.api + "/guilds/" + this.id,
method: "PATCH", method: "PATCH",
}); });
form.addTextInput(I18n.getTranslation("guild.name:"), "name", { initText: this.properties.name }); form.addTextInput(I18n.getTranslation("guild.name:"), "name", {
initText: this.properties.name,
});
form.addMDInput(I18n.getTranslation("guild.description:"), "description", { form.addMDInput(I18n.getTranslation("guild.description:"), "description", {
initText: this.properties.description, initText: this.properties.description,
}); });
@@ -126,22 +149,26 @@ class Guild extends SnowFlake{
form.addHR(); form.addHR();
const sysmap=[null,...textChannels.map(e=>e.id)]; const sysmap = [null, ...textChannels.map((e) => e.id)];
form.addSelect(I18n.getTranslation("guild.systemSelect:"), "system_channel_id", form.addSelect(
["No system messages",...textChannels.map(e=>e.name)],{defaultIndex:sysmap.indexOf(this.properties.system_channel_id)} I18n.getTranslation("guild.systemSelect:"),
,sysmap); "system_channel_id",
["No system messages", ...textChannels.map((e) => e.name)],
{defaultIndex: sysmap.indexOf(this.properties.system_channel_id)},
sysmap,
);
form.addCheckboxInput(I18n.getTranslation("guild.sendrandomwelcome?"), "s1", { form.addCheckboxInput(I18n.getTranslation("guild.sendrandomwelcome?"), "s1", {
initState:!(this.properties.system_channel_flags&1) initState: !(this.properties.system_channel_flags & 1),
}); });
form.addCheckboxInput(I18n.getTranslation("guild.stickWelcomeReact?"), "s4", { form.addCheckboxInput(I18n.getTranslation("guild.stickWelcomeReact?"), "s4", {
initState:!(this.properties.system_channel_flags&8) initState: !(this.properties.system_channel_flags & 8),
}); });
form.addCheckboxInput(I18n.getTranslation("guild.boostMessage?"), "s2", { form.addCheckboxInput(I18n.getTranslation("guild.boostMessage?"), "s2", {
initState:!(this.properties.system_channel_flags&2) initState: !(this.properties.system_channel_flags & 2),
}); });
form.addCheckboxInput(I18n.getTranslation("guild.helpTips?"), "s3", { form.addCheckboxInput(I18n.getTranslation("guild.helpTips?"), "s3", {
initState:!(this.properties.system_channel_flags&4) initState: !(this.properties.system_channel_flags & 4),
}); });
form.addPreprocessor((e: any) => { form.addPreprocessor((e: any) => {
let bits = 0; let bits = 0;
@@ -154,15 +181,19 @@ class Guild extends SnowFlake{
bits += (1 - e.s4) * 8; bits += (1 - e.s4) * 8;
delete e.s4; delete e.s4;
e.system_channel_flags = bits; e.system_channel_flags = bits;
}) });
form.addHR(); form.addHR();
form.addSelect(I18n.getTranslation("guild.defaultNoti"),"default_message_notifications", form.addSelect(
I18n.getTranslation("guild.defaultNoti"),
"default_message_notifications",
[I18n.getTranslation("guild.onlyMentions"), I18n.getTranslation("guild.all")], [I18n.getTranslation("guild.onlyMentions"), I18n.getTranslation("guild.all")],
{ {
defaultIndex: [1, 0].indexOf(this.properties.default_message_notifications), defaultIndex: [1, 0].indexOf(this.properties.default_message_notifications),
radio:true radio: true,
},[1,0]); },
[1, 0],
);
form.addHR(); form.addHR();
let region = this.properties.region; let region = this.properties.region;
if (!region) { if (!region) {
@@ -170,26 +201,31 @@ class Guild extends SnowFlake{
} }
form.addTextInput(I18n.getTranslation("guild.region:"), "region", {initText: region}); form.addTextInput(I18n.getTranslation("guild.region:"), "region", {initText: region});
} }
this.makeInviteMenu(settings.addButton(I18n.getTranslation("invite.inviteMaker")),textChannels); this.makeInviteMenu(
settings.addButton(I18n.getTranslation("invite.inviteMaker")),
textChannels,
);
const s1 = settings.addButton(I18n.getTranslation("guild.roles")); const s1 = settings.addButton(I18n.getTranslation("guild.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), false));
new RoleList(permlist, this, this.updateRolePermissions.bind(this),false)
);
{ {
const emoji = settings.addButton("Emojis"); const emoji = settings.addButton("Emojis");
emoji.addButtonInput("", "Upload Emoji", () => { emoji.addButtonInput("", "Upload Emoji", () => {
const popup = new Dialog("Upload emoji"); const popup = new Dialog("Upload emoji");
const form=popup.options.addForm("",()=>{ const form = popup.options.addForm(
"",
() => {
popup.hide(); popup.hide();
},{ },
{
fetchURL: `${this.info.api}/guilds/${this.id}/emojis`, fetchURL: `${this.info.api}/guilds/${this.id}/emojis`,
method: "POST", method: "POST",
headers:this.headers headers: this.headers,
}); },
);
form.addFileInput("Image:", "image", {required: true}); form.addFileInput("Image:", "image", {required: true});
form.addTextInput("Name:", "name", {required: true}); form.addTextInput("Name:", "name", {required: true});
popup.show(); popup.show();
@@ -209,8 +245,10 @@ class Guild extends SnowFlake{
fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`, { fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`, {
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
body:JSON.stringify({name:text.value}) body: JSON.stringify({name: text.value}),
}).then(e=>{if(!e.ok)text.value=emoji.name;})//if not ok, undo }).then((e) => {
if (!e.ok) text.value = emoji.name;
}); //if not ok, undo
}); });
const del = document.createElement("span"); const del = document.createElement("span");
@@ -222,37 +260,36 @@ class Guild extends SnowFlake{
options.addButtonInput("", I18n.getTranslation("yes"), () => { options.addButtonInput("", I18n.getTranslation("yes"), () => {
fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`, { fetch(`${this.info.api}/guilds/${this.id}/emojis/${emoji.id}`, {
method: "DELETE", method: "DELETE",
headers:this.headers headers: this.headers,
}) });
diaolog.hide(); diaolog.hide();
}); });
options.addButtonInput("", I18n.getTranslation("no"), () => { options.addButtonInput("", I18n.getTranslation("no"), () => {
diaolog.hide(); diaolog.hide();
}) });
diaolog.show(); diaolog.show();
} };
div.append(emojic.getHTML(true), ":", text, ":", del); div.append(emojic.getHTML(true), ":", text, ":", del);
containdiv.append(div); containdiv.append(div);
} }
} };
this.onEmojiUpdate = () => { this.onEmojiUpdate = () => {
if (!document.body.contains(containdiv)) { if (!document.body.contains(containdiv)) {
this.onEmojiUpdate = () => {}; this.onEmojiUpdate = () => {};
return; return;
} }
genDiv(); genDiv();
} };
genDiv(); genDiv();
emoji.addHTMLArea(containdiv); emoji.addHTMLArea(containdiv);
} }
settings.show(); settings.show();
} }
makeInviteMenu(options:Options,valid:void|(Channel[])){ makeInviteMenu(options: Options, valid: void | Channel[]) {
if (!valid) { if (!valid) {
valid=this.channels.filter(e=>{ valid = this.channels.filter((e) => {
//TODO there are almost certainly more types. is Voice valid? //TODO there are almost certainly more types. is Voice valid?
return new Set([0, 5]).has(e.type); return new Set([0, 5]).has(e.type);
}); });
@@ -270,7 +307,7 @@ class Guild extends SnowFlake{
const copy = document.createElement("span"); const copy = document.createElement("span");
copy.classList.add("copybutton", "svgicon", "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) {
navigator.clipboard.writeText(text.textContent); navigator.clipboard.writeText(text.textContent);
} }
@@ -286,11 +323,11 @@ class Guild extends SnowFlake{
target_user_id: null, target_user_id: null,
max_age: expires + "", max_age: expires + "",
max_uses: uses, max_uses: uses,
temporary: uses !== 0 temporary: uses !== 0,
}), }),
}) })
.then(_=>_.json()) .then((_) => _.json())
.then(json=>{ .then((json) => {
const params = new URLSearchParams(""); const params = new URLSearchParams("");
params.set("instance", this.info.wellknown); params.set("instance", this.info.wellknown);
const encoded = params.toString(); const encoded = params.toString();
@@ -300,43 +337,58 @@ class Guild extends SnowFlake{
options.addTitle(I18n.getTranslation("inviteOptions.title")); options.addTitle(I18n.getTranslation("inviteOptions.title"));
const text2 = options.addText(""); const text2 = options.addText("");
options.addSelect(I18n.getTranslation("invite.channel:"),()=>{},valid.map(e=>e.name)) options
.addSelect(
I18n.getTranslation("invite.channel:"),
() => {},
valid.map((e) => e.name),
)
.watchForChange((e) => { .watchForChange((e) => {
channel = valid[e]; channel = valid[e];
text2.setText(I18n.getTranslation("invite.subtext", channel.name, this.properties.name)); text2.setText(I18n.getTranslation("invite.subtext", channel.name, this.properties.name));
}) });
options.addSelect(
I18n.getTranslation("invite.expireAfter"),
() => {},
["30m", "1h", "6h", "12h", "1d", "7d", "30d", "never"].map((e) =>
I18n.getTranslation("inviteOptions." + e),
),
).onchange = (e) => {
expires = [1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];
};
options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{}, const timeOptions = ["1", "5", "10", "25", "50", "100"].map((e) =>
["30m","1h","6h","12h","1d","7d","30d","never"].map((e)=>I18n.getTranslation("inviteOptions."+e)) I18n.getTranslation("inviteOptions.limit", e),
).onchange=(e)=>{expires=[1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0][e];}; );
timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit"));
const timeOptions=["1","5","10","25","50","100"].map((e)=>I18n.getTranslation("inviteOptions.limit",e)) options.addSelect(I18n.getTranslation("invite.expireAfter"), () => {}, timeOptions).onchange = (
timeOptions.unshift(I18n.getTranslation("inviteOptions.noLimit")) e,
options.addSelect(I18n.getTranslation("invite.expireAfter"),()=>{},timeOptions) ) => {
.onchange=(e)=>{uses=[0, 1, 5, 10, 25, 50, 100][e];}; uses = [0, 1, 5, 10, 25, 50, 100][e];
};
options.addButtonInput("", I18n.getTranslation("invite.createInvite"), () => { options.addButtonInput("", I18n.getTranslation("invite.createInvite"), () => {
update(); update();
}) });
options.addHTMLArea(div); options.addHTMLArea(div);
} }
roleUpdate: (role: Role, added: -1 | 0 | 1) => unknown = () => {}; roleUpdate: (role: Role, added: -1 | 0 | 1) => unknown = () => {};
sortRoles() { sortRoles() {
this.roles.sort((a,b)=>(b.position-a.position)); this.roles.sort((a, b) => b.position - a.position);
} }
async recalcRoles() { async recalcRoles() {
let position = this.roles.length; let position = this.roles.length;
const map=this.roles.map(_=>{ const map = this.roles.map((_) => {
position--; position--;
return {id: _.id, position}; return {id: _.id, position};
}) });
await fetch(this.info.api + "/guilds/" + this.id + "/roles", { await fetch(this.info.api + "/guilds/" + this.id + "/roles", {
method: "PATCH", method: "PATCH",
body: JSON.stringify(map), body: JSON.stringify(map),
headers:this.headers headers: this.headers,
}) });
} }
newRole(rolej: rolesjson) { newRole(rolej: rolesjson) {
const role = new Role(rolej, this); const role = new Role(rolej, this);
@@ -374,11 +426,7 @@ class Guild extends SnowFlake{
this.roleUpdate(role, -1); this.roleUpdate(role, -1);
} }
onEmojiUpdate = (_: emojipjson[]) => {}; onEmojiUpdate = (_: emojipjson[]) => {};
constructor( constructor(json: guildjson | -1, owner: Localuser, member: memberjson | User | null) {
json: guildjson | -1,
owner: Localuser,
member: memberjson | User | null
){
if (json === -1 || member === null) { if (json === -1 || member === null) {
super("@me"); super("@me");
return; return;
@@ -406,7 +454,7 @@ class Guild extends SnowFlake{
this.sortRoles(); this.sortRoles();
if (member instanceof User) { if (member instanceof User) {
console.warn(member); console.warn(member);
Member.resolveMember(member, this).then(_=>{ Member.resolveMember(member, this).then((_) => {
if (_) { if (_) {
this.member = _; this.member = _;
} else { } else {
@@ -414,7 +462,7 @@ class Guild extends SnowFlake{
} }
}); });
} else { } else {
Member.new(member, this).then(_=>{ Member.new(member, this).then((_) => {
if (_) { if (_) {
this.member = _; this.member = _;
} }
@@ -443,7 +491,12 @@ class Guild extends SnowFlake{
this.localuser.perminfo.guilds[this.id] = e; this.localuser.perminfo.guilds[this.id] = e;
} }
notisetting(settings: { notisetting(settings: {
channel_overrides: {message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}[]; channel_overrides: {
message_notifications: number;
muted: boolean;
mute_config: {selected_time_window: number; end_time: number};
channel_id: string;
}[];
message_notifications: any; message_notifications: any;
flags?: number; flags?: number;
hide_muted_channels?: boolean; hide_muted_channels?: boolean;
@@ -465,29 +518,38 @@ class Guild extends SnowFlake{
} }
} }
setnotifcation() { setnotifcation() {
const options = ["all", "onlyMentions", "none"].map((e) => I18n.getTranslation("guild." + e));
const options=["all", "onlyMentions", "none"].map(e=>I18n.getTranslation("guild."+e));
const notiselect = new Dialog(""); const notiselect = new Dialog("");
const form=notiselect.options.addForm("",(_,sent:any)=>{ const form = notiselect.options.addForm(
"",
(_, sent: any) => {
notiselect.hide(); notiselect.hide();
this.message_notifications = sent.message_notifications; this.message_notifications = sent.message_notifications;
},{ },
{
fetchURL: `${this.info.api}/users/@me/guilds/${this.id}/settings/`, fetchURL: `${this.info.api}/users/@me/guilds/${this.id}/settings/`,
method: "PATCH", method: "PATCH",
headers:this.headers headers: this.headers,
}); },
form.addSelect(I18n.getTranslation("guild.selectnoti"),"message_notifications",options,{ );
form.addSelect(
I18n.getTranslation("guild.selectnoti"),
"message_notifications",
options,
{
radio: true, radio: true,
defaultIndex:this.message_notifications defaultIndex: this.message_notifications,
},[0,1,2]); },
[0, 1, 2],
);
notiselect.show(); notiselect.show();
} }
confirmleave() { confirmleave() {
const full = new Dialog(""); const full = new Dialog("");
full.options.addTitle(I18n.getTranslation("guild.confirmLeave")) full.options.addTitle(I18n.getTranslation("guild.confirmLeave"));
const options = full.options.addOptions("", {ltr: true}); const options = full.options.addOptions("", {ltr: true});
options.addButtonInput("", I18n.getTranslation("guild.yesLeave"), () => { options.addButtonInput("", I18n.getTranslation("guild.yesLeave"), () => {
this.leave().then(_=>{ this.leave().then((_) => {
full.hide(); full.hide();
}); });
}); });
@@ -619,7 +681,7 @@ class Guild extends SnowFlake{
} }
const build = name const build = name
.replace(/'s /g, " ") .replace(/'s /g, " ")
.replace(/\w+/g, word=>word[0]) .replace(/\w+/g, (word) => word[0])
.replace(/\s/g, ""); .replace(/\s/g, "");
div.textContent = build; div.textContent = build;
div.classList.add("blankserver", "servericon"); div.classList.add("blankserver", "servericon");
@@ -642,7 +704,8 @@ class Guild extends SnowFlake{
const full = new Dialog(""); const full = new Dialog("");
full.options.addTitle(I18n.getTranslation("guild.confirmDelete", this.properties.name)); full.options.addTitle(I18n.getTranslation("guild.confirmDelete", this.properties.name));
full.options.addTextInput(I18n.getTranslation("guild.serverName"),()=>{}).onchange=(e)=>confirmname=e; full.options.addTextInput(I18n.getTranslation("guild.serverName"), () => {}).onchange = (e) =>
(confirmname = e);
const options = full.options.addOptions("", {ltr: true}); const options = full.options.addOptions("", {ltr: true});
options.addButtonInput("", I18n.getTranslation("guild.yesDelete"), () => { options.addButtonInput("", I18n.getTranslation("guild.yesDelete"), () => {
@@ -651,7 +714,7 @@ class Guild extends SnowFlake{
alert("names don't match"); alert("names don't match");
return; return;
} }
this.delete().then(_=>{ this.delete().then((_) => {
full.hide(); full.hide();
}); });
}); });
@@ -815,7 +878,7 @@ class Guild extends SnowFlake{
thing.remove(); thing.remove();
} }
const h1 = document.createElement("h1"); const h1 = document.createElement("h1");
h1.classList.add("messagecontainer") h1.classList.add("messagecontainer");
h1.textContent = I18n.getTranslation("guild.emptytext"); h1.textContent = I18n.getTranslation("guild.emptytext");
messages.append(h1); messages.append(h1);
} }
@@ -853,7 +916,9 @@ class Guild extends SnowFlake{
return thischannel; return thischannel;
} }
createchannels(func = this.createChannel) { createchannels(func = this.createChannel) {
const options=["text", "announcement","voice"].map(e=>I18n.getTranslation("channel."+e)); const options = ["text", "announcement", "voice"].map((e) =>
I18n.getTranslation("channel." + e),
);
const channelselect = new Dialog(""); const channelselect = new Dialog("");
const form = channelselect.options.addForm("", (e: any) => { const form = channelselect.options.addForm("", (e: any) => {
@@ -861,7 +926,13 @@ class Guild extends SnowFlake{
channelselect.hide(); channelselect.hide();
}); });
form.addSelect(I18n.getTranslation("channel.selectType"),"type",options,{radio:true},[0,5,2]); form.addSelect(
I18n.getTranslation("channel.selectType"),
"type",
options,
{radio: true},
[0, 5, 2],
);
form.addTextInput(I18n.getTranslation("channel.selectName"), "name"); form.addTextInput(I18n.getTranslation("channel.selectName"), "name");
channelselect.show(); channelselect.show();
} }
@@ -913,9 +984,7 @@ class Guild extends SnowFlake{
}); });
} }
async createRole(name: string) { async createRole(name: string) {
const fetched = await fetch( const fetched = await fetch(this.info.api + "/guilds/" + this.id + "roles", {
this.info.api + "/guilds/" + this.id + "roles",
{
method: "POST", method: "POST",
headers: this.headers, headers: this.headers,
body: JSON.stringify({ body: JSON.stringify({
@@ -923,8 +992,7 @@ class Guild extends SnowFlake{
color: 0, color: 0,
permissions: "0", permissions: "0",
}), }),
} });
);
const json = await fetched.json(); const json = await fetched.json();
const role = new Role(json, this); const role = new Role(json, this);
this.roleids.set(role.id, role); this.roleids.set(role.id, role);

View File

@@ -1,39 +1,44 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description"> <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<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> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-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="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" <a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" class="TitleButtons">
class="TitleButtons">
Spacebar Guild Spacebar Guild
</a> </a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> <a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
Github <a href="/channels/@me" class="TitleButtons" id="openClient"> Open Client </a>
</a>
<a href="/channels/@me" class="TitleButtons" id="openClient">
Open Client
</a>
</div> </div>
<div id="homePage"> <div id="homePage">
<h1 class="pagehead" id="welcomeJank">Welcome to Jank Client</h1> <h1 class="pagehead" id="welcomeJank">Welcome to Jank Client</h1>
<div class="pagebox"> <div class="pagebox">
<p id="box1title">Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many features including:</p> <p id="box1title">
Jank Client is a Spacebar-compatible client seeking to be as good as it can be with many
features including:
</p>
<ul id="box1Items"> <ul id="box1Items">
<li>Direct Messaging</li> <li>Direct Messaging</li>
<li>Reactions support</li> <li>Reactions support</li>
@@ -47,18 +52,18 @@
</div> </div>
<div class="pagebox"> <div class="pagebox">
<h2 id="compatableInstances">Spacebar-Compatible Instances:</h2> <h2 id="compatableInstances">Spacebar-Compatible Instances:</h2>
<div id="instancebox"> <div id="instancebox"></div>
</div>
</div> </div>
<div class="pagebox"> <div class="pagebox">
<h2 id="box3title">Contribute to Jank Client</h2> <h2 id="box3title">Contribute to Jank Client</h2>
<p id="box3description">We always appreciate some help, whether that be in the form of bug reports, code, help translate, or even just pointing out some typos.</p><br> <p id="box3description">
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> We always appreciate some help, whether that be in the form of bug reports, code, help
Github translate, or even just pointing out some typos.
</a> </p>
<br />
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
</div> </div>
</div> </div>
</body> </body>
<script src="/home.js" type="module"></script> <script src="/home.js" type="module"></script>
</html> </html>

View File

@@ -5,14 +5,22 @@ const serverbox = document.getElementById("instancebox") as HTMLDivElement;
(async () => { (async () => {
await I18n.done; await I18n.done;
const openClient=document.getElementById("openClient") const openClient = document.getElementById("openClient");
const welcomeJank=document.getElementById("welcomeJank") const welcomeJank = document.getElementById("welcomeJank");
const box1title=document.getElementById("box1title") const box1title = document.getElementById("box1title");
const box1Items=document.getElementById("box1Items") const box1Items = document.getElementById("box1Items");
const compatableInstances=document.getElementById("compatableInstances") const compatableInstances = document.getElementById("compatableInstances");
const box3title=document.getElementById("box3title") const box3title = document.getElementById("box3title");
const box3description=document.getElementById("box3description") const box3description = document.getElementById("box3description");
if(openClient&&welcomeJank&&compatableInstances&&box3title&&box3description&&box1title&&box1Items){ if (
openClient &&
welcomeJank &&
compatableInstances &&
box3title &&
box3description &&
box1title &&
box1Items
) {
openClient.textContent = I18n.getTranslation("htmlPages.openClient"); openClient.textContent = I18n.getTranslation("htmlPages.openClient");
welcomeJank.textContent = I18n.getTranslation("htmlPages.welcomeJank"); welcomeJank.textContent = I18n.getTranslation("htmlPages.welcomeJank");
box1title.textContent = I18n.getTranslation("htmlPages.box1title"); box1title.textContent = I18n.getTranslation("htmlPages.box1title");
@@ -29,12 +37,20 @@ const serverbox = document.getElementById("instancebox") as HTMLDivElement;
i++; i++;
} }
} else { } else {
console.error(openClient,welcomeJank,compatableInstances,box3title,box3description,box1title,box1Items) console.error(
openClient,
welcomeJank,
compatableInstances,
box3title,
box3description,
box1title,
box1Items,
);
} }
})() })();
fetch("/instances.json") fetch("/instances.json")
.then(_=>_.json()) .then((_) => _.json())
.then( .then(
async ( async (
json: { json: {
@@ -53,7 +69,7 @@ fetch("/instances.json")
gateway: string; gateway: string;
login?: string; login?: string;
}; };
}[] }[],
) => { ) => {
await I18n.done; await I18n.done;
console.warn(json); console.warn(json);
@@ -98,16 +114,17 @@ fetch("/instances.json")
const stats = document.createElement("div"); const stats = document.createElement("div");
stats.classList.add("flexltr"); stats.classList.add("flexltr");
const span = document.createElement("span"); const span = document.createElement("span");
span.innerText = I18n.getTranslation("home.uptimeStats",Math.round( span.innerText = I18n.getTranslation(
instance.uptime.alltime * 100 "home.uptimeStats",
)+"",Math.round( Math.round(instance.uptime.alltime * 100) + "",
instance.uptime.weektime * 100 Math.round(instance.uptime.weektime * 100) + "",
)+"",Math.round(instance.uptime.daytime * 100)+"") Math.round(instance.uptime.daytime * 100) + "",
);
stats.append(span); stats.append(span);
statbox.append(stats); statbox.append(stats);
} }
div.append(statbox); div.append(statbox);
div.onclick = _=>{ div.onclick = (_) => {
if (instance.online) { if (instance.online) {
window.location.href = "/register.html?instance=" + encodeURI(instance.name); window.location.href = "/register.html?instance=" + encodeURI(instance.name);
} else { } else {
@@ -116,5 +133,5 @@ fetch("/instances.json")
}; };
serverbox.append(div); serverbox.append(div);
} }
} },
); );

View File

@@ -12,7 +12,7 @@ class Hover{
elm.addEventListener("mouseover", () => { elm.addEventListener("mouseover", () => {
timeOut = setTimeout(async () => { timeOut = setTimeout(async () => {
elm2 = await this.makeHover(elm); elm2 = await this.makeHover(elm);
},750) }, 750);
}); });
elm.addEventListener("mouseout", () => { elm.addEventListener("mouseout", () => {
clearTimeout(timeOut); clearTimeout(timeOut);
@@ -22,14 +22,15 @@ class Hover{
if (e[0].removedNodes) { if (e[0].removedNodes) {
clearTimeout(timeOut); clearTimeout(timeOut);
elm2.remove(); elm2.remove();
}; }
}).observe(elm, {childList: true}); }).observe(elm, {childList: true});
} }
async makeHover(elm: HTMLElement) { async makeHover(elm: HTMLElement) {
if(!document.contains(elm)) return document.createDocumentFragment() as unknown as HTMLDivElement; if (!document.contains(elm))
return document.createDocumentFragment() as unknown as HTMLDivElement;
const div = document.createElement("div"); const div = document.createElement("div");
if (this.str instanceof MarkDown) { if (this.str instanceof MarkDown) {
div.append(this.str.makeHTML()) div.append(this.str.makeHTML());
} else if (this.str instanceof Function) { } else if (this.str instanceof Function) {
const hover = await this.str(); const hover = await this.str();
if (hover instanceof MarkDown) { if (hover instanceof MarkDown) {
@@ -41,17 +42,17 @@ class Hover{
div.innerText = this.str; div.innerText = this.str;
} }
const box = elm.getBoundingClientRect(); const box = elm.getBoundingClientRect();
div.style.top=(box.bottom+4)+"px"; div.style.top = box.bottom + 4 + "px";
div.style.left = Math.floor(box.left + box.width / 2) + "px"; div.style.left = Math.floor(box.left + box.width / 2) + "px";
div.classList.add("hoverthing"); div.classList.add("hoverthing");
div.style.opacity = "0"; div.style.opacity = "0";
setTimeout(() => { setTimeout(() => {
div.style.opacity = "1"; div.style.opacity = "1";
},10) }, 10);
document.body.append(div); document.body.append(div);
Contextmenu.keepOnScreen(div); Contextmenu.keepOnScreen(div);
console.log(div, elm); console.log(div, elm);
return div; return div;
} }
} }
export{Hover} export {Hover};

View File

@@ -6,7 +6,7 @@ for(const lang of Object.keys(langs) as string[]){
} }
console.log(langs); console.log(langs);
type translation = { type translation = {
[key:string]:string|translation [key: string]: string | translation;
}; };
let res: () => unknown = () => {}; let res: () => unknown = () => {};
class I18n { class I18n {
@@ -16,12 +16,11 @@ class I18n{
res = res2; res = res2;
}); });
static async create(lang: string) { static async create(lang: string) {
const json = (await (await fetch("/translations/" + lang + ".json")).json()) as translation;
const json=await (await fetch("/translations/"+lang+".json")).json() as translation;
const translations: translation[] = []; const translations: translation[] = [];
translations.push(json); translations.push(json);
if (lang !== "en") { if (lang !== "en") {
translations.push(await (await fetch("/translations/en.json")).json() as translation); translations.push((await (await fetch("/translations/en.json")).json()) as translation);
} }
this.lang = lang; this.lang = lang;
this.translations = translations; this.translations = translations;
@@ -36,7 +35,6 @@ class I18n{
for (const thing of path) { for (const thing of path) {
if (typeof jsont !== "string" && jsont !== undefined) { if (typeof jsont !== "string" && jsont !== undefined) {
jsont = jsont[thing]; jsont = jsont[thing];
} else { } else {
jsont = json; jsont = json;
break; break;
@@ -51,7 +49,7 @@ class I18n{
if (str) { if (str) {
return this.fillInBlanks(str, params); return this.fillInBlanks(str, params);
} else { } else {
throw new Error(msg+" not found") throw new Error(msg + " not found");
} }
} }
static fillInBlanks(msg: string, params: string[]): string { static fillInBlanks(msg: string, params: string[]): string {
@@ -64,8 +62,7 @@ class I18n{
return match; return match;
} }
}); });
msg=msg.replace(/{{(.+?)}}/g, msg = msg.replace(/{{(.+?)}}/g, (str, match: string) => {
(str, match:string) => {
const [op, strsSplit] = this.fillInBlanks(match, params).split(":"); const [op, strsSplit] = this.fillInBlanks(match, params).split(":");
const [first, ...strs] = strsSplit.split("|"); const [first, ...strs] = strsSplit.split("|");
switch (op.toUpperCase()) { switch (op.toUpperCase()) {
@@ -91,13 +88,12 @@ class I18n{
} }
} }
return str; return str;
} });
);
return msg; return msg;
} }
static options() { static options() {
return [...langmap.keys()].map(e=>e.replace(".json","")); return [...langmap.keys()].map((e) => e.replace(".json", ""));
} }
static setLanguage(lang: string) { static setLanguage(lang: string) {
if (this.options().indexOf(userLocale) !== -1) { if (this.options().indexOf(userLocale) !== -1) {
@@ -115,7 +111,7 @@ const storage=localStorage.getItem("lang");
if (storage) { if (storage) {
userLocale = storage; userLocale = storage;
} else { } else {
localStorage.setItem("lang",userLocale) localStorage.setItem("lang", userLocale);
} }
I18n.create(userLocale); I18n.create(userLocale);

View File

@@ -1,23 +1,37 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description"> <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<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> <style>
<link rel="manifest" href="/manifest.json"> body.no-theme,
#loading {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme,
#loading {
background: #9397bd;
}
}
</style>
<link rel="manifest" href="/manifest.json" />
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div id="loading" class="loading"> <div id="loading" class="loading">
<div class="centeritem"> <div class="centeritem">
<img src="/logo.svg" style="width:3in;height:3in;"> <img src="/logo.svg" style="width: 3in; height: 3in" />
<h1 id="loadingText">Jank Client is loading</h1> <h1 id="loadingText">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>
<h1 id="switchaccounts">Switch Accounts</h1> <h1 id="switchaccounts">Switch Accounts</h1>
@@ -36,7 +50,7 @@
</div> </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="flexttb userflex"> <div class="flexttb userflex">
<p id="username">USERNAME</p> <p id="username">USERNAME</p>
@@ -55,8 +69,8 @@
<label for="maintoggle" id="maintoggleicon"> <label for="maintoggle" id="maintoggleicon">
<span class="svgicon svg-category"></span> <span class="svgicon svg-category"></span>
</label> </label>
<input type="checkbox" id="maintoggle"> <input type="checkbox" id="maintoggle" />
<span class="flexltr" style="align-items: center;"> <span class="flexltr" style="align-items: center">
<span id="channelname">Channel name</span> <span id="channelname">Channel name</span>
<span id="channelTopic" class="ellipsis" hidden>Channel topic</span> <span id="channelTopic" class="ellipsis" hidden>Channel topic</span>
</span> </span>
@@ -64,15 +78,14 @@
<label for="memberlisttoggle" id="memberlisttoggleicon"> <label for="memberlisttoggle" id="memberlisttoggleicon">
<span class="svgicon svg-friends"></span> <span class="svgicon svg-friends"></span>
</label> </label>
<input type="checkbox" id="memberlisttoggle" checked> <input type="checkbox" id="memberlisttoggle" checked />
</div> </div>
<div class="flexltr flexgrow"> <div class="flexltr flexgrow">
<div class="flexttb flexgrow"> <div class="flexttb flexgrow">
<div id="channelw" class="flexltr"> <div id="channelw" class="flexltr">
<div id="loadingdiv"> <div id="loadingdiv"></div>
</div> </div>
</div> <div style="position: relative">
<div style="position: relative;">
<div id="searchOptions" class="flexttb searchOptions"></div> <div id="searchOptions" class="flexttb searchOptions"></div>
</div> </div>
<div id="pasteimage" class="flexltr"></div> <div id="pasteimage" class="flexltr"></div>
@@ -81,7 +94,7 @@
<div id="realbox"> <div id="realbox">
<div class="outerTypeBox"> <div class="outerTypeBox">
<span class="svg-upload svgicon" id="upload"></span> <span class="svg-upload svgicon" id="upload"></span>
<div id="typebox" contentEditable="true"></div> <div id="typebox" contenteditable="true"></div>
</div> </div>
</div> </div>
<div id="typing" class="hidden flexltr"> <div id="typing" class="hidden flexltr">

View File

@@ -7,7 +7,7 @@ import{ Message }from"./message.js";
import {File} from "./file.js"; import {File} from "./file.js";
import {I18n} from "./i18n.js"; import {I18n} from "./i18n.js";
(async () => { (async () => {
await I18n.done await I18n.done;
const users = getBulkUsers(); const users = getBulkUsers();
if (!users.currentuser) { if (!users.currentuser) {
window.location.href = "/login.html"; window.location.href = "/login.html";
@@ -25,7 +25,7 @@ import { I18n } from "./i18n.js";
filedroptext.textContent = I18n.getTranslation("uploadFilesText"); filedroptext.textContent = I18n.getTranslation("uploadFilesText");
} }
} }
I18n I18n;
function showAccountSwitcher(): void { function showAccountSwitcher(): void {
const table = document.createElement("div"); const table = document.createElement("div");
table.classList.add("flexttb", "accountSwitcher"); table.classList.add("flexttb", "accountSwitcher");
@@ -94,13 +94,13 @@ import { I18n } from "./i18n.js";
} }
const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; const userInfoElement = document.getElementById("userinfo") as HTMLDivElement;
userInfoElement.addEventListener("click", event=>{ userInfoElement.addEventListener("click", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
const switchAccountsElement = document.getElementById("switchaccounts") as HTMLDivElement; const switchAccountsElement = document.getElementById("switchaccounts") as HTMLDivElement;
switchAccountsElement.addEventListener("click", event=>{ switchAccountsElement.addEventListener("click", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
@@ -119,7 +119,8 @@ import { I18n } from "./i18n.js";
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
(document.getElementById("load-desc") as HTMLSpanElement).textContent = I18n.getTranslation("accountNotStart"); (document.getElementById("load-desc") as HTMLSpanElement).textContent =
I18n.getTranslation("accountNotStart");
thisUser = new Localuser(-1); thisUser = new Localuser(-1);
} }
@@ -132,7 +133,7 @@ import { I18n } from "./i18n.js";
} }
}, },
null, null,
()=>thisUser.isAdmin() () => thisUser.isAdmin(),
); );
menu.addbutton( menu.addbutton(
@@ -143,7 +144,7 @@ import { I18n } from "./i18n.js";
} }
}, },
null, null,
()=>thisUser.isAdmin() () => thisUser.isAdmin(),
); );
menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement); menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement);
@@ -155,9 +156,11 @@ import { I18n } from "./i18n.js";
thisUser.goToChannel(e.state[1], false); thisUser.goToChannel(e.state[1], false);
} }
//console.log(e.state,"state:3") //console.log(e.state,"state:3")
}) });
async function handleEnter(event: KeyboardEvent): Promise<void> { async function handleEnter(event: KeyboardEvent): Promise<void> {
if(thisUser.keyup(event)){return} if (thisUser.keyup(event)) {
return;
}
const channel = thisUser.channelfocus; const channel = thisUser.channelfocus;
if (!channel) return; if (!channel) return;
if (markdown.rawString === "" && event.key === "ArrowUp") { if (markdown.rawString === "" && event.key === "ArrowUp") {
@@ -193,14 +196,16 @@ import { I18n } from "./i18n.js";
} }
} }
interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;} interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown;
}
const typebox = document.getElementById("typebox") as CustomHTMLDivElement; const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
typebox.markdown = markdown; typebox.markdown = markdown;
typebox.addEventListener("keyup", handleEnter); typebox.addEventListener("keyup", handleEnter);
typebox.addEventListener("keydown", event=>{ typebox.addEventListener("keydown", (event) => {
thisUser.keydown(event) thisUser.keydown(event);
if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
}); });
markdown.giveBox(typebox); markdown.giveBox(typebox);
@@ -209,12 +214,11 @@ import { I18n } from "./i18n.js";
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
searchBox.markdown = markdown; searchBox.markdown = markdown;
searchBox.addEventListener("keydown", event=>{ searchBox.addEventListener("keydown", (event) => {
if (event.key === "Enter") { if (event.key === "Enter") {
event.preventDefault(); event.preventDefault();
thisUser.mSearch(markdown.rawString) thisUser.mSearch(markdown.rawString);
}; }
}); });
markdown.giveBox(searchBox); markdown.giveBox(searchBox);
@@ -223,8 +227,6 @@ import { I18n } from "./i18n.js";
span.textContent = e.replace("\n", ""); span.textContent = e.replace("\n", "");
return span; return span;
}); });
} }
const images: Blob[] = []; const images: Blob[] = [];
const imagesHtml: HTMLElement[] = []; const imagesHtml: HTMLElement[] = [];
@@ -248,8 +250,7 @@ import { I18n } from "./i18n.js";
thisUser.showusersettings(); thisUser.showusersettings();
} }
(document.getElementById("settings") as HTMLImageElement).onclick = (document.getElementById("settings") as HTMLImageElement).onclick = userSettings;
userSettings;
if (mobile) { if (mobile) {
const channelWrapper = document.getElementById("channelw") as HTMLDivElement; const channelWrapper = document.getElementById("channelw") as HTMLDivElement;
@@ -260,7 +261,7 @@ import { I18n } from "./i18n.js";
const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement; const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement;
memberListToggle.checked = false; memberListToggle.checked = false;
} }
let dragendtimeout=setTimeout(()=>{}) let dragendtimeout = setTimeout(() => {});
document.addEventListener("dragover", (e) => { document.addEventListener("dragover", (e) => {
clearTimeout(dragendtimeout); clearTimeout(dragendtimeout);
const data = e.dataTransfer; const data = e.dataTransfer;
@@ -283,12 +284,12 @@ import { I18n } from "./i18n.js";
dragendtimeout = setTimeout(() => { dragendtimeout = setTimeout(() => {
const bg = document.getElementById("gimmefile") as HTMLDivElement; const bg = document.getElementById("gimmefile") as HTMLDivElement;
bg.hidden = true; bg.hidden = true;
},1000) }, 1000);
}); });
document.addEventListener("dragenter", (e) => { document.addEventListener("dragenter", (e) => {
e.preventDefault(); e.preventDefault();
}) });
document.addEventListener("drop",e=>{ document.addEventListener("drop", (e) => {
const data = e.dataTransfer; const data = e.dataTransfer;
const bg = document.getElementById("gimmefile") as HTMLDivElement; const bg = document.getElementById("gimmefile") as HTMLDivElement;
bg.hidden = true; bg.hidden = true;
@@ -311,8 +312,8 @@ import { I18n } from "./i18n.js";
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.click(); input.click();
console.log("clicked") console.log("clicked");
input.onchange=(() => { input.onchange = () => {
if (input.files) { if (input.files) {
for (const file of Array.from(input.files)) { for (const file of Array.from(input.files)) {
const fileInstance = File.initFromBlob(file); const fileInstance = File.initFromBlob(file);
@@ -322,7 +323,6 @@ import { I18n } from "./i18n.js";
imagesHtml.push(html); imagesHtml.push(html);
} }
} }
}) };
} };
})(); })();

View File

@@ -1,8 +1,5 @@
class InfiniteScroller { class InfiniteScroller {
readonly getIDFromOffset: ( readonly getIDFromOffset: (ID: string, offset: number) => Promise<string | undefined>;
ID: string,
offset: number
) => Promise<string | undefined>;
readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>; readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>;
readonly destroyFromID: (ID: string) => Promise<boolean>; readonly destroyFromID: (ID: string) => Promise<boolean>;
readonly reachesBottom: () => void; readonly reachesBottom: () => void;
@@ -42,7 +39,7 @@ offset: number
getIDFromOffset: InfiniteScroller["getIDFromOffset"], getIDFromOffset: InfiniteScroller["getIDFromOffset"],
getHTMLFromID: InfiniteScroller["getHTMLFromID"], getHTMLFromID: InfiniteScroller["getHTMLFromID"],
destroyFromID: InfiniteScroller["destroyFromID"], destroyFromID: InfiniteScroller["destroyFromID"],
reachesBottom: InfiniteScroller["reachesBottom"] = ()=>{} reachesBottom: InfiniteScroller["reachesBottom"] = () => {},
) { ) {
this.getIDFromOffset = getIDFromOffset; this.getIDFromOffset = getIDFromOffset;
this.getHTMLFromID = getHTMLFromID; this.getHTMLFromID = getHTMLFromID;
@@ -107,8 +104,7 @@ offset: number
this.timeout = null; this.timeout = null;
if (!this.div) return; if (!this.div) return;
this.scrollBottom = this.scrollBottom = this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight;
this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight;
this.averageheight = this.div.scrollHeight / this.HTMLElements.length; this.averageheight = this.div.scrollHeight / this.HTMLElements.length;
if (this.averageheight < 10) { if (this.averageheight < 10) {
this.averageheight = 60; this.averageheight = 60;
@@ -147,10 +143,7 @@ offset: number
}; };
} }
private async watchForTop( private async watchForTop(already = false, fragment = new DocumentFragment()): Promise<boolean> {
already = false,
fragment = new DocumentFragment()
): Promise<boolean>{
if (!this.div) return false; if (!this.div) return false;
try { try {
let again = false; let again = false;
@@ -173,7 +166,6 @@ offset: number
fragment.prepend(html); fragment.prepend(html);
this.HTMLElements.unshift([html, nextid]); this.HTMLElements.unshift([html, nextid]);
this.scrollTop += this.averageheight; this.scrollTop += this.averageheight;
} }
} }
if (this.scrollTop > this.maxDist) { if (this.scrollTop > this.maxDist) {
@@ -183,7 +175,6 @@ offset: number
await this.destroyFromID(html[1]); await this.destroyFromID(html[1]);
this.scrollTop -= this.averageheight; this.scrollTop -= this.averageheight;
} }
} }
if (again) { if (again) {
@@ -201,10 +192,7 @@ offset: number
} }
} }
async watchForBottom( async watchForBottom(already = false, fragment = new DocumentFragment()): Promise<boolean> {
already = false,
fragment = new DocumentFragment()
): Promise<boolean>{
let func: Function | undefined; let func: Function | undefined;
if (!already) func = this.snapBottom(); if (!already) func = this.snapBottom();
if (!this.div) return false; if (!this.div) return false;
@@ -256,15 +244,14 @@ offset: number
this.watchtime = false; this.watchtime = false;
} }
this.changePromise = new Promise<boolean>(async res=>{ this.changePromise = new Promise<boolean>(async (res) => {
try { try {
if (!this.div) { if (!this.div) {
res(false); res(false);
} }
const out = (await Promise.allSettled([ const out = (await Promise.allSettled([this.watchForTop(), this.watchForBottom()])) as {
this.watchForTop(), value: boolean;
this.watchForBottom(), }[];
])) as { value: boolean }[];
const changed = out[0].value || out[1].value; const changed = out[0].value || out[1].value;
if (this.timeout === null && changed) { if (this.timeout === null && changed) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
@@ -298,11 +285,11 @@ offset: number
behavior: "smooth", behavior: "smooth",
block: "center", block: "center",
}); });
await new Promise(resolve=>{ await new Promise((resolve) => {
setTimeout(resolve, 1000); setTimeout(resolve, 1000);
}); });
element.classList.remove("jumped"); element.classList.remove("jumped");
await new Promise(resolve=>{ await new Promise((resolve) => {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
element.classList.add("jumped"); element.classList.add("jumped");
@@ -319,7 +306,7 @@ offset: number
await this.firstElement(id); await this.firstElement(id);
this.updatestuff(); this.updatestuff();
await this.watchForChange(); await this.watchForChange();
await new Promise(resolve=>{ await new Promise((resolve) => {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
await this.focus(id, true); await this.focus(id, true);

View File

@@ -1,17 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Invite" property="og:title"> <meta content="Invite" property="og:title" />
<meta content="Accept this invite for a spacebar guild" property="og:description"> <meta content="Accept this invite for a spacebar guild" property="og:description" />
<meta name="description" content="You shouldn't see this, but this is an invite URL"> <meta name="description" content="You shouldn't see this, but this is an invite URL" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<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> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div> <div>

View File

@@ -33,16 +33,15 @@ import { getBulkUsers, Specialuser } from "./utils/utils.js";
} }
} }
} else { } else {
throw new Error( throw new Error("Someone needs to handle the case where the servers don't exist");
"Someone needs to handle the case where the servers don't exist"
);
} }
} else { } else {
urls = joinable[0].serverurls; urls = joinable[0].serverurls;
} }
await I18n.done; await I18n.done;
if (!joinable.length) { if (!joinable.length) {
document.getElementById("AcceptInvite")!.textContent = I18n.getTranslation("htmlPages.noAccount"); document.getElementById("AcceptInvite")!.textContent =
I18n.getTranslation("htmlPages.noAccount");
} }
const code = window.location.pathname.split("/")[2]; const code = window.location.pathname.split("/")[2];
@@ -51,14 +50,16 @@ import { getBulkUsers, Specialuser } from "./utils/utils.js";
fetch(`${urls!.api}/invites/${code}`, { fetch(`${urls!.api}/invites/${code}`, {
method: "GET", method: "GET",
}) })
.then(response=>response.json()) .then((response) => response.json())
.then(json=>{ .then((json) => {
const guildjson = json.guild; const guildjson = json.guild;
guildinfo = guildjson; guildinfo = guildjson;
document.getElementById("invitename")!.textContent = guildjson.name; document.getElementById("invitename")!.textContent = guildjson.name;
document.getElementById( document.getElementById("invitedescription")!.textContent = I18n.getTranslation(
"invitedescription" "invite.longInvitedBy",
)!.textContent = I18n.getTranslation("invite.longInvitedBy",json.inviter.username,guildjson.name) json.inviter.username,
guildjson.name,
);
if (guildjson.icon) { if (guildjson.icon) {
const img = document.createElement("img"); const img = document.createElement("img");
img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`;
@@ -95,9 +96,7 @@ document.getElementById("inviteimg")!.append(div);
userDiv.append(document.createElement("br")); userDiv.append(document.createElement("br"));
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = user.serverurls.wellknown span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
.replace("https://", "")
.replace("http://", "");
span.classList.add("serverURL"); span.classList.add("serverURL");
userDiv.append(span); userDiv.append(span);
@@ -142,7 +141,5 @@ document.getElementById("inviteimg")!.append(div);
document.body.append(table); document.body.append(table);
} }
document document.getElementById("AcceptInvite")!.addEventListener("click", showAccounts);
.getElementById("AcceptInvite")!
.addEventListener("click", showAccounts);
})(); })();

View File

@@ -63,7 +63,12 @@ type readyjson = {
}; };
user_guild_settings: { user_guild_settings: {
entries: { entries: {
channel_overrides: {message_notifications: number,muted: boolean,mute_config: {selected_time_window: number,end_time: number},channel_id: string}[]; channel_overrides: {
message_notifications: number;
muted: boolean;
mute_config: {selected_time_window: number; end_time: number};
channel_id: string;
}[];
message_notifications: number; message_notifications: number;
flags: number; flags: number;
hide_muted_channels: boolean; hide_muted_channels: boolean;
@@ -154,7 +159,7 @@ type memberjson = {
guild: { guild: {
id: string; id: string;
} | null; } | null;
presence?:presencejson presence?: presencejson;
nick?: string; nick?: string;
roles: string[]; roles: string[];
joined_at: string; joined_at: string;
@@ -171,16 +176,15 @@ type emojijson = {
emoji?: string; emoji?: string;
}; };
type emojipjson = emojijson & { type emojipjson = emojijson & {
available: boolean, available: boolean;
guild_id:string, guild_id: string;
user_id:string, user_id: string;
managed:boolean, managed: boolean;
require_colons:boolean, require_colons: boolean;
roles:string[], roles: string[];
groups:null//TODO figure out what this means lol groups: null; //TODO figure out what this means lol
}; };
type guildjson = { type guildjson = {
application_command_counts: {[key: string]: number}; application_command_counts: {[key: string]: number};
channels: channeljson[]; channels: channeljson[];
@@ -405,16 +409,17 @@ type messageCreateJson = {
t: "MESSAGE_CREATE"; t: "MESSAGE_CREATE";
}; };
type roleCreate = { type roleCreate = {
op: 0, op: 0;
t: "GUILD_ROLE_CREATE", t: "GUILD_ROLE_CREATE";
d: { d: {
guild_id: string, guild_id: string;
role: rolesjson role: rolesjson;
}, };
s: 6 s: 6;
} };
type wsjson = type wsjson =
roleCreate | { | roleCreate
| {
op: 0; op: 0;
d: any; d: any;
s: number; s: number;
@@ -492,77 +497,89 @@ roleCreate | {
emoji: emojijson; emoji: emojijson;
}; };
s: number; s: number;
}|{ }
op: 0, | {
t: "GUILD_ROLE_UPDATE", op: 0;
t: "GUILD_ROLE_UPDATE";
d: { d: {
guild_id: string, guild_id: string;
role: rolesjson role: rolesjson;
}, };
"s": number s: number;
}|{ }
op: 0, | {
t: "GUILD_ROLE_DELETE", op: 0;
d: { t: "GUILD_ROLE_DELETE";
guild_id: string, d: {
role_id: string guild_id: string;
}, role_id: string;
s:number };
}|{ s: number;
op: 0, }
t: "GUILD_MEMBER_UPDATE", | {
d: memberjson, op: 0;
s: 3 t: "GUILD_MEMBER_UPDATE";
}|{ d: memberjson;
op:9, s: 3;
d:boolean, }
s:number | {
}|memberlistupdatejson|voiceupdate|voiceserverupdate|{ op: 9;
op: 0, d: boolean;
t: "RELATIONSHIP_ADD", s: number;
d: { }
id: string, | memberlistupdatejson
type: 0|1|2|3|4|5|6, | voiceupdate
user: userjson | voiceserverupdate
}, | {
s: number op: 0;
}|{ t: "RELATIONSHIP_ADD";
op: 0, d: {
t: "RELATIONSHIP_REMOVE", id: string;
d: { type: 0 | 1 | 2 | 3 | 4 | 5 | 6;
id: string, user: userjson;
type: number, };
nickname: null s: number;
}, }
s: number | {
}|{ op: 0;
op: 0, t: "RELATIONSHIP_REMOVE";
t: "PRESENCE_UPDATE", d: {
d: presencejson, id: string;
s:number type: number;
}|{ nickname: null;
op:0, };
t:"GUILD_MEMBER_ADD", s: number;
d:memberjson, }
s:number | {
}|{ op: 0;
op:0, t: "PRESENCE_UPDATE";
t:"GUILD_MEMBER_REMOVE", d: presencejson;
d:{ s: number;
guild_id:string, }
user:userjson | {
}, op: 0;
s:number t: "GUILD_MEMBER_ADD";
}|{ d: memberjson;
op: 0, s: number;
t: "GUILD_EMOJIS_UPDATE", }
d: { | {
guild_id: string, op: 0;
emojis: emojipjson[] t: "GUILD_MEMBER_REMOVE";
}, d: {
s: number guild_id: string;
user: userjson;
};
s: number;
}
| {
op: 0;
t: "GUILD_EMOJIS_UPDATE";
d: {
guild_id: string;
emojis: emojipjson[];
};
s: number;
}; };
type memberChunk = { type memberChunk = {
guild_id: string; guild_id: string;
@@ -574,132 +591,138 @@ type memberChunk = {
not_found: string[]; not_found: string[];
}; };
type voiceupdate = { type voiceupdate = {
op: 0, op: 0;
t: "VOICE_STATE_UPDATE", t: "VOICE_STATE_UPDATE";
d: { d: {
guild_id: string, guild_id: string;
channel_id: string, channel_id: string;
user_id: string, user_id: string;
member: memberjson, member: memberjson;
session_id: string, session_id: string;
token: string, token: string;
deaf: boolean, deaf: boolean;
mute: boolean, mute: boolean;
self_deaf: boolean, self_deaf: boolean;
self_mute: boolean, self_mute: boolean;
self_video: boolean, self_video: boolean;
suppress: boolean suppress: boolean;
}, };
s: number s: number;
}; };
type voiceserverupdate = { type voiceserverupdate = {
op: 0, op: 0;
t: "VOICE_SERVER_UPDATE", t: "VOICE_SERVER_UPDATE";
d: { d: {
token: string, token: string;
guild_id: string, guild_id: string;
endpoint: string endpoint: string;
}, };
s: 6 s: 6;
}; };
type memberlistupdatejson = { type memberlistupdatejson = {
op: 0, op: 0;
s: number, s: number;
t: "GUILD_MEMBER_LIST_UPDATE", t: "GUILD_MEMBER_LIST_UPDATE";
d: { d: {
ops: [ ops: [
{ {
items:({ items: (
| {
group: { group: {
count:number, count: number;
id:string id: string;
};
} }
}|{ | {
member:memberjson member: memberjson;
})[]
op: "SYNC",
range: [
number,
number
]
} }
], )[];
online_count: number, op: "SYNC";
member_count: number, range: [number, number];
id: string, },
guild_id: string, ];
online_count: number;
member_count: number;
id: string;
guild_id: string;
groups: { groups: {
count: number, count: number;
id: string id: string;
}[] }[];
} };
} };
type webRTCSocket= { type webRTCSocket =
op: 8, | {
op: 8;
d: { d: {
heartbeat_interval: number heartbeat_interval: number;
};
} }
}|{ | {
op:6, op: 6;
d:{t: number} d: {t: number};
}|{ }
op: 2, | {
op: 2;
d: { d: {
ssrc: number, ssrc: number;
"streams": { streams: {
type: "video",//probally more options, but idk type: "video"; //probally more options, but idk
rid: string, rid: string;
quality: number, quality: number;
ssrc: number, ssrc: number;
rtx_ssrc:number rtx_ssrc: number;
}[], }[];
ip: number, ip: number;
port: number, port: number;
"modes": [],//no clue modes: []; //no clue
"experiments": []//no clue experiments: []; //no clue
};
} }
}|sdpback|opRTC12|{ | sdpback
op: 5, | opRTC12
| {
op: 5;
d: { d: {
user_id: string, user_id: string;
speaking: 0, speaking: 0;
ssrc: 940464811 ssrc: 940464811;
} };
}; };
type sdpback = { type sdpback = {
op: 4, op: 4;
d: { d: {
audioCodec: string, audioCodec: string;
videoCodec: string, videoCodec: string;
media_session_id: string, media_session_id: string;
sdp: string sdp: string;
} };
}; };
type opRTC12 = { type opRTC12 = {
op: 12, op: 12;
d: { d: {
user_id: string, user_id: string;
audio_ssrc: number, audio_ssrc: number;
video_ssrc: number, video_ssrc: number;
streams: [ streams: [
{ {
type: "video", type: "video";
rid: "100", rid: "100";
ssrc: number, ssrc: number;
active: boolean, active: boolean;
quality: 100, quality: 100;
rtx_ssrc: number, rtx_ssrc: number;
max_bitrate: 2500000, max_bitrate: 2500000;
max_framerate: number, max_framerate: number;
max_resolution: { max_resolution: {
type: "fixed", type: "fixed";
width: number, width: number;
height: number height: number;
} };
} },
] ];
} };
} };
export { export {
readyjson, readyjson,
dirrectjson, dirrectjson,
@@ -725,5 +748,5 @@ export{
webRTCSocket, webRTCSocket,
sdpback, sdpback,
opRTC12, opRTC12,
emojipjson emojipjson,
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,25 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
content="A spacebar client that has DMs, replying and more" <meta content="/logo.webp" property="og:image" />
property="og:description" <meta content="#4b458c" data-react-helmet="true" name="theme-color" />
> <link href="/style.css" rel="stylesheet" />
<meta content="/logo.webp" property="og:image"> <link href="/themes.css" rel="stylesheet" id="lightcss" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color"> <style>
<link href="/style.css" rel="stylesheet"> body.no-theme {
<link href="/themes.css" rel="stylesheet" id="lightcss"> background: #16191b;
<style>body.no-theme{background:#16191b;}@media(prefers-color-scheme:light){body.no-theme{background:#9397bd;}}</style> }
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div id="logindiv"> <div id="logindiv">
@@ -29,25 +35,13 @@
id="instancein" id="instancein"
value="" value=""
required required
> />
<label for="uname" id="emailField"><b>Email:</b></label> <label for="uname" id="emailField"><b>Email:</b></label>
<input <input type="text" placeholder="Enter email address" name="uname" id="uname" required />
type="text"
placeholder="Enter email address"
name="uname"
id="uname"
required
>
<label for="psw" id="pwField"><b>Password:</b></label> <label for="psw" id="pwField"><b>Password:</b></label>
<input <input type="password" placeholder="Enter Password" name="psw" id="psw" required />
type="password"
placeholder="Enter Password"
name="psw"
id="psw"
required
>
<p class="wrongred" id="wrong"></p> <p class="wrongred" id="wrong"></p>
<div id="h-captcha"></div> <div id="h-captcha"></div>

View File

@@ -3,19 +3,15 @@ import { I18n } from "./i18n.js";
import {Dialog, FormError} from "./settings.js"; import {Dialog, FormError} from "./settings.js";
import {checkInstance} from "./utils/utils.js"; import {checkInstance} from "./utils/utils.js";
await I18n.done; await I18n.done;
(async () => { (async () => {
await I18n.done await I18n.done;
const instanceField = document.getElementById("instanceField"); const instanceField = document.getElementById("instanceField");
const emailField = document.getElementById("emailField"); const emailField = document.getElementById("emailField");
const pwField = document.getElementById("pwField"); const pwField = document.getElementById("pwField");
const loginButton = document.getElementById("loginButton"); const loginButton = document.getElementById("loginButton");
const noAccount=document.getElementById("switch") const noAccount = document.getElementById("switch");
if (instanceField && emailField && pwField && loginButton && noAccount) { if (instanceField && emailField && pwField && loginButton && noAccount) {
instanceField.textContent = I18n.getTranslation("htmlPages.instanceField"); instanceField.textContent = I18n.getTranslation("htmlPages.instanceField");
emailField.textContent = I18n.getTranslation("htmlPages.emailField"); emailField.textContent = I18n.getTranslation("htmlPages.emailField");
@@ -23,8 +19,7 @@ await I18n.done;
loginButton.textContent = I18n.getTranslation("htmlPages.loginButton"); loginButton.textContent = I18n.getTranslation("htmlPages.loginButton");
noAccount.textContent = I18n.getTranslation("htmlPages.noAccount"); noAccount.textContent = I18n.getTranslation("htmlPages.noAccount");
} }
})() })();
function trimswitcher() { function trimswitcher() {
const json = getBulkInfo(); const json = getBulkInfo();
@@ -59,8 +54,6 @@ function trimswitcher(){
console.log(json); console.log(json);
} }
function adduser(user: typeof Specialuser.prototype.json) { function adduser(user: typeof Specialuser.prototype.json) {
user = new Specialuser(user); user = new Specialuser(user);
const info = getBulkInfo(); const info = getBulkInfo();
@@ -73,8 +66,6 @@ const instancein = document.getElementById("instancein") as HTMLInputElement;
let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null; let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null;
// let instanceinfo; // let instanceinfo;
if (instancein) { if (instancein) {
console.log(instancein); console.log(instancein);
instancein.addEventListener("keydown", () => { instancein.addEventListener("keydown", () => {
@@ -117,8 +108,8 @@ async function login(username: string, password: string, captcha: string){
const info = JSON.parse(localStorage.getItem("instanceinfo")!); const info = JSON.parse(localStorage.getItem("instanceinfo")!);
const api = info.login + (info.login.startsWith("/") ? "/" : ""); const api = info.login + (info.login.startsWith("/") ? "/" : "");
return await fetch(api + "/auth/login", options) return await fetch(api + "/auth/login", options)
.then(response=>response.json()) .then((response) => response.json())
.then(response=>{ .then((response) => {
console.log(response, response.message); console.log(response, response.message);
if (response.message === "Invalid Form Body") { if (response.message === "Invalid Form Body") {
return response.errors.login._errors[0].message; return response.errors.login._errors[0].message;
@@ -145,7 +136,9 @@ async function login(username: string, password: string, captcha: string){
console.log(response); console.log(response);
if (response.ticket) { if (response.ticket) {
const better = new Dialog(""); const better = new Dialog("");
const form=better.options.addForm("",(res:any)=>{ const form = better.options.addForm(
"",
(res: any) => {
if (res.message) { if (res.message) {
throw new FormError(ti, res.message); throw new FormError(ti, res.message);
} else { } else {
@@ -156,25 +149,25 @@ async function login(username: string, password: string, captcha: string){
email: username, email: username,
token: res.token, token: res.token,
}).username = username; }).username = username;
const redir = new URLSearchParams( const redir = new URLSearchParams(window.location.search).get("goback");
window.location.search
).get("goback");
if (redir) { if (redir) {
window.location.href = redir; window.location.href = redir;
} else { } else {
window.location.href = "/channels/@me"; window.location.href = "/channels/@me";
} }
} }
},{ },
{
fetchURL: api + "/auth/mfa/totp", fetchURL: api + "/auth/mfa/totp",
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
} },
}); },
);
form.addTitle(I18n.getTranslation("2faCode")); form.addTitle(I18n.getTranslation("2faCode"));
const ti = form.addTextInput("", "code"); const ti = form.addTextInput("", "code");
better.show() better.show();
} else { } else {
console.warn(response); console.warn(response);
if (!response.token) return; if (!response.token) return;
@@ -183,9 +176,7 @@ async function login(username: string, password: string, captcha: string){
email: username, email: username,
token: response.token, token: response.token,
}).username = username; }).username = username;
const redir = new URLSearchParams(window.location.search).get( const redir = new URLSearchParams(window.location.search).get("goback");
"goback"
);
if (redir) { if (redir) {
window.location.href = redir; window.location.href = redir;
} else { } else {
@@ -206,7 +197,7 @@ async function check(e: SubmitEvent){
const h = await login( const h = await login(
(target[1] as HTMLInputElement).value, (target[1] as HTMLInputElement).value,
(target[2] as HTMLInputElement).value, (target[2] as HTMLInputElement).value,
(target[3] as HTMLInputElement).value (target[3] as HTMLInputElement).value,
); );
const wrongElement = document.getElementById("wrong"); const wrongElement = document.getElementById("wrong");
if (wrongElement) { if (wrongElement) {
@@ -237,11 +228,4 @@ if(switchurl){
} }
trimswitcher(); trimswitcher();
export{ export {adduser};
adduser,
};

View File

@@ -15,7 +15,7 @@ class MarkDown{
constructor( constructor(
text: string | string[], text: string | string[],
owner: MarkDown["owner"], owner: MarkDown["owner"],
{ keep = false, stdsize = false } = {} {keep = false, stdsize = false} = {},
) { ) {
if (typeof text === typeof "") { if (typeof text === typeof "") {
this.txt = (text as string).split(""); this.txt = (text as string).split("");
@@ -300,7 +300,8 @@ class MarkDown{
} }
} }
if ( if (
find === count &&(count != 1 ||txt[j + 1] === " " ||txt[j + 1] === "\n" ||txt[j + 1] === undefined) find === count &&
(count != 1 || txt[j + 1] === " " || txt[j + 1] === "\n" || txt[j + 1] === undefined)
) { ) {
appendcurrent(); appendcurrent();
i = j; i = j;
@@ -442,10 +443,10 @@ class MarkDown{
continue; continue;
} }
} }
if((txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#"))&&this.localuser){ if (txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#") && this.localuser) {
let id = ""; let id = "";
let j = i + 2; let j = i + 2;
const numbers = new Set(["0","1","2","3","4","5","6","7","8","9",]); const numbers = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
for (; txt[j] !== undefined; j++) { for (; txt[j] !== undefined; j++) {
const char = txt[j]; const char = txt[j];
if (!numbers.has(char)) { if (!numbers.has(char)) {
@@ -474,7 +475,7 @@ class MarkDown{
user.bind(mention, guild); user.bind(mention, guild);
} }
if (guild) { if (guild) {
Member.resolveMember(user, guild).then(member=>{ Member.resolveMember(user, guild).then((member) => {
if (member) { if (member) {
mention.textContent = `@${member.name}`; mention.textContent = `@${member.name}`;
} }
@@ -489,7 +490,7 @@ class MarkDown{
if (channel) { if (channel) {
mention.textContent = `#${channel.name}`; mention.textContent = `#${channel.name}`;
if (!keep) { if (!keep) {
mention.onclick = _=>{ mention.onclick = (_) => {
if (!this.localuser) return; if (!this.localuser) return;
this.localuser.goToChannel(id); this.localuser.goToChannel(id);
}; };
@@ -576,7 +577,9 @@ class MarkDown{
second: "2-digit", second: "2-digit",
}); });
else if (parts[3] === "R") else if (parts[3] === "R")
time =Math.round((Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60) + " minutes ago"; time =
Math.round((Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60) +
" minutes ago";
} }
const timeElem = document.createElement("span"); const timeElem = document.createElement("span");
@@ -587,7 +590,10 @@ class MarkDown{
} }
} }
if(txt[i] === "<" && (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")&&this.owner)){ if (
txt[i] === "<" &&
(txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":" && this.owner))
) {
let found = false; let found = false;
const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"];
let j = i + build.length; let j = i + build.length;
@@ -611,7 +617,7 @@ class MarkDown{
if (!owner) continue; if (!owner) continue;
const emoji = new Emoji( const emoji = new Emoji(
{name: buildjoin, id: parts[2], animated: Boolean(parts[1])}, {name: buildjoin, id: parts[2], animated: Boolean(parts[1])},
owner owner,
); );
span.appendChild(emoji.getHTML(isEmojiOnly)); span.appendChild(emoji.getHTML(isEmojiOnly));
@@ -649,9 +655,7 @@ class MarkDown{
if (partsFound === 2) { if (partsFound === 2) {
appendcurrent(); appendcurrent();
const parts = build const parts = build.join("").match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/);
.join("")
.match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/);
if (parts) { if (parts) {
const linkElem = document.createElement("a"); const linkElem = document.createElement("a");
if (URL.canParse(parts[2])) { if (URL.canParse(parts[2])) {
@@ -661,9 +665,7 @@ class MarkDown{
linkElem.target = "_blank"; linkElem.target = "_blank";
linkElem.rel = "noopener noreferrer"; linkElem.rel = "noopener noreferrer";
linkElem.title = linkElem.title =
(parts[3] (parts[3] ? parts[3].substring(2, parts[3].length - 1) + "\n\n" : "") + parts[2];
? parts[3].substring(2, parts[3].length - 1) + "\n\n"
: "") + parts[2];
span.appendChild(linkElem); span.appendChild(linkElem);
continue; continue;
@@ -686,11 +688,11 @@ class MarkDown{
giveBox(box: HTMLDivElement, onUpdate: (upto: string, pre: boolean) => unknown = () => {}) { giveBox(box: HTMLDivElement, onUpdate: (upto: string, pre: boolean) => unknown = () => {}) {
this.box = new WeakRef(box); this.box = new WeakRef(box);
this.onUpdate = onUpdate; this.onUpdate = onUpdate;
box.onkeydown = _=>{ box.onkeydown = (_) => {
//console.log(_); //console.log(_);
}; };
let prevcontent = ""; let prevcontent = "";
box.onkeyup = _=>{ box.onkeyup = (_) => {
const content = MarkDown.gatherBoxText(box); const content = MarkDown.gatherBoxText(box);
if (content !== prevcontent) { if (content !== prevcontent) {
prevcontent = content; prevcontent = content;
@@ -698,9 +700,8 @@ class MarkDown{
this.boxupdate(); this.boxupdate();
MarkDown.gatherBoxText(box); MarkDown.gatherBoxText(box);
} }
}; };
box.onpaste = _=>{ box.onpaste = (_) => {
if (!_.clipboardData) return; if (!_.clipboardData) return;
console.log(_.clipboardData.types); console.log(_.clipboardData.types);
const data = _.clipboardData.getData("text"); const data = _.clipboardData.getData("text");
@@ -711,11 +712,14 @@ class MarkDown{
box.onkeyup(new KeyboardEvent("_")); box.onkeyup(new KeyboardEvent("_"));
}; };
} }
customBox?:[(arg1:string)=>HTMLElement,((arg1:HTMLElement)=>string)]; customBox?: [(arg1: string) => HTMLElement, (arg1: HTMLElement) => string];
clearCustom() { clearCustom() {
this.customBox = undefined; this.customBox = undefined;
} }
setCustomBox(stringToHTML:(arg1:string)=>HTMLElement,HTMLToString=MarkDown.gatherBoxText.bind(MarkDown)){ setCustomBox(
stringToHTML: (arg1: string) => HTMLElement,
HTMLToString = MarkDown.gatherBoxText.bind(MarkDown),
) {
this.customBox = [stringToHTML, HTMLToString]; this.customBox = [stringToHTML, HTMLToString];
} }
boxupdate(offset = 0) { boxupdate(offset = 0) {
@@ -725,7 +729,7 @@ class MarkDown{
if (this.customBox) { if (this.customBox) {
restore = saveCaretPosition(box, offset, this.customBox[1]); restore = saveCaretPosition(box, offset, this.customBox[1]);
} else { } else {
restore= saveCaretPosition(box,offset) restore = saveCaretPosition(box, offset);
} }
box.innerHTML = ""; box.innerHTML = "";
if (this.customBox) { if (this.customBox) {
@@ -784,7 +788,7 @@ class MarkDown{
elm.target = "_blank"; elm.target = "_blank";
return; return;
} }
elm.onmouseup = _=>{ elm.onmouseup = (_) => {
if (_.button === 2) return; if (_.button === 2) return;
console.log(":3"); console.log(":3");
function open() { function open() {
@@ -833,7 +837,11 @@ class MarkDown{
//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div
let text = ""; let text = "";
let formatted = false; let formatted = false;
function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.gatherBoxText.bind(MarkDown)){ function saveCaretPosition(
context: HTMLElement,
offset = 0,
txtLengthFunc = MarkDown.gatherBoxText.bind(MarkDown),
) {
const selection = window.getSelection() as Selection; const selection = window.getSelection() as Selection;
if (!selection) return; if (!selection) return;
try { try {
@@ -854,7 +862,7 @@ function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.
i++; i++;
} }
if (base instanceof HTMLElement) { if (base instanceof HTMLElement) {
baseString=txtLengthFunc(base) baseString = txtLengthFunc(base);
} else { } else {
baseString = base.textContent as string; baseString = base.textContent as string;
} }
@@ -862,7 +870,6 @@ function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.
baseString = selection.toString(); baseString = selection.toString();
} }
range.setStart(context, 0); range.setStart(context, 0);
let build = ""; let build = "";
@@ -885,7 +892,6 @@ function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.
return; return;
} }
for (const node of children as Node[]) { for (const node of children as Node[]) {
if (selection.containsNode(node, false)) { if (selection.containsNode(node, false)) {
if (node instanceof HTMLElement) { if (node instanceof HTMLElement) {
build += txtLengthFunc(node); build += txtLengthFunc(node);
@@ -896,7 +902,7 @@ function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.
if (node instanceof HTMLElement) { if (node instanceof HTMLElement) {
crawlForText(node); crawlForText(node);
} else { } else {
console.error(node,"This shouldn't happen") console.error(node, "This shouldn't happen");
} }
} else { } else {
//console.error(node,"This shouldn't happen"); //console.error(node,"This shouldn't happen");
@@ -909,7 +915,7 @@ function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.
} }
text = build; text = build;
let len = build.length + offset; let len = build.length + offset;
len=Math.min(len,txtLengthFunc(context).length) len = Math.min(len, txtLengthFunc(context).length);
return function restore() { return function restore() {
if (!selection) return; if (!selection) return;
const pos = getTextNodeAtPosition(context, len, txtLengthFunc); const pos = getTextNodeAtPosition(context, len, txtLengthFunc);
@@ -923,9 +929,13 @@ function saveCaretPosition(context: HTMLElement,offset=0,txtLengthFunc=MarkDown.
} }
} }
function getTextNodeAtPosition(root: Node, index: number,txtLengthFunc=MarkDown.gatherBoxText.bind(MarkDown)):{ function getTextNodeAtPosition(
node: Node, root: Node,
position: number, index: number,
txtLengthFunc = MarkDown.gatherBoxText.bind(MarkDown),
): {
node: Node;
position: number;
} { } {
if (root instanceof Text) { if (root instanceof Text) {
return { return {
@@ -946,11 +956,11 @@ function getTextNodeAtPosition(root: Node, index: number,txtLengthFunc=MarkDown.
let lastElm: Node = root; let lastElm: Node = root;
for (const node of root.childNodes as unknown as Node[]) { for (const node of root.childNodes as unknown as Node[]) {
lastElm = node; lastElm = node;
let len:number let len: number;
if (node instanceof HTMLElement) { if (node instanceof HTMLElement) {
len = txtLengthFunc(node).length; len = txtLengthFunc(node).length;
} else { } else {
len=(node.textContent as string).length len = (node.textContent as string).length;
} }
if (len <= index && (len < index || len !== 0)) { if (len <= index && (len < index || len !== 0)) {
index -= len; index -= len;
@@ -964,7 +974,7 @@ function getTextNodeAtPosition(root: Node, index: number,txtLengthFunc=MarkDown.
return returny; return returny;
} }
} }
if( !((lastElm instanceof HTMLElement && lastElm.hasAttribute("real")))){ if (!(lastElm instanceof HTMLElement && lastElm.hasAttribute("real"))) {
while (lastElm && !(lastElm instanceof Text || lastElm instanceof HTMLBRElement)) { while (lastElm && !(lastElm instanceof Text || lastElm instanceof HTMLBRElement)) {
lastElm = lastElm.childNodes[lastElm.childNodes.length - 1]; lastElm = lastElm.childNodes[lastElm.childNodes.length - 1];
} }
@@ -972,16 +982,15 @@ function getTextNodeAtPosition(root: Node, index: number,txtLengthFunc=MarkDown.
const position = (lastElm.textContent as string).length; const position = (lastElm.textContent as string).length;
return { return {
node: lastElm, node: lastElm,
position position,
}; };
} }
} }
const span = document.createElement("span"); const span = document.createElement("span");
root.appendChild(span) root.appendChild(span);
return { return {
node: span, node: span,
position: 0, position: 0,
}; };
} }
export {MarkDown, saveCaretPosition, getTextNodeAtPosition}; export {MarkDown, saveCaretPosition, getTextNodeAtPosition};

View File

@@ -48,8 +48,8 @@ class Member extends SnowFlake{
} }
if (!this.user.bot) { if (!this.user.bot) {
const everyone = this.guild.roleids.get(this.guild.id); const everyone = this.guild.roleids.get(this.guild.id);
if(everyone&&(this.roles.indexOf(everyone)===-1)){ if (everyone && this.roles.indexOf(everyone) === -1) {
this.roles.push(everyone) this.roles.push(everyone);
} }
} }
this.roles.sort((a, b) => { this.roles.sort((a, b) => {
@@ -78,7 +78,7 @@ class Member extends SnowFlake{
if (this.banner) { if (this.banner) {
return `${this.info.cdn}/banners/${this.guild.id}/${ return `${this.info.cdn}/banners/${this.guild.id}/${
this.banner this.banner
}.${this.banner.startsWith("a_")?"gif":"png"}`;; }.${this.banner.startsWith("a_") ? "gif" : "png"}`;
} else { } else {
return undefined; return undefined;
} }
@@ -87,25 +87,27 @@ class Member extends SnowFlake{
premium_since!: string; premium_since!: string;
deaf!: boolean; deaf!: boolean;
mute!: boolean; mute!: boolean;
pending!:boolean pending!: boolean;
clone() { clone() {
return new Member({ return new Member(
{
id: this.id + "#clone", id: this.id + "#clone",
user: this.user.tojson(), user: this.user.tojson(),
guild_id: this.guild.id, guild_id: this.guild.id,
guild: {id: this.guild.id}, guild: {id: this.guild.id},
avatar:this.avatar as (string|undefined), avatar: this.avatar as string | undefined,
banner:this.banner as (string|undefined), banner: this.banner as string | undefined,
//TODO presence //TODO presence
nick: this.nick, nick: this.nick,
roles:this.roles.map(_=>_.id), roles: this.roles.map((_) => _.id),
joined_at: this.joined_at, joined_at: this.joined_at,
premium_since: this.premium_since, premium_since: this.premium_since,
deaf: this.deaf, deaf: this.deaf,
mute: this.mute, mute: this.mute,
pending:this.pending pending: this.pending,
},
},this.owner) this.owner,
);
} }
pronouns?: string; pronouns?: string;
bio?: string; bio?: string;
@@ -153,11 +155,7 @@ class Member extends SnowFlake{
} }
} }
updateProfile(json: { updateProfile(json: {bio?: string | null; pronouns?: string | null; nick?: string | null}) {
bio?: string|null;
pronouns?: string|null;
nick?:string|null;
}){
console.log(JSON.stringify(json)); console.log(JSON.stringify(json));
/* /*
if(json.bio===""){ if(json.bio===""){
@@ -178,7 +176,9 @@ class Member extends SnowFlake{
} }
showEditProfile() { showEditProfile() {
const settings = new Settings(""); const settings = new Settings("");
this.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true})); this.editProfile(
settings.addButton(I18n.getTranslation("user.editServerProfile"), {ltr: true}),
);
settings.show(); settings.show();
} }
editProfile(options: Options) { editProfile(options: Options) {
@@ -203,24 +203,24 @@ class Member extends SnowFlake{
settingsRight.addHTMLArea(hypotheticalProfile); settingsRight.addHTMLArea(hypotheticalProfile);
const nicky = settingsLeft.addTextInput(I18n.getTranslation("member.nick:"), () => {}, { const nicky = settingsLeft.addTextInput(I18n.getTranslation("member.nick:"), () => {}, {
initText:this.nick||"" initText: this.nick || "",
}); });
nicky.watchForChange(_=>{ nicky.watchForChange((_) => {
hypomember.nick = _; hypomember.nick = _;
nick = _; nick = _;
regen(); regen();
}) });
const finput = settingsLeft.addFileInput( const finput = settingsLeft.addFileInput(
I18n.getTranslation("uploadPfp"), I18n.getTranslation("uploadPfp"),
_=>{ (_) => {
if (file) { if (file) {
this.updatepfp(file); this.updatepfp(file);
} }
}, },
{ clear: true } {clear: true},
); );
finput.watchForChange(_=>{ finput.watchForChange((_) => {
if (!_) { if (!_) {
file = null; file = null;
hypomember.avatar = undefined; hypomember.avatar = undefined;
@@ -239,14 +239,14 @@ class Member extends SnowFlake{
let bfile: undefined | File | null; let bfile: undefined | File | null;
const binput = settingsLeft.addFileInput( const binput = settingsLeft.addFileInput(
I18n.getTranslation("uploadBanner"), I18n.getTranslation("uploadBanner"),
_=>{ (_) => {
if (bfile !== undefined) { if (bfile !== undefined) {
this.updatebanner(bfile); this.updatebanner(bfile);
} }
}, },
{ clear: true } {clear: true},
); );
binput.watchForChange(_=>{ binput.watchForChange((_) => {
if (!_) { if (!_) {
bfile = null; bfile = null;
hypomember.banner = undefined; hypomember.banner = undefined;
@@ -265,27 +265,27 @@ class Member extends SnowFlake{
let changed = false; let changed = false;
const pronounbox = settingsLeft.addTextInput( const pronounbox = settingsLeft.addTextInput(
I18n.getTranslation("pronouns"), I18n.getTranslation("pronouns"),
_=>{ (_) => {
if (newpronouns !== undefined || newbio !== undefined || changed !== undefined) { if (newpronouns !== undefined || newbio !== undefined || changed !== undefined) {
this.updateProfile({ this.updateProfile({
pronouns: newpronouns, pronouns: newpronouns,
bio: newbio, bio: newbio,
//accent_color: Number.parseInt("0x" + color.substr(1), 16), //accent_color: Number.parseInt("0x" + color.substr(1), 16),
nick nick,
}); });
} }
}, },
{ initText: this.pronouns } {initText: this.pronouns},
); );
pronounbox.watchForChange(_=>{ pronounbox.watchForChange((_) => {
hypomember.pronouns = _; hypomember.pronouns = _;
newpronouns = _; newpronouns = _;
regen(); regen();
}); });
const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), _=>{}, { const bioBox = settingsLeft.addMDInput(I18n.getTranslation("bio"), (_) => {}, {
initText: this.bio, initText: this.bio,
}); });
bioBox.watchForChange(_=>{ bioBox.watchForChange((_) => {
newbio = _; newbio = _;
hypomember.bio = _; hypomember.bio = _;
regen(); regen();
@@ -298,10 +298,10 @@ class Member extends SnowFlake{
} }
const colorPicker = settingsLeft.addColorInput( const colorPicker = settingsLeft.addColorInput(
I18n.getTranslation("profileColor"), I18n.getTranslation("profileColor"),
_=>{}, (_) => {},
{ initColor: color } {initColor: color},
); );
colorPicker.watchForChange(_=>{ colorPicker.watchForChange((_) => {
console.log(); console.log();
color = _; color = _;
hypomember.accent_color = Number.parseInt("0x" + _.substr(1), 16); hypomember.accent_color = Number.parseInt("0x" + _.substr(1), 16);
@@ -327,8 +327,8 @@ class Member extends SnowFlake{
} }
if (!this.user.bot) { if (!this.user.bot) {
const everyone = this.guild.roleids.get(this.guild.id); const everyone = this.guild.roleids.get(this.guild.id);
if(everyone&&(this.roles.indexOf(everyone)===-1)){ if (everyone && this.roles.indexOf(everyone) === -1) {
this.roles.push(everyone) this.roles.push(everyone);
} }
} }
continue; continue;
@@ -353,10 +353,7 @@ class Member extends SnowFlake{
get info() { get info() {
return this.owner.info; return this.owner.info;
} }
static async new( static async new(memberjson: memberjson, owner: Guild): Promise<Member | undefined> {
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined>{
let user: User; let user: User;
if (owner.localuser.userMap.has(memberjson.id)) { if (owner.localuser.userMap.has(memberjson.id)) {
if (memberjson.user) { if (memberjson.user) {
@@ -398,7 +395,7 @@ class Member extends SnowFlake{
compare(str: string) { compare(str: string) {
function similar(str2: string | null | undefined) { function similar(str2: string | null | undefined) {
if (!str2) return 0; if (!str2) return 0;
const strl=Math.max(str.length,1) const strl = Math.max(str.length, 1);
if (str2.includes(str)) { if (str2.includes(str)) {
return strl / str2.length; return strl / str2.length;
} else if (str2.toLowerCase().includes(str.toLowerCase())) { } else if (str2.toLowerCase().includes(str.toLowerCase())) {
@@ -406,16 +403,19 @@ class Member extends SnowFlake{
} }
return 0; return 0;
} }
return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username),similar(this.id)/1.5); return Math.max(
similar(this.user.name),
similar(this.user.nickname),
similar(this.nick),
similar(this.user.username),
similar(this.id) / 1.5,
);
} }
static async resolveMember( static async resolveMember(user: User, guild: Guild): Promise<Member | undefined> {
user: User,
guild: Guild
): Promise<Member | undefined>{
const maybe = user.members.get(guild); const maybe = user.members.get(guild);
if (!user.members.has(guild)) { if (!user.members.has(guild)) {
const membpromise = guild.localuser.resolvemember(user.id, guild.id); const membpromise = guild.localuser.resolvemember(user.id, guild.id);
const promise = new Promise<Member | undefined>(async res=>{ const promise = new Promise<Member | undefined>(async (res) => {
const membjson = await membpromise; const membjson = await membpromise;
if (membjson === undefined) { if (membjson === undefined) {
return res(undefined); return res(undefined);
@@ -454,7 +454,7 @@ class Member extends SnowFlake{
this.id + this.id +
"/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" + "/profile?with_mutual_guilds=true&with_mutual_friends_count=true&guild_id=" +
this.guild.id, this.guild.id,
{ headers: this.guild.headers } {headers: this.guild.headers},
); );
} }
hasRole(ID: string) { hasRole(ID: string) {
@@ -505,10 +505,10 @@ class Member extends SnowFlake{
} }
kick() { kick() {
const menu = new Dialog(""); const menu = new Dialog("");
const form=menu.options.addForm("",((e:any)=>{ const form = menu.options.addForm("", (e: any) => {
this.kickAPI(e.reason); this.kickAPI(e.reason);
menu.hide(); menu.hide();
})); });
form.addTitle(I18n.getTranslation("member.kick", this.name, this.guild.properties.name)); form.addTitle(I18n.getTranslation("member.kick", this.name, this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"), "reason"); form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
menu.show(); menu.show();
@@ -523,31 +523,31 @@ class Member extends SnowFlake{
} }
ban() { ban() {
const menu = new Dialog(""); const menu = new Dialog("");
const form=menu.options.addForm("",((e:any)=>{ const form = menu.options.addForm("", (e: any) => {
this.banAPI(e.reason); this.banAPI(e.reason);
menu.hide(); menu.hide();
})); });
form.addTitle(I18n.getTranslation("member.ban", this.name, this.guild.properties.name)); form.addTitle(I18n.getTranslation("member.ban", this.name, this.guild.properties.name));
form.addTextInput(I18n.getTranslation("member.reason:"), "reason"); form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
menu.show(); menu.show();
} }
addRole(role: Role) { addRole(role: Role) {
const roles=this.roles.map(_=>_.id) const roles = this.roles.map((_) => _.id);
roles.push(role.id); roles.push(role.id);
fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, { fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
method: "PATCH", method: "PATCH",
headers: this.guild.headers, headers: this.guild.headers,
body:JSON.stringify({roles}) body: JSON.stringify({roles}),
}) });
} }
removeRole(role: Role) { removeRole(role: Role) {
let roles=this.roles.map(_=>_.id) let roles = this.roles.map((_) => _.id);
roles=roles.filter(_=>_!==role.id); roles = roles.filter((_) => _ !== role.id);
fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, { fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
method: "PATCH", method: "PATCH",
headers: this.guild.headers, headers: this.guild.headers,
body:JSON.stringify({roles}) body: JSON.stringify({roles}),
}) });
} }
banAPI(reason: string) { banAPI(reason: string) {
const headers = structuredClone(this.guild.headers); const headers = structuredClone(this.guild.headers);

View File

@@ -25,9 +25,9 @@ class Message extends SnowFlake{
mention_roles!: Role[]; mention_roles!: Role[];
attachments!: File[]; //probably should be its own class tbh, should be Attachments[] attachments!: File[]; //probably should be its own class tbh, should be Attachments[]
message_reference!: { message_reference!: {
guild_id: string, guild_id: string;
channel_id: string, channel_id: string;
message_id: string message_id: string;
}; };
type!: number; type!: number;
timestamp!: number; timestamp!: number;
@@ -51,28 +51,37 @@ class Message extends SnowFlake{
member: Member | undefined; member: Member | undefined;
reactions!: messagejson["reactions"]; reactions!: messagejson["reactions"];
static setup() { static setup() {
this.del = new Promise(_=>{ this.del = new Promise((_) => {
this.resolve = _; this.resolve = _;
}); });
Message.setupcmenu(); Message.setupcmenu();
} }
static setupcmenu() { static setupcmenu() {
Message.contextmenu.addbutton(()=>I18n.getTranslation("copyrawtext"), function(this: Message){ Message.contextmenu.addbutton(
() => I18n.getTranslation("copyrawtext"),
function (this: Message) {
navigator.clipboard.writeText(this.content.rawString); navigator.clipboard.writeText(this.content.rawString);
}); },
Message.contextmenu.addbutton(()=>I18n.getTranslation("reply"), function(this: Message){ );
Message.contextmenu.addbutton(
() => I18n.getTranslation("reply"),
function (this: Message) {
this.channel.setReplying(this); this.channel.setReplying(this);
}); },
Message.contextmenu.addbutton(()=>I18n.getTranslation("copymessageid"), function(this: Message){ );
Message.contextmenu.addbutton(
() => I18n.getTranslation("copymessageid"),
function (this: Message) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); },
);
Message.contextmenu.addsubmenu( Message.contextmenu.addsubmenu(
() => I18n.getTranslation("message.reactionAdd"), () => I18n.getTranslation("message.reactionAdd"),
function (this: Message, _, e: MouseEvent) { function (this: Message, _, e: MouseEvent) {
Emoji.emojiPicker(e.x, e.y, this.localuser).then(_=>{ Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => {
this.reactionToggle(_); this.reactionToggle(_);
}); });
} },
); );
Message.contextmenu.addbutton( Message.contextmenu.addbutton(
() => I18n.getTranslation("message.edit"), () => I18n.getTranslation("message.edit"),
@@ -82,7 +91,7 @@ class Message extends SnowFlake{
null, null,
function () { function () {
return this.author.id === this.localuser.user.id; return this.author.id === this.localuser.user.id;
} },
); );
Message.contextmenu.addbutton( Message.contextmenu.addbutton(
() => I18n.getTranslation("message.delete"), () => I18n.getTranslation("message.delete"),
@@ -92,14 +101,14 @@ class Message extends SnowFlake{
null, null,
function () { function () {
return this.canDelete(); return this.canDelete();
} },
); );
} }
setEdit() { setEdit() {
const prev = this.channel.editing; const prev = this.channel.editing;
this.channel.editing = this; this.channel.editing = this;
if (prev) prev.generateMessage(); if (prev) prev.generateMessage();
this.generateMessage(undefined,false) this.generateMessage(undefined, false);
} }
constructor(messagejson: messagejson, owner: Channel, dontStore = false) { constructor(messagejson: messagejson, owner: Channel, dontStore = false) {
super(messagejson.id); super(messagejson.id);
@@ -129,7 +138,7 @@ class Message extends SnowFlake{
{ {
method: remove ? "DELETE" : "PUT", method: remove ? "DELETE" : "PUT",
headers: this.headers, headers: this.headers,
} },
); );
} }
edited_timestamp: string | null = null; edited_timestamp: string | null = null;
@@ -148,7 +157,7 @@ class Message extends SnowFlake{
} else if (thing === "id") { } else if (thing === "id") {
continue; continue;
} else if (thing === "member") { } else if (thing === "member") {
Member.new(messagejson.member as memberjson, this.guild).then(_=>{ Member.new(messagejson.member as memberjson, this.guild).then((_) => {
this.member = _ as Member; this.member = _ as Member;
}); });
continue; continue;
@@ -167,13 +176,10 @@ class Message extends SnowFlake{
this.author = new User(messagejson.author, this.localuser); this.author = new User(messagejson.author, this.localuser);
for (const thing in messagejson.mentions) { for (const thing in messagejson.mentions) {
this.mentions[thing] = new User( this.mentions[thing] = new User(messagejson.mentions[thing], this.localuser);
messagejson.mentions[thing],
this.localuser
);
} }
if (!this.member && this.guild.id !== "@me") { if (!this.member && this.guild.id !== "@me") {
this.author.resolvemember(this.guild).then(_=>{ this.author.resolvemember(this.guild).then((_) => {
this.member = _; this.member = _;
}); });
} }
@@ -193,10 +199,7 @@ class Message extends SnowFlake{
} }
} }
canDelete() { canDelete() {
return( return this.channel.hasPermission("MANAGE_MESSAGES") || this.author === this.localuser.user;
this.channel.hasPermission("MANAGE_MESSAGES") ||
this.author === this.localuser.user
);
} }
get channel() { get channel() {
return this.owner; return this.owner;
@@ -212,26 +215,30 @@ class Message extends SnowFlake{
} }
messageevents(obj: HTMLDivElement) { messageevents(obj: HTMLDivElement) {
let drag = false; let drag = false;
Message.contextmenu.bindContextmenu(obj, this, undefined,(x)=>{ Message.contextmenu.bindContextmenu(
obj,
this,
undefined,
(x) => {
//console.log(x,y); //console.log(x,y);
if (!drag && x < 20) { if (!drag && x < 20) {
return return;
} }
drag = true; drag = true;
this.channel.moveForDrag(Math.max(x, 0)); this.channel.moveForDrag(Math.max(x, 0));
},
},(x,y)=>{ (x, y) => {
drag = false; drag = false;
console.log(x, y); console.log(x, y);
this.channel.moveForDrag(-1); this.channel.moveForDrag(-1);
if (x > 60) { if (x > 60) {
console.log("In here?") console.log("In here?");
const toggle = document.getElementById("maintoggle") as HTMLInputElement; const toggle = document.getElementById("maintoggle") as HTMLInputElement;
toggle.checked = false; toggle.checked = false;
console.log(toggle); console.log(toggle);
} }
},
},); );
this.div = obj; this.div = obj;
obj.classList.add("messagediv"); obj.classList.add("messagediv");
} }
@@ -249,7 +256,7 @@ class Message extends SnowFlake{
return this.mentions.includes(userd); return this.mentions.includes(userd);
} else if (userd instanceof Member) { } else if (userd instanceof Member) {
if (this.mentions.includes(userd.user)) { if (this.mentions.includes(userd.user)) {
return true return true;
} else { } else {
return !new Set(this.mentions).isDisjointFrom(new Set(userd.roles)); //if the message mentions a role the user has return !new Set(this.mentions).isDisjointFrom(new Set(userd.roles)); //if the message mentions a role the user has
} }
@@ -267,14 +274,11 @@ class Message extends SnowFlake{
return build; return build;
} }
async edit(content: string) { async edit(content: string) {
return await fetch( return await fetch(this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id, {
this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id,
{
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
body: JSON.stringify({content}), body: JSON.stringify({content}),
} });
);
} }
delete() { delete() {
fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, { fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, {
@@ -308,10 +312,7 @@ class Message extends SnowFlake{
prevmessage.generateMessage(); prevmessage.generateMessage();
} }
} }
if( if (this.channel.lastmessage === this || this.channel.lastmessageid === this.id) {
this.channel.lastmessage === this ||
this.channel.lastmessageid === this.id
){
if (prev) { if (prev) {
this.channel.lastmessage = this.channel.messages.get(prev); this.channel.lastmessage = this.channel.messages.get(prev);
this.channel.lastmessageid = prev; this.channel.lastmessageid = prev;
@@ -343,15 +344,17 @@ class Message extends SnowFlake{
this.generateMessage(); this.generateMessage();
} }
} }
generateMessage(premessage?: Message | undefined, ignoredblock = false,dupe:false|HTMLDivElement=false){ generateMessage(
premessage?: Message | undefined,
ignoredblock = false,
dupe: false | HTMLDivElement = false,
) {
const div = dupe || this.div; const div = dupe || this.div;
if (!div) return; if (!div) return;
const editmode = this.channel.editing === this; const editmode = this.channel.editing === this;
if (!premessage && !dupe) { if (!premessage && !dupe) {
premessage = this.channel.messages.get( premessage = this.channel.messages.get(this.channel.idToPrev.get(this.id) as string);
this.channel.idToPrev.get(this.id) as string
);
} }
for (const user of this.mentions) { for (const user of this.mentions) {
if (user === this.localuser.user) { if (user === this.localuser.user) {
@@ -373,7 +376,7 @@ class Message extends SnowFlake{
span.textContent = I18n.getTranslation("hideBlockedMessages"); span.textContent = I18n.getTranslation("hideBlockedMessages");
div.append(span); div.append(span);
span.classList.add("blocked"); span.classList.add("blocked");
span.onclick = _=>{ span.onclick = (_) => {
const scroll = this.channel.infinite.scrollTop; const scroll = this.channel.infinite.scrollTop;
let next: Message | undefined = this; let next: Message | undefined = this;
while (next?.author === this.author) { while (next?.author === this.author) {
@@ -403,15 +406,13 @@ class Message extends SnowFlake{
} }
span.textContent = I18n.getTranslation("showBlockedMessages", count + ""); span.textContent = I18n.getTranslation("showBlockedMessages", count + "");
build.append(span); build.append(span);
span.onclick = _=>{ span.onclick = (_) => {
const scroll = this.channel.infinite.scrollTop; const scroll = this.channel.infinite.scrollTop;
const func = this.channel.infinite.snapBottom(); const func = this.channel.infinite.snapBottom();
let next: Message | undefined = this; let next: Message | undefined = this;
while (next?.author === this.author) { while (next?.author === this.author) {
next.generateMessage(undefined, true); next.generateMessage(undefined, true);
next = this.channel.messages.get( next = this.channel.messages.get(this.channel.idToNext.get(next.id) as string);
this.channel.idToNext.get(next.id) as string
);
console.log("loopy"); console.log("loopy");
} }
if (this.channel.infinite.scollDiv && scroll) { if (this.channel.infinite.scollDiv && scroll) {
@@ -440,7 +441,7 @@ class Message extends SnowFlake{
line2.classList.add("reply"); line2.classList.add("reply");
replyline.classList.add("flexltr", "replyflex"); replyline.classList.add("flexltr", "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) { if (!message) {
minipfp.remove(); minipfp.remove();
username.textContent = I18n.getTranslation("message.deleted"); username.textContent = I18n.getTranslation("message.deleted");
@@ -457,13 +458,13 @@ 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(_=>{ Member.resolveMember(author, this.guild).then((_) => {
if (_) { if (_) {
username.textContent = _.name; username.textContent = _.name;
} }
})
}); });
reply.onclick = _=>{ });
reply.onclick = (_) => {
// TODO: FIX this // TODO: FIX this
this.channel.infinite.focus(this.message_reference.message_id); this.channel.infinite.focus(this.message_reference.message_id);
}; };
@@ -484,7 +485,11 @@ class Message extends SnowFlake{
const newt = new Date(this.timestamp).getTime() / 1000; const newt = new Date(this.timestamp).getTime() / 1000;
current = newt - old > 600; current = newt - old > 600;
} }
const combine = premessage?.author != this.author || current || this.message_reference || !messageTypes.has(premessage.type); const combine =
premessage?.author != this.author ||
current ||
this.message_reference ||
!messageTypes.has(premessage.type);
if (combine) { if (combine) {
const pfp = this.author.buildpfp(); const pfp = this.author.buildpfp();
this.author.bind(pfp, this.guild, false); this.author.bind(pfp, this.guild, false);
@@ -500,11 +505,11 @@ class Message extends SnowFlake{
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(_=>{ Member.resolveMember(this.author, this.guild).then((_) => {
if (_) { if (_) {
username.textContent = _.name; 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");
@@ -555,7 +560,7 @@ class Message extends SnowFlake{
this.generateMessage(); this.generateMessage();
} }
}); });
area.addEventListener("keydown", event=>{ area.addEventListener("keydown", (event) => {
this.localuser.keydown(event); this.localuser.keydown(event);
if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
if (event.key === "Escape") { if (event.key === "Escape") {
@@ -564,8 +569,8 @@ class Message extends SnowFlake{
} }
}); });
md.giveBox(area, (str, pre) => { md.giveBox(area, (str, pre) => {
this.localuser.search(search,md,str,pre) this.localuser.search(search, md, str, pre);
}) });
sb.append(search); sb.append(search);
box.append(sb, area); box.append(sb, area);
messagedwrap.append(box); messagedwrap.append(box);
@@ -573,14 +578,13 @@ class Message extends SnowFlake{
area.focus(); area.focus();
const fun = saveCaretPosition(area, Infinity); const fun = saveCaretPosition(area, Infinity);
if (fun) fun(); if (fun) fun();
}) });
} else { } else {
this.content.onUpdate = () => {}; this.content.onUpdate = () => {};
const messaged = this.content.makeHTML(); const messaged = this.content.makeHTML();
(div as any).txt = messaged; (div as any).txt = messaged;
messagedwrap.classList.add("flexttb"); messagedwrap.classList.add("flexttb");
messagedwrap.appendChild(messaged); messagedwrap.appendChild(messaged);
} }
text.appendChild(messagedwrap); text.appendChild(messagedwrap);
build.appendChild(text); build.appendChild(text);
@@ -635,7 +639,7 @@ class Message extends SnowFlake{
bindButtonEvent() { bindButtonEvent() {
if (this.div) { if (this.div) {
let buttons: HTMLDivElement | undefined; let buttons: HTMLDivElement | undefined;
this.div.onmouseenter = _=>{ this.div.onmouseenter = (_) => {
if (mobile) return; if (mobile) return;
if (buttons) { if (buttons) {
buttons.remove(); buttons.remove();
@@ -650,7 +654,7 @@ class Message extends SnowFlake{
reply.classList.add("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 = (_) => {
this.channel.setReplying(this); this.channel.setReplying(this);
}; };
} }
@@ -660,8 +664,8 @@ class Message extends SnowFlake{
reply.classList.add("svg-emoji", "svgicon"); reply.classList.add("svg-emoji", "svgicon");
container.append(reply); container.append(reply);
buttons.append(container); buttons.append(container);
container.onclick = e=>{ container.onclick = (e) => {
Emoji.emojiPicker(e.x, e.y, this.localuser).then(_=>{ Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => {
this.reactionToggle(_); this.reactionToggle(_);
}); });
}; };
@@ -672,7 +676,7 @@ class Message extends SnowFlake{
edit.classList.add("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 = (_) => {
this.setEdit(); this.setEdit();
}; };
} }
@@ -682,7 +686,7 @@ class Message extends SnowFlake{
reply.classList.add("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 = (_) => {
if (_.shiftKey) { if (_.shiftKey) {
this.delete(); this.delete();
return; return;
@@ -695,7 +699,7 @@ class Message extends SnowFlake{
} }
} }
}; };
this.div.onmouseleave = _=>{ this.div.onmouseleave = (_) => {
if (buttons) { if (buttons) {
buttons.remove(); buttons.remove();
buttons = undefined; buttons = undefined;
@@ -713,7 +717,7 @@ class Message extends SnowFlake{
}); });
options.addButtonInput("", I18n.getTranslation("no"), () => { options.addButtonInput("", I18n.getTranslation("no"), () => {
diaolog.hide(); diaolog.hide();
}) });
diaolog.show(); diaolog.show();
} }
updateReactions() { updateReactions() {
@@ -732,7 +736,10 @@ class Message extends SnowFlake{
if (/\d{17,21}/.test(thing.emoji.name)) { if (/\d{17,21}/.test(thing.emoji.name)) {
thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug
} }
const emo = new Emoji(thing.emoji as { name: string; id: string; animated: boolean },this.guild); const emo = new Emoji(
thing.emoji as {name: string; id: string; animated: boolean},
this.guild,
);
emoji = emo.getHTML(false); emoji = emo.getHTML(false);
} else { } else {
emoji = document.createElement("p"); emoji = document.createElement("p");
@@ -740,10 +747,13 @@ class Message extends SnowFlake{
} }
const h = new Hover(async () => { const h = new Hover(async () => {
//TODO this can't be real, name conflicts must happen, but for now it's fine //TODO this can't be real, name conflicts must happen, but for now it's fine
const f=await fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${thing.emoji.name}?limit=3&type=0`,{headers:this.headers}); const f = await fetch(
const json=await f.json() as userjson[]; `${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${thing.emoji.name}?limit=3&type=0`,
{headers: this.headers},
);
const json = (await f.json()) as userjson[];
let build = ""; let build = "";
let users=json.map(_=>new User(_,this.localuser)); let users = json.map((_) => new User(_, this.localuser));
//FIXME this is a spacebar bug, I can't fix this the api ignores limit and just sends everything. //FIXME this is a spacebar bug, I can't fix this the api ignores limit and just sends everything.
users = users.splice(0, 3); users = users.splice(0, 3);
let first = true; let first = true;
@@ -755,14 +765,12 @@ class Message extends SnowFlake{
first = false; first = false;
} }
if (thing.count > 3) { if (thing.count > 3) {
build+=", and more!" build += ", and more!";
} else { } else {
} }
build += "\nReacted with " + thing.emoji.name; build += "\nReacted with " + thing.emoji.name;
console.log(build); console.log(build);
return build; return build;
}); });
h.addEvent(reaction); h.addEvent(reaction);
const count = document.createElement("p"); const count = document.createElement("p");
@@ -772,7 +780,7 @@ class Message extends SnowFlake{
reaction.append(emoji); reaction.append(emoji);
reactdiv.append(reaction); reactdiv.append(reaction);
reaction.onclick = _=>{ reaction.onclick = (_) => {
this.reactionToggle(thing.emoji.name); this.reactionToggle(thing.emoji.name);
}; };
} }
@@ -858,14 +866,20 @@ let yesterdayStr: string;
function formatTime(date: Date) { function formatTime(date: Date) {
updateTimes(); updateTimes();
const datestring = date.toLocaleDateString(); const datestring = date.toLocaleDateString();
const formatTime = (date: Date)=>date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); const formatTime = (date: Date) =>
date.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
if (datestring === now) { if (datestring === now) {
return I18n.getTranslation("todayAt", formatTime(date)); return I18n.getTranslation("todayAt", formatTime(date));
} else if (datestring === yesterdayStr) { } else if (datestring === yesterdayStr) {
return I18n.getTranslation("yesterdayAt", formatTime(date)); return I18n.getTranslation("yesterdayAt", formatTime(date));
} else { } else {
return I18n.getTranslation("otherAt",formatTime(date),date.toLocaleDateString(),formatTime(date)); return I18n.getTranslation(
"otherAt",
formatTime(date),
date.toLocaleDateString(),
formatTime(date),
);
} }
} }
let tomorrow = 0; let tomorrow = 0;

View File

@@ -4,46 +4,46 @@ import { getBulkUsers, Specialuser } from "../utils/utils.js";
import {Permissions} from "../permissions.js"; import {Permissions} from "../permissions.js";
type botjsonfetch = { type botjsonfetch = {
guilds: { guilds: {
id: string, id: string;
name: string, name: string;
icon: string, icon: string;
mfa_level: number, mfa_level: number;
permissions: string permissions: string;
}[], }[];
"user": { user: {
id: string, id: string;
username: string, username: string;
avatar: string, avatar: string;
avatar_decoration?: string, avatar_decoration?: string;
discriminator: string, discriminator: string;
public_flags: number public_flags: number;
}, };
application: { application: {
id: string, id: string;
name: string, name: string;
icon: string|null, icon: string | null;
description: string, description: string;
summary: string, summary: string;
type: null,//not sure what this means :P type: null; //not sure what this means :P
hook: boolean, hook: boolean;
guild_id: null|string, guild_id: null | string;
bot_public: boolean, bot_public: boolean;
bot_require_code_grant: boolean, bot_require_code_grant: boolean;
verify_key: "IMPLEMENTME",//no clue what this is meant to be :P verify_key: "IMPLEMENTME"; //no clue what this is meant to be :P
flags: number flags: number;
}, };
bot: { bot: {
id: string, id: string;
username: string, username: string;
avatar: string|null, avatar: string | null;
avatar_decoration: null|string, avatar_decoration: null | string;
discriminator: string, discriminator: string;
public_flags: number, public_flags: number;
bot: boolean, bot: boolean;
approximated_guild_count: number approximated_guild_count: number;
}, };
authorized: boolean authorized: boolean;
} };
(async () => { (async () => {
const users = getBulkUsers(); const users = getBulkUsers();
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
@@ -77,9 +77,7 @@ type botjsonfetch={
} }
} }
} else { } else {
throw new Error( throw new Error("Someone needs to handle the case where the servers don't exist");
"Someone needs to handle the case where the servers don't exist"
);
} }
} else { } else {
urls = joinable[0].serverurls; urls = joinable[0].serverurls;
@@ -93,12 +91,14 @@ type botjsonfetch={
if (!urls) return; if (!urls) return;
fetch(urls.api + "/oauth2/authorize/" + window.location.search, { fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers: { headers: {
Authorization:user.token Authorization: user.token,
} },
}).then(_=>_.json()).then((json:botjsonfetch)=>{ })
.then((_) => _.json())
.then((json: botjsonfetch) => {
const guilds: botjsonfetch["guilds"] = []; const guilds: botjsonfetch["guilds"] = [];
for (const guild of json.guilds) { for (const guild of json.guilds) {
const permisions=new Permissions(guild.permissions) const permisions = new Permissions(guild.permissions);
if (permisions.hasPermission("MANAGE_GUILD")) { if (permisions.hasPermission("MANAGE_GUILD")) {
guilds.push(guild); guilds.push(guild);
} }
@@ -128,26 +128,26 @@ type botjsonfetch={
button.onclick = () => { button.onclick = () => {
const id = select.value; const id = select.value;
const params2 = new URLSearchParams(""); const params2 = new URLSearchParams("");
params2.set("client_id",params.get("client_id") as string) params2.set("client_id", params.get("client_id") as string);
fetch(urls.api + "/oauth2/authorize?" + params2.toString(), { fetch(urls.api + "/oauth2/authorize?" + params2.toString(), {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
authorize: true, authorize: true,
guild_id: id, guild_id: id,
permissions:permstr permissions: permstr,
}), }),
headers: { headers: {
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
Authorization: user.token, Authorization: user.token,
} },
}).then(req=>{ }).then((req) => {
if (req.ok) { if (req.ok) {
alert("Bot added successfully"); alert("Bot added successfully");
} }
}) });
} };
document.body.append(dialog); document.body.append(dialog);
}) });
} }
function showAccounts(): void { function showAccounts(): void {
const table = document.createElement("dialog"); const table = document.createElement("dialog");
@@ -168,9 +168,7 @@ type botjsonfetch={
userDiv.append(document.createElement("br")); userDiv.append(document.createElement("br"));
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = user.serverurls.wellknown span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
.replace("https://", "")
.replace("http://", "");
span.classList.add("serverURL"); span.classList.add("serverURL");
userDiv.append(span); userDiv.append(span);
@@ -211,12 +209,14 @@ type botjsonfetch={
} }
fetch(urls.api + "/oauth2/authorize/" + window.location.search, { fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers: { headers: {
Authorization:user.token Authorization: user.token,
} },
}).then(_=>_.json()).then((json:botjsonfetch)=>{ })
.then((_) => _.json())
.then((json: botjsonfetch) => {
const title = document.getElementById("invitename"); const title = document.getElementById("invitename");
if (title) { if (title) {
title.textContent=`Invite ${json.bot.username} to your servers` title.textContent = `Invite ${json.bot.username} to your servers`;
} }
const desc = document.getElementById("invitedescription"); const desc = document.getElementById("invitedescription");
if (desc) { if (desc) {
@@ -232,8 +232,8 @@ type botjsonfetch={
const perms = document.getElementById("permissions") as HTMLDivElement; const perms = document.getElementById("permissions") as HTMLDivElement;
if (perms && permstr) { if (perms && permstr) {
perms.children[0].textContent=I18n.getTranslation("htmlPages.idpermissions") perms.children[0].textContent = I18n.getTranslation("htmlPages.idpermissions");
const permisions=new Permissions(permstr) const permisions = new Permissions(permstr);
for (const perm of Permissions.info()) { for (const perm of Permissions.info()) {
if (permisions.hasPermission(perm.name, false)) { if (permisions.hasPermission(perm.name, false)) {
const div = document.createElement("div"); const div = document.createElement("div");
@@ -245,10 +245,10 @@ type botjsonfetch={
} }
} }
} }
}) });
const AcceptInvite = document.getElementById("AcceptInvite"); const AcceptInvite = document.getElementById("AcceptInvite");
if (AcceptInvite) { if (AcceptInvite) {
AcceptInvite.addEventListener("click", showAccounts); AcceptInvite.addEventListener("click", showAccounts);
AcceptInvite.textContent=I18n.getTranslation("htmlPages.addBot") AcceptInvite.textContent = I18n.getTranslation("htmlPages.addBot");
} }
})(); })();

View File

@@ -1,16 +1,25 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Bot Invite" property="og:title"> <meta content="Bot Invite" property="og:title" />
<meta name="description" content="Invite this bot to your server!"> <meta name="description" content="Invite this bot to your server!" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<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> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div> <div>

View File

@@ -13,7 +13,7 @@ class Permissions{
this.allow = 0n; this.allow = 0n;
this.deny = 0n; this.deny = 0n;
console.error( console.error(
`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` `Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`,
); );
} }
} }
@@ -31,7 +31,7 @@ class Permissions{
name: thing, name: thing,
readableName: I18n.getTranslation("permissions.readableNames." + thing), readableName: I18n.getTranslation("permissions.readableNames." + thing),
description: I18n.getTranslation("permissions.descriptions." + thing), description: I18n.getTranslation("permissions.descriptions." + thing),
} };
} }
} }
static permisions = [ static permisions = [
@@ -83,7 +83,7 @@ class Permissions{
"USE_EXTERNAL_SOUNDS", "USE_EXTERNAL_SOUNDS",
"SEND_VOICE_MESSAGES", "SEND_VOICE_MESSAGES",
"SEND_POLLS", "SEND_POLLS",
"USE_EXTERNAL_APPS" "USE_EXTERNAL_APPS",
]; ];
getPermission(name: string): number { getPermission(name: string): number {
if (undefined === Permissions.permisions.indexOf(name)) { if (undefined === Permissions.permisions.indexOf(name)) {
@@ -91,9 +91,7 @@ class Permissions{
} }
if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) { if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) {
return 1; return 1;
}else if( } else if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)) {
this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)
){
return -1; return -1;
} else { } else {
return 0; return 0;
@@ -102,11 +100,10 @@ class Permissions{
hasPermission(name: string, adminOverride = true): boolean { hasPermission(name: string, adminOverride = true): boolean {
if (this.deny) { if (this.deny) {
console.warn( console.warn(
"This function may of been used in error, think about using getPermision instead" "This function may of been used in error, think about using getPermision instead",
); );
} }
if(this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) return true;
return true;
if (name !== "ADMINISTRATOR" && adminOverride) return this.hasPermission("ADMINISTRATOR"); if (name !== "ADMINISTRATOR" && adminOverride) return this.hasPermission("ADMINISTRATOR");
return false; return false;
} }
@@ -114,11 +111,7 @@ class Permissions{
const bit = Permissions.permisions.indexOf(name); const bit = Permissions.permisions.indexOf(name);
if (bit === undefined) { if (bit === undefined) {
return console.error( return console.error(
"Tried to set permission to " + "Tried to set permission to " + setto + " for " + name + " but it doesn't exist",
setto +
" for " +
name +
" but it doesn't exist"
); );
} }

View File

@@ -1,16 +1,25 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <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" />
<title>Jank Client</title> <title>Jank Client</title>
<meta content="Jank Client" property="og:title"> <meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description"> <meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image"> <meta content="/logo.webp" property="og:image" />
<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> <style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head> </head>
<body class="no-theme"> <body class="no-theme">
<div id="logindiv"> <div id="logindiv">
@@ -19,41 +28,53 @@
<div> <div>
<label for="instance" id="instanceField"><b>Instance:</b></label> <label for="instance" id="instanceField"><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="" required> <input
type="search"
list="instances"
placeholder="Instance URL"
id="instancein"
name="instance"
value=""
required
/>
</div> </div>
<div> <div>
<label for="uname" id="emailField"><b>Email:</b></label> <label for="uname" id="emailField"><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" id="userField"><b>Username:</b></label> <label for="uname" id="userField"><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" id="pwField"><b>Password:</b></label> <label for="psw" id="pwField"><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" id="pw2Field"><b>Enter password again:</b></label> <label for="psw2" id="pw2Field"><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" id="dobField"><b>Date of birth:</b></label> <label for="date" id="dobField"><b>Date of birth:</b></label>
<input type="date" id="date" name="date"> <input type="date" id="date" name="date" />
</div> </div>
<div> <div>
<b id="TOSbox">I agree to the <a href="" id="TOSa">Terms of Service</a>:</b> <b id="TOSbox">I agree to the <a href="" id="TOSa">Terms of Service</a>:</b>
<input type="checkbox" id="TOS" name="TOS"> <input type="checkbox" id="TOS" name="TOS" />
</div> </div>
<p class="wrongred" id="wrong"></p> <p class="wrongred" id="wrong"></p>
<div id="h-captcha"> <div id="h-captcha"></div>
</div>
<button type="submit" class="dontgrow" id="createAccount">Create account</button> <button type="submit" class="dontgrow" id="createAccount">Create account</button>
</form> </form>
<a href="/login.html" id="switch" id="alreadyHave">Already have an account?</a> <a href="/login.html" id="switch" id="alreadyHave">Already have an account?</a>

View File

@@ -2,7 +2,7 @@ import { I18n } from "./i18n.js";
import {checkInstance} from "./utils/utils.js"; import {checkInstance} from "./utils/utils.js";
import {adduser} from "./login.js"; import {adduser} from "./login.js";
import {MarkDown} from "./markdown.js"; import {MarkDown} from "./markdown.js";
await I18n.done await I18n.done;
const registerElement = document.getElementById("register"); const registerElement = document.getElementById("register");
if (registerElement) { if (registerElement) {
registerElement.addEventListener("submit", registertry); registerElement.addEventListener("submit", registertry);
@@ -15,17 +15,16 @@ if(registerElement){
const createAccount = document.getElementById("createAccount"); const createAccount = document.getElementById("createAccount");
const alreadyHave = document.getElementById("alreadyHave"); const alreadyHave = document.getElementById("alreadyHave");
if (userField && pw2Field && alreadyHave && createAccount && dobField) { if (userField && pw2Field && alreadyHave && createAccount && dobField) {
userField.textContent=I18n.getTranslation("htmlPages.userField") userField.textContent = I18n.getTranslation("htmlPages.userField");
pw2Field.textContent=I18n.getTranslation("htmlPages.pw2Field") pw2Field.textContent = I18n.getTranslation("htmlPages.pw2Field");
dobField.textContent=I18n.getTranslation("htmlPages.dobField") dobField.textContent = I18n.getTranslation("htmlPages.dobField");
createAccount.textContent=I18n.getTranslation("htmlPages.createAccount") createAccount.textContent = I18n.getTranslation("htmlPages.createAccount");
alreadyHave.textContent=I18n.getTranslation("htmlPages.alreadyHave") alreadyHave.textContent = I18n.getTranslation("htmlPages.alreadyHave");
} }
})() })();
async function registertry(e: Event) { async function registertry(e: Event) {
e.preventDefault(); e.preventDefault();
const elements = (e.target as HTMLFormElement) const elements = (e.target as HTMLFormElement).elements as HTMLFormControlsCollection;
.elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value; const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value; const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value; const password = (elements[3] as HTMLInputElement).value;
@@ -35,7 +34,9 @@ async function registertry(e: Event){
const captchaKey = (elements[7] as HTMLInputElement)?.value; const captchaKey = (elements[7] as HTMLInputElement)?.value;
if (password !== confirmPassword) { if (password !== confirmPassword) {
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation("localuser.PasswordsNoMatch"); (document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation(
"localuser.PasswordsNoMatch",
);
return; return;
} }
@@ -99,22 +100,22 @@ function handleErrors(errors: any, elements: HTMLFormControlsCollection){
} else if (errors.password) { } else if (errors.password) {
showError( showError(
elements[3] as HTMLElement, elements[3] as HTMLElement,
I18n.getTranslation("register.passwordError",errors.password._errors[0].message) I18n.getTranslation("register.passwordError", errors.password._errors[0].message),
); );
} else if (errors.username) { } else if (errors.username) {
showError( showError(
elements[2] as HTMLElement, elements[2] as HTMLElement,
I18n.getTranslation("register.usernameError",errors.username._errors[0].message) I18n.getTranslation("register.usernameError", errors.username._errors[0].message),
); );
} else if (errors.email) { } else if (errors.email) {
showError( showError(
elements[1] as HTMLElement, elements[1] as HTMLElement,
I18n.getTranslation("register.emailError",errors.email._errors[0].message) I18n.getTranslation("register.emailError", errors.email._errors[0].message),
); );
} else if (errors.date_of_birth) { } else if (errors.date_of_birth) {
showError( showError(
elements[5] as HTMLElement, elements[5] as HTMLElement,
I18n.getTranslation("register.DOBError",errors.date_of_birth._errors[0].message) I18n.getTranslation("register.DOBError", errors.date_of_birth._errors[0].message),
); );
} else { } else {
(document.getElementById("wrong") as HTMLElement).textContent = (document.getElementById("wrong") as HTMLElement).textContent =
@@ -124,9 +125,7 @@ errors[Object.keys(errors)[0]]._errors[0].message;
function showError(element: HTMLElement, message: string) { function showError(element: HTMLElement, message: string) {
const parent = element.parentElement!; const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName( let errorElement = parent.getElementsByClassName("suberror")[0] as HTMLElement;
"suberror"
)[0] as HTMLElement;
if (!errorElement) { if (!errorElement) {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("suberror", "suberrora"); div.classList.add("suberror", "suberrora");

View File

@@ -53,7 +53,7 @@ class Role extends SnowFlake{
canManage() { canManage() {
if (this.guild.member.hasPermission("MANAGE_ROLES")) { if (this.guild.member.hasPermission("MANAGE_ROLES")) {
let max = -Infinity; let max = -Infinity;
this.guild.member.roles.forEach(r=>max=Math.max(max,r.position)) 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 this.position <= max || this.guild.properties.owner_id === this.guild.member.id;
} }
return false; return false;
@@ -70,11 +70,7 @@ class PermissionToggle implements OptionsElement<number>{
permissions: Permissions; permissions: Permissions;
owner: Options; owner: Options;
value!: number; value!: number;
constructor( constructor(roleJSON: PermissionToggle["rolejson"], permissions: Permissions, owner: Options) {
roleJSON: PermissionToggle["rolejson"],
permissions: Permissions,
owner: Options
){
this.rolejson = roleJSON; this.rolejson = roleJSON;
this.permissions = permissions; this.permissions = permissions;
this.owner = owner; this.owner = owner;
@@ -106,7 +102,7 @@ class PermissionToggle implements OptionsElement<number>{
if (state === 1) { if (state === 1) {
on.checked = true; on.checked = true;
} }
on.onclick = _=>{ on.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 1); this.permissions.setPermission(this.rolejson.name, 1);
this.owner.changed(); this.owner.changed();
}; };
@@ -118,7 +114,7 @@ class PermissionToggle implements OptionsElement<number>{
if (state === 0) { if (state === 0) {
no.checked = true; no.checked = true;
} }
no.onclick = _=>{ no.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 0); this.permissions.setPermission(this.rolejson.name, 0);
this.owner.changed(); this.owner.changed();
}; };
@@ -130,7 +126,7 @@ class PermissionToggle implements OptionsElement<number>{
if (state === -1) { if (state === -1) {
off.checked = true; off.checked = true;
} }
off.onclick = _=>{ off.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, -1); this.permissions.setPermission(this.rolejson.name, -1);
this.owner.changed(); this.owner.changed();
}; };
@@ -158,7 +154,12 @@ class RoleList extends Buttons{
get headers() { get headers() {
return this.guild.headers; return this.guild.headers;
} }
constructor(permissions:[Role, Permissions][], guild:Guild, onchange:Function, channel:false|Channel){ constructor(
permissions: [Role, Permissions][],
guild: Guild,
onchange: Function,
channel: false | Channel,
) {
super(""); super("");
this.guild = guild; this.guild = guild;
this.permissions = permissions; this.permissions = permissions;
@@ -172,9 +173,7 @@ class RoleList extends Buttons{
} }
this.makeguildmenus(options); 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) {
this.buttons.push([i[0].name, i[0].id]); this.buttons.push([i[0].name, i[0].id]);
@@ -192,15 +191,15 @@ class RoleList extends Buttons{
} }
} }
if (added === -1) { if (added === -1) {
this.permissions=this.permissions.filter(r=>r[0]!==role); this.permissions = this.permissions.filter((r) => r[0] !== role);
} }
this.redoButtons(); this.redoButtons();
} }
private croleUpdate(role: Role, perm: Permissions, added: boolean) { private croleUpdate(role: Role, perm: Permissions, added: boolean) {
if (added) { if (added) {
this.permissions.push([role,perm]) this.permissions.push([role, perm]);
} else { } else {
this.permissions=this.permissions.filter(r=>r[0]!==role); this.permissions = this.permissions.filter((r) => r[0] !== role);
} }
this.redoButtons(); this.redoButtons();
} }
@@ -212,51 +211,59 @@ class RoleList extends Buttons{
fetchURL: this.info.api + "/guilds/" + this.guild.id + "/roles/" + this.curid, fetchURL: this.info.api + "/guilds/" + this.guild.id + "/roles/" + this.curid,
method: "PATCH", method: "PATCH",
headers: this.headers, headers: this.headers,
traditionalSubmit:true traditionalSubmit: true,
}); });
form.addTextInput(I18n.getTranslation("role.name"), "name", { form.addTextInput(I18n.getTranslation("role.name"), "name", {
initText:role.name initText: role.name,
}); });
form.addCheckboxInput(I18n.getTranslation("role.hoisted"), "hoist", { form.addCheckboxInput(I18n.getTranslation("role.hoisted"), "hoist", {
initState:role.hoist initState: role.hoist,
}); });
form.addCheckboxInput(I18n.getTranslation("role.mentionable"), "mentionable", { form.addCheckboxInput(I18n.getTranslation("role.mentionable"), "mentionable", {
initState:role.mentionable initState: role.mentionable,
}); });
const color = "#" + role.color.toString(16).padStart(6, "0"); const color = "#" + role.color.toString(16).padStart(6, "0");
form.addColorInput(I18n.getTranslation("role.color"), "color", { form.addColorInput(I18n.getTranslation("role.color"), "color", {
initColor:color initColor: color,
}); });
form.addPreprocessor((obj: any) => { form.addPreprocessor((obj: any) => {
obj.color = Number("0x" + obj.color.substring(1)); obj.color = Number("0x" + obj.color.substring(1));
console.log(obj.color); console.log(obj.color);
}) });
}) });
} }
static channelrolemenu = this.ChannelRoleMenu(); static channelrolemenu = this.ChannelRoleMenu();
static guildrolemenu = this.GuildRoleMenu(); static guildrolemenu = this.GuildRoleMenu();
private static ChannelRoleMenu() { private static ChannelRoleMenu() {
const menu = new Contextmenu<RoleList, Role>("role settings"); const menu = new Contextmenu<RoleList, Role>("role settings");
menu.addbutton(()=>I18n.getTranslation("role.remove"),function(role){ menu.addbutton(
() => I18n.getTranslation("role.remove"),
function (role) {
if (!this.channel) return; if (!this.channel) return;
console.log(role); console.log(role);
fetch(this.info.api + "/channels/" + this.channel.id + "/permissions/" + role.id, { fetch(this.info.api + "/channels/" + this.channel.id + "/permissions/" + role.id, {
method: "DELETE", method: "DELETE",
headers:this.headers headers: this.headers,
}) });
},null); },
null,
);
return menu; return menu;
} }
private static GuildRoleMenu() { private static GuildRoleMenu() {
const menu = new Contextmenu<RoleList, Role>("role settings"); const menu = new Contextmenu<RoleList, Role>("role settings");
menu.addbutton(()=>I18n.getTranslation("role.delete"),function(role){ menu.addbutton(
() => I18n.getTranslation("role.delete"),
function (role) {
if (!confirm(I18n.getTranslation("role.confirmDelete"))) return; if (!confirm(I18n.getTranslation("role.confirmDelete"))) return;
console.log(role); console.log(role);
fetch(this.info.api + "/guilds/" + this.guild.id + "/roles/" + role.id, { fetch(this.info.api + "/guilds/" + this.guild.id + "/roles/" + role.id, {
method: "DELETE", method: "DELETE",
headers:this.headers headers: this.headers,
}) });
},null); },
null,
);
return menu; return menu;
} }
redoButtons() { redoButtons() {
@@ -265,7 +272,7 @@ class RoleList extends Buttons{
for (const i of this.permissions) { for (const i of this.permissions) {
this.buttons.push([i[0].name, i[0].id]); this.buttons.push([i[0].name, i[0].id]);
} }
console.log("in here :P") console.log("in here :P");
if (!this.buttonList) return; if (!this.buttonList) return;
console.log("in here :P"); console.log("in here :P");
const elms = Array.from(this.buttonList.children); const elms = Array.from(this.buttonList.children);
@@ -279,7 +286,7 @@ class RoleList extends Buttons{
dragged?: HTMLButtonElement; dragged?: HTMLButtonElement;
buttonDragEvents(button: HTMLButtonElement, role: Role) { buttonDragEvents(button: HTMLButtonElement, role: Role) {
this.buttonMap.set(button, role); this.buttonMap.set(button, role);
button.addEventListener("dragstart", e=>{ button.addEventListener("dragstart", (e) => {
this.dragged = button; this.dragged = button;
e.stopImmediatePropagation(); e.stopImmediatePropagation();
}); });
@@ -288,18 +295,18 @@ class RoleList extends Buttons{
this.dragged = undefined; this.dragged = undefined;
}); });
button.addEventListener("dragenter", event=>{ button.addEventListener("dragenter", (event) => {
console.log("enter"); console.log("enter");
event.preventDefault(); event.preventDefault();
return true; return true;
}); });
button.addEventListener("dragover", event=>{ button.addEventListener("dragover", (event) => {
event.preventDefault(); event.preventDefault();
return true; return true;
}); });
button.addEventListener("drop", _=>{ button.addEventListener("drop", (_) => {
const role2 = this.buttonMap.get(this.dragged as HTMLButtonElement); const role2 = this.buttonMap.get(this.dragged as HTMLButtonElement);
if (!role2) return; if (!role2) return;
const index2 = this.guild.roles.indexOf(role2); const index2 = this.guild.roles.indexOf(role2);
@@ -325,7 +332,7 @@ class RoleList extends Buttons{
if (this.channel) { if (this.channel) {
const roles: [Role, string[]][] = []; const roles: [Role, string[]][] = [];
for (const role of this.guild.roles) { for (const role of this.guild.roles) {
if(this.permissions.find(r=>r[0]==role)){ if (this.permissions.find((r) => r[0] == role)) {
continue; continue;
} }
roles.push([role, [role.name]]); roles.push([role, [role.name]]);
@@ -334,7 +341,6 @@ class RoleList extends Buttons{
const found = await search.find(box.left, box.top); const found = await search.find(box.left, box.top);
if (!found) return; if (!found) return;
console.log(found); console.log(found);
this.onchange(found.id, new Permissions("0", "0")); this.onchange(found.id, new Permissions("0", "0"));
@@ -345,7 +351,7 @@ class RoleList extends Buttons{
bar.type = "text"; bar.type = "text";
div.style.left = (box.left ^ 0) + "px"; div.style.left = (box.left ^ 0) + "px";
div.style.top = (box.top ^ 0) + "px"; div.style.top = (box.top ^ 0) + "px";
div.append(bar) div.append(bar);
document.body.append(div); document.body.append(div);
if (Contextmenu.currentmenu != "") { if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
@@ -354,7 +360,7 @@ class RoleList extends Buttons{
Contextmenu.keepOnScreen(div); Contextmenu.keepOnScreen(div);
bar.onchange = () => { bar.onchange = () => {
div.remove(); div.remove();
console.log(bar.value) console.log(bar.value);
if (bar.value === "") return; if (bar.value === "") return;
fetch(this.info.api + `/guilds/${this.guild.id}/roles`, { fetch(this.info.api + `/guilds/${this.guild.id}/roles`, {
method: "POST", method: "POST",
@@ -362,12 +368,12 @@ class RoleList extends Buttons{
body: JSON.stringify({ body: JSON.stringify({
color: 0, color: 0,
name: bar.value, name: bar.value,
permissions:"" permissions: "",
}) }),
}) });
} };
}
} }
};
roleRow.append(add); roleRow.append(add);
buttonTable.append(roleRow); buttonTable.append(roleRow);
@@ -381,15 +387,15 @@ class RoleList extends Buttons{
if (role.canManage()) { if (role.canManage()) {
this.buttonDragEvents(button, role); this.buttonDragEvents(button, role);
button.draggable = true; button.draggable = true;
RoleList.guildrolemenu.bindContextmenu(button,this,role) RoleList.guildrolemenu.bindContextmenu(button, this, role);
} }
} else { } else {
if (role.canManage()) { if (role.canManage()) {
RoleList.channelrolemenu.bindContextmenu(button,this,role) RoleList.channelrolemenu.bindContextmenu(button, this, role);
} }
} }
} }
button.onclick = _=>{ button.onclick = (_) => {
this.generateHTMLArea(thing[1], html); this.generateHTMLArea(thing[1], html);
if (this.warndiv) { if (this.warndiv) {
this.warndiv.remove(); this.warndiv.remove();
@@ -408,12 +414,12 @@ class RoleList extends Buttons{
} }
handleString(str: string): HTMLElement { handleString(str: string): HTMLElement {
this.curid = str; this.curid = str;
const arr = this.permissions.find(_=>_[0].id === str); const arr = this.permissions.find((_) => _[0].id === str);
if (arr) { if (arr) {
const perm = arr[1]; const perm = arr[1];
this.permission.deny = perm.deny; this.permission.deny = perm.deny;
this.permission.allow = perm.allow; this.permission.allow = perm.allow;
const role = this.permissions.find(e=>e[0].id === str); const role = this.permissions.find((e) => e[0].id === str);
if (role) { if (role) {
this.options.name = role[0].name; this.options.name = role[0].name;
this.options.haschanged = false; this.options.haschanged = false;

View File

@@ -4,17 +4,17 @@ class Search<E>{
options: Map<string, E>; options: Map<string, E>;
readonly keys: string[]; readonly keys: string[];
constructor(options: [E, string[]][]) { constructor(options: [E, string[]][]) {
const map=options.flatMap(e=>{ const map = options.flatMap((e) => {
const val=e[1].map(f=>[f,e[0]]); const val = e[1].map((f) => [f, e[0]]);
return val as [string, E][]; return val as [string, E][];
}) });
this.options = new Map(map); this.options = new Map(map);
this.keys = [...this.options.keys()]; this.keys = [...this.options.keys()];
} }
generateList(str: string, max: number, res: (e: E) => void) { generateList(str: string, max: number, res: (e: E) => void) {
str = str.toLowerCase(); str = str.toLowerCase();
const options=this.keys.filter(e=>{ const options = this.keys.filter((e) => {
return e.toLowerCase().includes(str) return e.toLowerCase().includes(str);
}); });
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("OptionList", "flexttb"); div.classList.add("OptionList", "flexttb");
@@ -23,15 +23,14 @@ class Search<E>{
hoption.textContent = option; hoption.textContent = option;
hoption.onclick = () => { hoption.onclick = () => {
if (!this.options.has(option)) return; if (!this.options.has(option)) return;
res(this.options.get(option) as E) res(this.options.get(option) as E);
} };
div.append(hoption); div.append(hoption);
} }
return div; return div;
} }
async find(x: number, y: number, max = 4): Promise<E | undefined> { async find(x: number, y: number, max = 4): Promise<E | undefined> {
return new Promise<E | undefined>((res) => { return new Promise<E | undefined>((res) => {
const container = document.createElement("div"); const container = document.createElement("div");
container.classList.add("fixedsearch"); container.classList.add("fixedsearch");
console.log((x ^ 0) + "", (y ^ 0) + ""); console.log((x ^ 0) + "", (y ^ 0) + "");
@@ -41,7 +40,7 @@ class Search<E>{
container.remove = () => { container.remove = () => {
remove.call(container); remove.call(container);
res(undefined); res(undefined);
} };
function resolve(e: E) { function resolve(e: E) {
res(e); res(e);
@@ -53,7 +52,7 @@ class Search<E>{
const html = this.generateList(bar.value, max, resolve); const html = this.generateList(bar.value, max, resolve);
options.innerHTML = ""; options.innerHTML = "";
options.append(html); options.append(html);
} };
bar.oninput = keydown; bar.oninput = keydown;
keydown(); keydown();
bar.type = "text"; bar.type = "text";
@@ -65,8 +64,7 @@ class Search<E>{
} }
Contextmenu.currentmenu = container; Contextmenu.currentmenu = container;
Contextmenu.keepOnScreen(container); Contextmenu.keepOnScreen(container);
});
})
} }
} }
export {Search}; export {Search};

View File

@@ -29,10 +29,13 @@ async function checkCache(){
lastcache = await promise.text(); lastcache = await promise.text();
} }
console.log(lastcache); console.log(lastcache);
fetch("/getupdates").then(async data=>{ fetch("/getupdates").then(async (data) => {
setTimeout((_: any)=>{ setTimeout(
(_: any) => {
checkedrecently = false; checkedrecently = false;
}, 1000 * 60 * 30); },
1000 * 60 * 30,
);
if (!data.ok) return; if (!data.ok) return;
const text = await data.clone().text(); const text = await data.clone().text();
console.log(text, lastcache); console.log(text, lastcache);
@@ -51,7 +54,6 @@ function samedomain(url: string | URL){
const htmlFiles = new Set(["/index", "/login", "/home", "/register", "/oauth2/auth"]); const htmlFiles = new Set(["/index", "/login", "/home", "/register", "/oauth2/auth"]);
function isHtml(url: string): string | void { function isHtml(url: string): string | void {
const path = new URL(url).pathname; const path = new URL(url).pathname;
if (htmlFiles.has(path) || htmlFiles.has(path + ".html")) { if (htmlFiles.has(path) || htmlFiles.has(path + ".html")) {
@@ -67,9 +69,9 @@ function toPath(url:string):string{
if (!html) { if (!html) {
const path = Url.pathname; const path = Url.pathname;
if (path.startsWith("/channels")) { if (path.startsWith("/channels")) {
html="./index.html" html = "./index.html";
} else if (path.startsWith("/invite/") || path === "/invite") { } else if (path.startsWith("/invite/") || path === "/invite") {
html="./invite.html" html = "./invite.html";
} }
} }
return html || Url.pathname; return html || Url.pathname;
@@ -77,7 +79,11 @@ function toPath(url:string):string{
let fails = 0; let fails = 0;
async function getfile(event: FetchEvent): Promise<Response> { async function getfile(event: FetchEvent): Promise<Response> {
checkCache(); checkCache();
if(!samedomain(event.request.url)||enabled==="false"||(enabled==="offlineOnly"&&!offline)){ if (
!samedomain(event.request.url) ||
enabled === "false" ||
(enabled === "offlineOnly" && !offline)
) {
const responce = await fetch(event.request.clone()); const responce = await fetch(event.request.clone());
if (samedomain(event.request.url)) { if (samedomain(event.request.url)) {
if (enabled === "offlineOnly" && responce.ok) { if (enabled === "offlineOnly" && responce.ok) {
@@ -115,7 +121,6 @@ async function getfile(event: FetchEvent):Promise<Response>{
} }
} }
self.addEventListener("fetch", (e) => { self.addEventListener("fetch", (e) => {
const event = e as FetchEvent; const event = e as FetchEvent;
try { try {
@@ -139,4 +144,4 @@ self.addEventListener("message", (message)=>{
deleteoldcache(); deleteoldcache();
break; break;
} }
}) });

View File

@@ -52,7 +52,7 @@ class Buttons implements OptionsElement<unknown>{
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], optionsArea); this.generateHTMLArea(thing[1], optionsArea);
if (this.warndiv) { if (this.warndiv) {
this.warndiv.remove(); this.warndiv.remove();
@@ -67,10 +67,7 @@ class Buttons implements OptionsElement<unknown>{
div.textContent = str; div.textContent = str;
return div; return div;
} }
generateHTMLArea( generateHTMLArea(buttonInfo: Options | string, htmlarea: HTMLElement) {
buttonInfo: Options | string,
htmlarea: HTMLElement
){
let html: HTMLElement; let html: HTMLElement;
if (buttonInfo instanceof Options) { if (buttonInfo instanceof Options) {
buttonInfo.subOptions = undefined; buttonInfo.subOptions = undefined;
@@ -101,7 +98,7 @@ class TextInput implements OptionsElement<string>{
label: string, label: string,
onSubmit: (str: string) => void, onSubmit: (str: string) => void,
owner: Options, owner: Options,
{ initText = "", password = false } = {} {initText = "", password = false} = {},
) { ) {
this.label = label; this.label = label;
this.value = initText; this.value = initText;
@@ -131,7 +128,7 @@ class TextInput implements OptionsElement<string>{
this.value = value; this.value = value;
} }
} }
onchange: (str: string) => void = _=>{}; onchange: (str: string) => void = (_) => {};
watchForChange(func: (str: string) => void) { watchForChange(func: (str: string) => void) {
this.onchange = func; this.onchange = func;
} }
@@ -190,7 +187,7 @@ class CheckboxInput implements OptionsElement<boolean>{
label: string, label: string,
onSubmit: (str: boolean) => void, onSubmit: (str: boolean) => void,
owner: Options, owner: Options,
{ initState = false } = {} {initState = false} = {},
) { ) {
this.label = label; this.label = label;
this.value = initState; this.value = initState;
@@ -228,7 +225,7 @@ class CheckboxInput implements OptionsElement<boolean>{
} }
} }
} }
onchange: (str: boolean) => void = _=>{}; onchange: (str: boolean) => void = (_) => {};
watchForChange(func: (str: boolean) => void) { watchForChange(func: (str: boolean) => void) {
this.onchange = func; this.onchange = func;
} }
@@ -243,13 +240,7 @@ class ButtonInput implements OptionsElement<void>{
readonly onClick: () => void; readonly onClick: () => void;
textContent: string; textContent: string;
value!: void; value!: void;
constructor( constructor(label: string, textContent: string, onClick: () => void, owner: Options, {} = {}) {
label: string,
textContent: string,
onClick: () => void,
owner: Options,
{} = {}
){
this.label = label; this.label = label;
this.owner = owner; this.owner = owner;
this.onClick = onClick; this.onClick = onClick;
@@ -287,7 +278,7 @@ class ColorInput implements OptionsElement<string>{
label: string, label: string,
onSubmit: (str: string) => void, onSubmit: (str: string) => void,
owner: Options, owner: Options,
{ initColor = "" } = {} {initColor = ""} = {},
) { ) {
this.label = label; this.label = label;
this.colorContent = initColor; this.colorContent = initColor;
@@ -317,7 +308,7 @@ class ColorInput implements OptionsElement<string>{
this.colorContent = value; this.colorContent = value;
} }
} }
onchange: (str: string) => void = _=>{}; onchange: (str: string) => void = (_) => {};
watchForChange(func: (str: string) => void) { watchForChange(func: (str: string) => void) {
this.onchange = func; this.onchange = func;
} }
@@ -342,7 +333,7 @@ class SelectInput implements OptionsElement<number>{
onSubmit: (str: number) => void, onSubmit: (str: number) => void,
options: string[], options: string[],
owner: Options, owner: Options,
{ defaultIndex = 0,radio=false } = {} {defaultIndex = 0, radio = false} = {},
) { ) {
this.label = label; this.label = label;
this.index = defaultIndex; this.index = defaultIndex;
@@ -434,7 +425,7 @@ class SelectInput implements OptionsElement<number>{
this.index = value; this.index = value;
} }
} }
onchange: (str: number) => void = _=>{}; onchange: (str: number) => void = (_) => {};
watchForChange(func: (str: number) => void) { watchForChange(func: (str: number) => void) {
this.onchange = func; this.onchange = func;
} }
@@ -452,7 +443,7 @@ class MDInput implements OptionsElement<string>{
label: string, label: string,
onSubmit: (str: string) => void, onSubmit: (str: string) => void,
owner: Options, owner: Options,
{ initText = "" } = {} {initText = ""} = {},
) { ) {
this.label = label; this.label = label;
this.value = initText; this.value = initText;
@@ -481,7 +472,7 @@ class MDInput implements OptionsElement<string>{
this.value = value; this.value = value;
} }
} }
onchange: (str: string) => void = _=>{}; onchange: (str: string) => void = (_) => {};
watchForChange(func: (str: string) => void) { watchForChange(func: (str: string) => void) {
this.onchange = func; this.onchange = func;
} }
@@ -500,7 +491,7 @@ class FileInput implements OptionsElement<FileList | null>{
label: string, label: string,
onSubmit: (str: FileList | null) => void, onSubmit: (str: FileList | null) => void,
owner: Options, owner: Options,
{ clear = false } = {} {clear = false} = {},
) { ) {
this.label = label; this.label = label;
this.owner = owner; this.owner = owner;
@@ -522,7 +513,7 @@ class FileInput implements OptionsElement<FileList | null>{
if (this.clear) { if (this.clear) {
const button = document.createElement("button"); const button = document.createElement("button");
button.textContent = "Clear"; button.textContent = "Clear";
button.onclick = _=>{ button.onclick = (_) => {
if (this.onchange) { if (this.onchange) {
this.onchange(null); this.onchange(null);
} }
@@ -582,7 +573,7 @@ class Float{
* This is a simple wrapper class for Options to make it happy so it can be used outside of Settings. * This is a simple wrapper class for Options to make it happy so it can be used outside of Settings.
*/ */
constructor(name: string, options = {ltr: false, noSubmit: true}) { constructor(name: string, options = {ltr: false, noSubmit: true}) {
this.options=new Options(name,this,options) this.options = new Options(name, this, options);
} }
changed = () => {}; changed = () => {};
generateHTML() { generateHTML() {
@@ -605,12 +596,12 @@ class Dialog{
center.classList.add("centeritem", "nonimagecenter"); center.classList.add("centeritem", "nonimagecenter");
center.classList.remove("titlediv"); center.classList.remove("titlediv");
background.append(center); background.append(center);
center.onclick=e=>{ center.onclick = (e) => {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
} };
document.body.append(background); document.body.append(background);
this.background = new WeakRef(background); this.background = new WeakRef(background);
background.onclick = _=>{ background.onclick = (_) => {
background.remove(); background.remove();
}; };
} }
@@ -630,13 +621,11 @@ class Options implements OptionsElement<void>{
value!: void; value!: void;
readonly html: WeakMap<OptionsElement<any>, WeakRef<HTMLDivElement>> = new WeakMap(); readonly html: WeakMap<OptionsElement<any>, WeakRef<HTMLDivElement>> = new WeakMap();
readonly noSubmit: boolean = false; readonly noSubmit: boolean = false;
container: WeakRef<HTMLDivElement> = new WeakRef( container: WeakRef<HTMLDivElement> = new WeakRef(document.createElement("div"));
document.createElement("div")
);
constructor( constructor(
name: string, name: string,
owner: Buttons | Options | Form | Float, owner: Buttons | Options | Form | Float,
{ ltr = false, noSubmit=false} = {} {ltr = false, noSubmit = false} = {},
) { ) {
this.name = name; this.name = name;
this.options = []; this.options = [];
@@ -678,9 +667,7 @@ class Options implements OptionsElement<void>{
(this.owner as Form).owner.genTop(); (this.owner as Form).owner.genTop();
} }
} else { } else {
throw new Error( throw new Error("Tried to make a sub menu when the options weren't rendered");
"Tried to make a sub menu when the options weren't rendered"
);
} }
} }
addSubOptions(name: string, {ltr = false, noSubmit = false} = {}) { addSubOptions(name: string, {ltr = false, noSubmit = false} = {}) {
@@ -699,7 +686,7 @@ class Options implements OptionsElement<void>{
headers = {}, headers = {},
method = "POST", method = "POST",
traditionalSubmit = false, traditionalSubmit = false,
} = {} } = {},
) { ) {
const options = new Form(name, this, onSubmit, { const options = new Form(name, this, onSubmit, {
ltr, ltr,
@@ -721,20 +708,17 @@ class Options implements OptionsElement<void>{
label: string, label: string,
onSubmit: (str: number) => void, onSubmit: (str: number) => void,
selections: string[], selections: string[],
{ defaultIndex = 0,radio=false } = {} {defaultIndex = 0, radio = false} = {},
) { ) {
const select = new SelectInput(label, onSubmit, selections, this, { const select = new SelectInput(label, onSubmit, selections, this, {
defaultIndex,radio defaultIndex,
radio,
}); });
this.options.push(select); this.options.push(select);
this.generate(select); this.generate(select);
return select; return select;
} }
addFileInput( addFileInput(label: string, onSubmit: (files: FileList | null) => void, {clear = false} = {}) {
label: string,
onSubmit: (files: FileList | null) => void,
{ clear = false } = {}
){
const FI = new FileInput(label, onSubmit, this, {clear}); const FI = new FileInput(label, onSubmit, this, {clear});
this.options.push(FI); this.options.push(FI);
this.generate(FI); this.generate(FI);
@@ -743,7 +727,7 @@ class Options implements OptionsElement<void>{
addTextInput( addTextInput(
label: string, label: string,
onSubmit: (str: string) => void, onSubmit: (str: string) => void,
{ initText = "", password = false } = {} {initText = "", password = false} = {},
) { ) {
const textInput = new TextInput(label, onSubmit, this, { const textInput = new TextInput(label, onSubmit, this, {
initText, initText,
@@ -753,30 +737,19 @@ class Options implements OptionsElement<void>{
this.generate(textInput); this.generate(textInput);
return textInput; return textInput;
} }
addColorInput( addColorInput(label: string, onSubmit: (str: string) => void, {initColor = ""} = {}) {
label: string,
onSubmit: (str: string) => void,
{ initColor = "" } = {}
){
const colorInput = new ColorInput(label, onSubmit, this, {initColor}); const colorInput = new ColorInput(label, onSubmit, this, {initColor});
this.options.push(colorInput); this.options.push(colorInput);
this.generate(colorInput); this.generate(colorInput);
return colorInput; return colorInput;
} }
addMDInput( addMDInput(label: string, onSubmit: (str: string) => void, {initText = ""} = {}) {
label: string,
onSubmit: (str: string) => void,
{ initText = "" } = {}
){
const mdInput = new MDInput(label, onSubmit, this, {initText}); const mdInput = new MDInput(label, onSubmit, this, {initText});
this.options.push(mdInput); this.options.push(mdInput);
this.generate(mdInput); this.generate(mdInput);
return mdInput; return mdInput;
} }
addHTMLArea( addHTMLArea(html: (() => HTMLElement) | HTMLElement, submit: () => void = () => {}) {
html: (() => HTMLElement) | HTMLElement,
submit: () => void = ()=>{}
){
const htmlarea = new HtmlArea(html, submit); const htmlarea = new HtmlArea(html, submit);
this.options.push(htmlarea); this.options.push(htmlarea);
this.generate(htmlarea); this.generate(htmlarea);
@@ -788,11 +761,7 @@ class Options implements OptionsElement<void>{
this.generate(button); this.generate(button);
return button; return button;
} }
addCheckboxInput( addCheckboxInput(label: string, onSubmit: (str: boolean) => void, {initState = false} = {}) {
label: string,
onSubmit: (str: boolean) => void,
{ initState = false } = {}
){
const box = new CheckboxInput(label, onSubmit, this, {initState}); const box = new CheckboxInput(label, onSubmit, this, {initState});
this.options.push(box); this.options.push(box);
this.generate(box); this.generate(box);
@@ -826,7 +795,7 @@ class Options implements OptionsElement<void>{
headers = {}, headers = {},
method = "POST", method = "POST",
traditionalSubmit = false, traditionalSubmit = false,
} = {} } = {},
) { ) {
const options = new Form(name, this, onSubmit, { const options = new Form(name, this, onSubmit, {
ltr, ltr,
@@ -898,10 +867,12 @@ class Options implements OptionsElement<void>{
return build; return build;
} }
isTop() { isTop() {
return (this.owner instanceof Options&&this.owner.subOptions!==this)|| return (
(this.owner instanceof Options && this.owner.subOptions !== this) ||
(this.owner instanceof Form && this.owner.owner.subOptions !== this.owner) || (this.owner instanceof Form && this.owner.owner.subOptions !== this.owner) ||
(this.owner instanceof Settings)|| this.owner instanceof Settings ||
(this.owner instanceof Buttons); this.owner instanceof Buttons
);
} }
generateContainter() { generateContainter() {
const container = this.container.deref(); const container = this.container.deref();
@@ -957,7 +928,7 @@ class Options implements OptionsElement<void>{
this.haschanged = true; this.haschanged = true;
this.owner.changed(div); this.owner.changed(div);
button.onclick = _=>{ button.onclick = (_) => {
if (this.owner instanceof Buttons) { if (this.owner instanceof Buttons) {
this.owner.save(); this.owner.save();
} }
@@ -1013,7 +984,7 @@ class Form implements OptionsElement<object>{
headers = {}, headers = {},
method = "POST", method = "POST",
traditionalSubmit = false, traditionalSubmit = false,
} = {} } = {},
) { ) {
this.traditionalSubmit = traditionalSubmit; this.traditionalSubmit = traditionalSubmit;
this.name = name; this.name = name;
@@ -1046,17 +1017,24 @@ class Form implements OptionsElement<object>{
headers = {}, headers = {},
method = "POST", method = "POST",
traditionalSubmit = false, traditionalSubmit = false,
} = {} } = {},
) { ) {
if (this.button && this.button.deref()) { if (this.button && this.button.deref()) {
console.warn("hidden"); console.warn("hidden");
(this.button.deref() as HTMLElement).hidden = true; (this.button.deref() as HTMLElement).hidden = true;
} }
return this.options.addSubForm(name,onSubmit,{ltr,submitText,fetchURL,headers,method,traditionalSubmit}); return this.options.addSubForm(name, onSubmit, {
ltr,
submitText,
fetchURL,
headers,
method,
traditionalSubmit,
});
} }
generateContainter() { generateContainter() {
this.options.generateContainter(); this.options.generateContainter();
if((this.options.isTop())&&this.button&&this.button.deref()){ if (this.options.isTop() && this.button && this.button.deref()) {
(this.button.deref() as HTMLElement).hidden = false; (this.button.deref() as HTMLElement).hidden = false;
} }
} }
@@ -1066,10 +1044,11 @@ class Form implements OptionsElement<object>{
formName: string, formName: string,
selections: string[], selections: string[],
{defaultIndex = 0, required = false, radio = false} = {}, {defaultIndex = 0, required = false, radio = false} = {},
correct:(string|number|null)[]=selections correct: (string | number | null)[] = selections,
) { ) {
const select = this.options.addSelect(label, _=>{}, selections, { const select = this.options.addSelect(label, (_) => {}, selections, {
defaultIndex,radio defaultIndex,
radio,
}); });
this.selectMap.set(select, correct); this.selectMap.set(select, correct);
this.names.set(formName, select); this.names.set(formName, select);
@@ -1082,11 +1061,10 @@ class Form implements OptionsElement<object>{
addFileInput( addFileInput(
label: string, label: string,
formName: string, formName: string,
{ required = false, files = "one", clear = false } = {} {required = false, files = "one", clear = false} = {},
) { ) {
const FI = this.options.addFileInput(label, _=>{}, { clear }); const FI = this.options.addFileInput(label, (_) => {}, {clear});
if(files !== "one" && files !== "multi") if (files !== "one" && files !== "multi") throw new Error("files should equal one or multi");
throw new Error("files should equal one or multi");
this.fileOptions.set(FI, {files}); this.fileOptions.set(FI, {files});
this.names.set(formName, FI); this.names.set(formName, FI);
if (required) { if (required) {
@@ -1098,9 +1076,9 @@ class Form implements OptionsElement<object>{
addTextInput( addTextInput(
label: string, label: string,
formName: string, formName: string,
{ initText = "", required = false, password = false } = {} {initText = "", required = false, password = false} = {},
) { ) {
const textInput = this.options.addTextInput(label, _=>{}, { const textInput = this.options.addTextInput(label, (_) => {}, {
initText, initText,
password, password,
}); });
@@ -1110,12 +1088,8 @@ class Form implements OptionsElement<object>{
} }
return textInput; return textInput;
} }
addColorInput( addColorInput(label: string, formName: string, {initColor = "", required = false} = {}) {
label: string, const colorInput = this.options.addColorInput(label, (_) => {}, {
formName: string,
{ initColor = "", required = false } = {}
){
const colorInput = this.options.addColorInput(label, _=>{}, {
initColor, initColor,
}); });
this.names.set(formName, colorInput); this.names.set(formName, colorInput);
@@ -1125,12 +1099,8 @@ class Form implements OptionsElement<object>{
return colorInput; return colorInput;
} }
addMDInput( addMDInput(label: string, formName: string, {initText = "", required = false} = {}) {
label: string, const mdInput = this.options.addMDInput(label, (_) => {}, {initText});
formName: string,
{ initText = "", required = false } = {}
){
const mdInput = this.options.addMDInput(label, _=>{}, { initText });
this.names.set(formName, mdInput); this.names.set(formName, mdInput);
if (required) { if (required) {
this.required.add(mdInput); this.required.add(mdInput);
@@ -1151,12 +1121,8 @@ class Form implements OptionsElement<object>{
addOptions(name: string, {ltr = false, noSubmit = false} = {}) { addOptions(name: string, {ltr = false, noSubmit = false} = {}) {
return this.options.addOptions(name, {ltr, noSubmit}); return this.options.addOptions(name, {ltr, noSubmit});
} }
addCheckboxInput( addCheckboxInput(label: string, formName: string, {initState = false, required = false} = {}) {
label: string, const box = this.options.addCheckboxInput(label, (_) => {}, {initState});
formName: string,
{ initState = false, required = false } = {}
){
const box = this.options.addCheckboxInput(label, _=>{}, { initState });
this.names.set(formName, box); this.names.set(formName, box);
if (required) { if (required) {
this.required.add(box); this.required.add(box);
@@ -1179,7 +1145,7 @@ class Form implements OptionsElement<object>{
div.classList.add("FormSettings"); div.classList.add("FormSettings");
if (!this.traditionalSubmit) { if (!this.traditionalSubmit) {
const button = document.createElement("button"); const button = document.createElement("button");
button.onclick = _=>{ button.onclick = (_) => {
this.submit(); this.submit();
}; };
button.textContent = this.submitText; button.textContent = this.submitText;
@@ -1191,7 +1157,9 @@ class Form implements OptionsElement<object>{
} }
return div; return div;
} }
onSubmit: ((arg1: object,sent:object) => void )|((arg1: object,sent:object) => Promise<void> ); onSubmit:
| ((arg1: object, sent: object) => void)
| ((arg1: object, sent: object) => Promise<void>);
watchForChange(func: (arg1: object) => void) { watchForChange(func: (arg1: object) => void) {
this.onSubmit = func; this.onSubmit = func;
} }
@@ -1244,7 +1212,7 @@ class Form implements OptionsElement<object>{
const options = this.fileOptions.get(input); const options = this.fileOptions.get(input);
if (!options) { if (!options) {
throw new Error( throw new Error(
"FileInput without its options is in this form, this should never happen." "FileInput without its options is in this form, this should never happen.",
); );
} }
if (options.files === "one") { if (options.files === "one") {
@@ -1252,7 +1220,7 @@ class Form implements OptionsElement<object>{
if (input.value) { if (input.value) {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(input.value[0]); reader.readAsDataURL(input.value[0]);
const promise = new Promise<void>(res=>{ const promise = new Promise<void>((res) => {
reader.onload = () => { reader.onload = () => {
(build as any)[thing] = reader.result; (build as any)[thing] = reader.result;
res(); res();
@@ -1288,13 +1256,14 @@ class Form implements OptionsElement<object>{
body: JSON.stringify(build), body: JSON.stringify(build),
headers: this.headers, headers: this.headers,
}) })
.then(_=>{ .then((_) => {
return _.text() return _.text();
}).then(_=>{
if(_==="") return {};
return JSON.parse(_)
}) })
.then(async json=>{ .then((_) => {
if (_ === "") return {};
return JSON.parse(_);
})
.then(async (json) => {
if (json.errors) { if (json.errors) {
if (this.errors(json)) { if (this.errors(json)) {
return; return;
@@ -1334,7 +1303,11 @@ class Form implements OptionsElement<object>{
} }
console.warn("needs to be implemented"); console.warn("needs to be implemented");
} }
errors(errors: {code: number; message: string; errors: { [key: string]: { _errors: { message: string; code: string }[] } }}){ errors(errors: {
code: number;
message: string;
errors: {[key: string]: {_errors: {message: string; code: string}[]}};
}) {
if (!(errors instanceof Object)) { if (!(errors instanceof Object)) {
return; return;
} }
@@ -1375,7 +1348,7 @@ class Form implements OptionsElement<object>{
element = div; element = div;
} else { } else {
element.classList.remove("suberror"); element.classList.remove("suberror");
setTimeout(_=>{ setTimeout((_) => {
element.classList.add("suberror"); element.classList.add("suberror");
}, 100); }, 100);
} }
@@ -1388,8 +1361,8 @@ class HorrizonalRule implements OptionsElement<unknown>{
return document.createElement("hr"); return document.createElement("hr");
} }
watchForChange(_: (arg1: undefined) => void) { watchForChange(_: (arg1: undefined) => void) {
throw new Error("don't do this") throw new Error("don't do this");
}; }
submit = () => {}; submit = () => {};
value = undefined; value = undefined;
} }
@@ -1419,7 +1392,7 @@ class Settings extends Buttons{
const exit = document.createElement("span"); const exit = document.createElement("span");
exit.classList.add("exitsettings", "svgicon", "svg-x"); exit.classList.add("exitsettings", "svgicon", "svg-x");
background.append(exit); background.append(exit);
exit.onclick = _=>{ exit.onclick = (_) => {
this.hide(); this.hide();
}; };
document.body.append(background); document.body.append(background);

View File

@@ -22,22 +22,23 @@ body {
flex-direction: column; flex-direction: column;
} }
.searchOptions { .searchOptions {
padding:.05in .15in; padding: 0.05in 0.15in;
border-radius: .1in; border-radius: 0.1in;
background: var(--channels-bg); background: var(--channels-bg);
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: calc(100% - 32px); width: calc(100% - 32px);
box-sizing: border-box; box-sizing: border-box;
> span { > span {
transition: background .1s; transition: background 0.1s;
margin-bottom:.025in; margin-bottom: 0.025in;
margin-top:.025in; margin-top: 0.025in;
padding:.075in .05in; padding: 0.075in 0.05in;
border-radius:.03in; border-radius: 0.03in;
cursor: pointer; cursor: pointer;
> span, img{ > span,
margin-right:.05in; img {
margin-right: 0.05in;
} }
} }
span.selected { span.selected {
@@ -47,7 +48,7 @@ body {
background: var(--button-bg); background: var(--button-bg);
} }
margin: 16px; margin: 16px;
border: solid .025in var(--black); border: solid 0.025in var(--black);
} }
.searchOptions:empty { .searchOptions:empty {
padding: 0; padding: 0;
@@ -62,10 +63,15 @@ body {
min-height: 0; min-height: 0;
} }
.channelSTitle { .channelSTitle {
margin-top:.2in; margin-top: 0.2in;
margin-bottom: 0; margin-bottom: 0;
} }
p, h1, h2, h3, pre, form { p,
h1,
h2,
h3,
pre,
form {
margin: 0; margin: 0;
} }
h2:empty { h2:empty {
@@ -82,13 +88,15 @@ h2:empty {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
} }
a, .clickable { a,
.clickable {
color: var(--link); color: var(--link);
text-decoration: none; text-decoration: none;
word-break: break-word; word-break: break-word;
cursor: pointer; cursor: pointer;
} }
a:hover, .clickable:hover { a:hover,
.clickable:hover {
text-decoration: underline; text-decoration: underline;
} }
.clickable { .clickable {
@@ -99,20 +107,24 @@ hr {
background: var(--divider); background: var(--divider);
border: none; border: none;
} }
pre, samp { pre,
samp {
background: var(--code-bg); background: var(--code-bg);
color: var(--code-text); color: var(--code-text);
text-wrap: wrap; text-wrap: wrap;
word-break: break-word; word-break: break-word;
} }
video, iframe { video,
iframe {
max-height: 50svh; max-height: 50svh;
max-width: 100%; max-width: 100%;
} }
audio::-webkit-media-controls-panel { audio::-webkit-media-controls-panel {
background: var(--secondary-bg); background: var(--secondary-bg);
} }
button, input::file-selector-button, select { button,
input::file-selector-button,
select {
padding: 6px 10px; padding: 6px 10px;
background: var(--button-bg); background: var(--button-bg);
font-size: 1rem; font-size: 1rem;
@@ -122,15 +134,17 @@ button, input::file-selector-button, select {
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
transition: background .1s ease-in-out; transition: background 0.1s ease-in-out;
} }
input::file-selector-button { input::file-selector-button {
margin-right: 6px; margin-right: 6px;
} }
button:hover, input::file-selector-button:hover { button:hover,
input::file-selector-button:hover {
background: var(--button-hover); background: var(--button-hover);
} }
button:active:enabled, input::file-selector-button:active { button:active:enabled,
input::file-selector-button:active {
border: none; border: none;
} }
button:disabled { button:disabled {
@@ -206,7 +220,8 @@ textarea {
.pfpDiv { .pfpDiv {
position: relative; position: relative;
} }
.pfp, .replypfp { .pfp,
.replypfp {
display: block; display: block;
height: 32px; height: 32px;
width: 32px; width: 32px;
@@ -255,7 +270,7 @@ textarea {
} }
.svg-upload { .svg-upload {
mask: url(/icons/upload.svg); mask: url(/icons/upload.svg);
width: .2in !important; width: 0.2in !important;
cursor: pointer; cursor: pointer;
} }
.svg-x { .svg-x {
@@ -301,16 +316,16 @@ textarea {
.hoverthing { .hoverthing {
position: absolute; position: absolute;
background: var(--dock-bg); background: var(--dock-bg);
padding:.04in; padding: 0.04in;
border-radius:.05in; border-radius: 0.05in;
transform: translate(-50%, 0); transform: translate(-50%, 0);
transition: opacity .2s; transition: opacity 0.2s;
border: solid .03in var(--black); border: solid 0.03in var(--black);
} }
.editMessage { .editMessage {
background: var(--typebox-bg); background: var(--typebox-bg);
padding: .05in; padding: 0.05in;
border-radius: .04in; border-radius: 0.04in;
} }
#gimmefile { #gimmefile {
position: absolute; position: absolute;
@@ -321,15 +336,17 @@ textarea {
} }
/* Animations */ /* Animations */
@keyframes fade { @keyframes fade {
0%, 100% { 0%,
opacity: .2; 100% {
opacity: 0.2;
} }
50% { 50% {
opacity: 1; opacity: 1;
} }
} }
@keyframes jumped { @keyframes jumped {
0%, 100% { 0%,
100% {
background: transparent; background: transparent;
} }
50% { 50% {
@@ -361,10 +378,14 @@ textarea {
width: 0px; width: 0px;
padding: 0; padding: 0;
} }
#servers::-webkit-scrollbar, #channels::-webkit-scrollbar, #sideDiv::-webkit-scrollbar { #servers::-webkit-scrollbar,
#channels::-webkit-scrollbar,
#sideDiv::-webkit-scrollbar {
display: none; display: none;
} }
#servers, #channels, #sideDiv { #servers,
#channels,
#sideDiv {
scrollbar-width: none; scrollbar-width: none;
} }
@@ -372,7 +393,7 @@ textarea {
#titleDiv { #titleDiv {
padding: 8px; padding: 8px;
background: var(--primary-bg); background: var(--primary-bg);
font-size: .8rem; font-size: 0.8rem;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -429,11 +450,11 @@ h1.pagehead {
} }
.instance span { .instance span {
margin-bottom: 4px; margin-bottom: 4px;
font-size: .9rem; font-size: 0.9rem;
color: var(--secondary-text-soft); color: var(--secondary-text-soft);
} }
span.instanceStatus { span.instanceStatus {
font-size: .75rem; font-size: 0.75rem;
} }
.instancetextbox h2 { .instancetextbox h2 {
margin-bottom: 4px; margin-bottom: 4px;
@@ -442,7 +463,8 @@ span.instanceStatus {
} }
/* Login/Invite */ /* Login/Invite */
#logindiv, #invitebody { #logindiv,
#invitebody {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
@@ -456,7 +478,8 @@ span.instanceStatus {
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; overflow-y: auto;
} }
#logindiv label, #TOSbox { #logindiv label,
#TOSbox {
display: inline-block; display: inline-block;
margin-top: 12px; margin-top: 12px;
} }
@@ -534,7 +557,7 @@ span.instanceStatus {
background: var(--loading-bg, inherit) !important; background: var(--loading-bg, inherit) !important;
color: var(--loading-text); color: var(--loading-text);
text-align: center; text-align: center;
transition: transform .2s; transition: transform 0.2s;
overflow: hidden; /* keep size if window height is too small */ overflow: hidden; /* keep size if window height is too small */
} }
#loading.doneloading { #loading.doneloading {
@@ -581,7 +604,8 @@ span.instanceStatus {
overflow-y: auto; overflow-y: auto;
user-select: none; user-select: none;
} }
.servericon, #sentdms .pfp { .servericon,
#sentdms .pfp {
height: 48px; height: 48px;
width: 48px; width: 48px;
margin-bottom: 6px; margin-bottom: 6px;
@@ -591,9 +615,11 @@ span.instanceStatus {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: border-radius .2s; transition: border-radius 0.2s;
} }
.servericon:hover, .serveropen .servericon, #sentdms .pfp:hover { .servericon:hover,
.serveropen .servericon,
#sentdms .pfp:hover {
border-radius: 30%; border-radius: 30%;
} }
.home .svgicon { .home .svgicon {
@@ -610,10 +636,12 @@ span.instanceStatus {
.lightbr { .lightbr {
margin: 8px 4px; margin: 8px 4px;
} }
.blankserver, .home { .blankserver,
.home {
background: var(--blank-bg); background: var(--blank-bg);
} }
.servernoti, .home { .servernoti,
.home {
position: relative; position: relative;
} }
.unread { .unread {
@@ -623,7 +651,9 @@ span.instanceStatus {
width: 8px; width: 8px;
background: var(--primary-text); background: var(--primary-text);
border-radius: 4px; border-radius: 4px;
transition: transform .2s, height .2s; transition:
transform 0.2s,
height 0.2s;
} }
.servernoti:hover .unread.pinged { .servernoti:hover .unread.pinged {
transform: translate(34px, 14px); transform: translate(34px, 14px);
@@ -637,7 +667,6 @@ span.instanceStatus {
transform: translate(-12px, 8px) !important; transform: translate(-12px, 8px) !important;
height: 32px !important; height: 32px !important;
width: 8px !important; width: 8px !important;
} }
.serveropen .unread.pinged { .serveropen .unread.pinged {
color: transparent; color: transparent;
@@ -653,7 +682,7 @@ span.instanceStatus {
width: 16px; width: 16px;
transform: translate(34px, 34px); transform: translate(34px, 34px);
background: var(--red); background: var(--red);
font-size: .75rem; font-size: 0.75rem;
font-weight: bold; font-weight: bold;
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
@@ -690,7 +719,7 @@ span.instanceStatus {
} }
.channels { .channels {
overflow-y: hidden; overflow-y: hidden;
transition: height .2s ease-in-out; transition: height 0.2s ease-in-out;
} }
#channels > div > div:first-child { #channels > div > div:first-child {
margin-top: 6px; margin-top: 6px;
@@ -707,7 +736,7 @@ span.instanceStatus {
color: var(--primary-text-soft); color: var(--primary-text-soft);
display: flex; display: flex;
align-items: center; align-items: center;
transition: font-weight .1s; transition: font-weight 0.1s;
} }
.channelbutton:hover { .channelbutton:hover {
background: var(--channel-hover); background: var(--channel-hover);
@@ -716,7 +745,8 @@ span.instanceStatus {
.channels .channelbutton { .channels .channelbutton {
margin-left: 8px; margin-left: 8px;
} }
.viewChannel .channelbutton, .viewChannel .channelbutton:hover { .viewChannel .channelbutton,
.viewChannel .channelbutton:hover {
background: var(--channel-selected); background: var(--channel-selected);
font-weight: bold; font-weight: bold;
color: var(--primary-text-prominent); color: var(--primary-text-prominent);
@@ -726,7 +756,7 @@ span.instanceStatus {
color: var(--primary-text-prominent); color: var(--primary-text-prominent);
} }
.cunread:after { .cunread:after {
content: ''; content: "";
position: absolute; position: absolute;
top: calc(50% - 4px); top: calc(50% - 4px);
left: -10px; left: -10px;
@@ -764,7 +794,7 @@ span.instanceStatus {
height: 12px; height: 12px;
width: 12px; width: 12px;
margin-right: 6px; margin-right: 6px;
transition: rotate .2s; transition: rotate 0.2s;
} }
.hiddencat { .hiddencat {
rotate: -90deg; rotate: -90deg;
@@ -780,7 +810,7 @@ span.instanceStatus {
.voiceuser { .voiceuser {
margin-left: 32px; margin-left: 32px;
padding: 4px 0; padding: 4px 0;
font-size: .9rem; font-size: 0.9rem;
} }
/* Member Info (DM/Member List) */ /* Member Info (DM/Member List) */
@@ -841,7 +871,7 @@ span.instanceStatus {
justify-content: center; justify-content: center;
} }
#status { #status {
font-size: .8em; font-size: 0.8em;
} }
#user-actions { #user-actions {
border-radius: 50%; border-radius: 50%;
@@ -867,10 +897,10 @@ span.instanceStatus {
} }
#channelTopic { #channelTopic {
margin: auto 0 0 8px; margin: auto 0 0 8px;
font-size: .9em; font-size: 0.9em;
color: var(--primary-text-soft); color: var(--primary-text-soft);
button { button {
margin-right:.05in; margin-right: 0.05in;
} }
} }
#channelTopic[hidden] { #channelTopic[hidden] {
@@ -925,13 +955,13 @@ span.instanceStatus {
padding: 0 10px 0 16px; padding: 0 10px 0 16px;
margin: 0 16px; margin: 0 16px;
background: var(--secondary-bg); background: var(--secondary-bg);
font-size: .9em; font-size: 0.9em;
color: var(--secondary-text); color: var(--secondary-text);
border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
transition: height .2s; transition: height 0.2s;
} }
.cancelReply { .cancelReply {
height: 16px; height: 16px;
@@ -950,7 +980,7 @@ span.instanceStatus {
#typebox { #typebox {
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
margin-left: .06in; margin-left: 0.06in;
} }
.outerTypeBox { .outerTypeBox {
max-height: 50svh; max-height: 50svh;
@@ -967,7 +997,7 @@ span.instanceStatus {
} }
.searchBox { .searchBox {
white-space: nowrap; white-space: nowrap;
height: .075in; height: 0.075in;
padding: 7px 10px 13px 10px; padding: 7px 10px 13px 10px;
background: var(--dock-bg); background: var(--dock-bg);
border-radius: 4px; border-radius: 4px;
@@ -975,21 +1005,21 @@ span.instanceStatus {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 3in; width: 3in;
margin: 0 .1in; margin: 0 0.1in;
overflow: hidden; overflow: hidden;
margin-left: auto; margin-left: auto;
flex-shrink: 0; flex-shrink: 0;
transition: width .2s; transition: width 0.2s;
} }
.outerTypeBox > span::before { .outerTypeBox > span::before {
content: "\feff"; content: "\feff";
} }
#typebox[contenteditable=false] { #typebox[contenteditable="false"] {
cursor: not-allowed; cursor: not-allowed;
} }
#typebox[contenteditable=false]:before { #typebox[contenteditable="false"]:before {
content: "You can't send messages here"; content: "You can't send messages here";
opacity: .5; opacity: 0.5;
} }
#typebox.typeboxreplying { #typebox.typeboxreplying {
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
@@ -1001,7 +1031,7 @@ span.instanceStatus {
position: absolute; position: absolute;
bottom: 7px; bottom: 7px;
margin-left: 24px; margin-left: 24px;
font-size: .9em; font-size: 0.9em;
gap: 4px; gap: 4px;
} }
#typing.hidden { #typing.hidden {
@@ -1020,14 +1050,15 @@ span.instanceStatus {
margin-right: 3px; margin-right: 3px;
} }
.dot:nth-child(2) { .dot:nth-child(2) {
animation-delay: .33s; animation-delay: 0.33s;
} }
.dot:nth-child(3) { .dot:nth-child(3) {
animation-delay: .66s; animation-delay: 0.66s;
} }
/* Message */ /* Message */
.messagediv, .titlespace { .messagediv,
.titlespace {
padding: 3px 36px 3px 16px; padding: 3px 36px 3px 16px;
border-left: 2px solid transparent; border-left: 2px solid transparent;
} }
@@ -1037,32 +1068,39 @@ span.instanceStatus {
.messagediv:hover { .messagediv:hover {
background: var(--primary-hover); background: var(--primary-hover);
} }
.messageButtons, .controls { .messageButtons,
.controls {
position: absolute; position: absolute;
top: -16px; top: -16px;
right: 16px; right: 16px;
background: var(--secondary-bg); background: var(--secondary-bg);
box-shadow: 0 0 4px var(--shadow), 0 0 2px var(--shadow); box-shadow:
0 0 4px var(--shadow),
0 0 2px var(--shadow);
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
.messageButtons button, .controls button { .messageButtons button,
.controls button {
padding: 8px; padding: 8px;
background: transparent; background: transparent;
border-radius: 0; border-radius: 0;
cursor: pointer; cursor: pointer;
transition: none; transition: none;
} }
.messageButtons button span, .controls button span { .messageButtons button span,
.controls button span {
display: block; display: block;
height: 16px; height: 16px;
width: 16px; width: 16px;
background: var(--secondary-text-soft); background: var(--secondary-text-soft);
} }
.messageButtons button:hover, .controls button:hover { .messageButtons button:hover,
.controls button:hover {
background: var(--secondary-hover); background: var(--secondary-hover);
} }
.messageButtons button:hover span, .controls button:hover span { .messageButtons button:hover span,
.controls button:hover span {
background: var(--secondary-text); background: var(--secondary-text);
} }
.controls { .controls {
@@ -1119,7 +1157,7 @@ span.instanceStatus {
margin-left: 4px; margin-left: 4px;
vertical-align: 1px; vertical-align: 1px;
background: color-mix(in srgb, var(--accent-color) 75%, transparent); background: color-mix(in srgb, var(--accent-color) 75%, transparent);
font-size: .75em; font-size: 0.75em;
font-weight: bold; font-weight: bold;
color: var(--primary-text-prominent); color: var(--primary-text-prominent);
border-radius: 4px; border-radius: 4px;
@@ -1149,7 +1187,7 @@ span.instanceStatus {
} }
.timestamp { .timestamp {
margin-left: 6px; margin-left: 6px;
font-size: .75em; font-size: 0.75em;
color: var(--primary-text-soft); color: var(--primary-text-soft);
} }
.spoiler { .spoiler {
@@ -1190,13 +1228,13 @@ span .quote:last-of-type .quoteline {
position: relative; position: relative;
padding-left: 52px; padding-left: 52px;
margin-bottom: 4px; margin-bottom: 4px;
font-size: .9em; font-size: 0.9em;
color: var(--reply-text); color: var(--reply-text);
align-items: center; align-items: center;
gap: 4px; gap: 4px;
} }
.replyflex::before { .replyflex::before {
content: ''; content: "";
position: absolute; position: absolute;
top: calc(50% - 1px); top: calc(50% - 1px);
left: 19px; left: 19px;
@@ -1309,7 +1347,7 @@ span .quote:last-of-type .quoteline {
max-width: 400px; max-width: 400px;
padding: 12px; padding: 12px;
background: var(--embed-bg); background: var(--embed-bg);
font-size: .88em; font-size: 0.88em;
color: var(--secondary-text); color: var(--secondary-text);
border-radius: 4px; border-radius: 4px;
} }
@@ -1406,7 +1444,8 @@ img.bigembedimg {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
} }
.acceptinvbutton:hover, .acceptinvbutton:disabled { .acceptinvbutton:hover,
.acceptinvbutton:disabled {
background: color-mix(in hsl, var(--green) 80%, var(--black)); background: color-mix(in hsl, var(--green) 80%, var(--black));
} }
#sideDiv.searchDiv { #sideDiv.searchDiv {
@@ -1415,13 +1454,12 @@ img.bigembedimg {
margin-top: 2px; margin-top: 2px;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer; cursor: pointer;
padding:.05in; padding: 0.05in;
border-radius:.075in; border-radius: 0.075in;
background: #00000020; background: #00000020;
} }
.topMessage:hover { .topMessage:hover {
background: #00000050; background: #00000050;
} }
} }
/* Sidebar */ /* Sidebar */
@@ -1447,7 +1485,7 @@ img.bigembedimg {
background: var(--sidebar-hover); background: var(--sidebar-hover);
} }
.memberList.offline .liststyle { .memberList.offline .liststyle {
opacity: .5; opacity: 0.5;
} }
#memberlisttoggle { #memberlisttoggle {
display: none; display: none;
@@ -1482,7 +1520,7 @@ img.bigembedimg {
width: 180px; width: 180px;
padding: 8px; padding: 8px;
background: transparent; background: transparent;
font-size: .9rem; font-size: 0.9rem;
line-height: 1em; line-height: 1em;
color: var(--secondary-text); color: var(--secondary-text);
border: none; border: none;
@@ -1502,7 +1540,9 @@ img.bigembedimg {
width: 300px; width: 300px;
max-height: 100svh; max-height: 100svh;
background: var(--card-bg); background: var(--card-bg);
box-shadow: 0 0 8px var(--shadow), inset 0 132px 64px var(--accent_color); box-shadow:
0 0 8px var(--shadow),
inset 0 132px 64px var(--accent_color);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
align-items: flex-start; align-items: flex-start;
@@ -1557,7 +1597,8 @@ img.bigembedimg {
.profile:has(.banner) .infosection { .profile:has(.banner) .infosection {
margin-top: 8px; margin-top: 8px;
} }
.infosection h2, .infosection h3 { .infosection h2,
.infosection h3 {
word-break: break-word; word-break: break-word;
} }
.infosection hr { .infosection hr {
@@ -1565,11 +1606,11 @@ img.bigembedimg {
} }
.tag { .tag {
margin: 4px 0; margin: 4px 0;
font-size: .9em; font-size: 0.9em;
color: var(--secondary-text); color: var(--secondary-text);
} }
.pronouns { .pronouns {
font-size: .9em; font-size: 0.9em;
color: var(--secondary-text-soft); color: var(--secondary-text-soft);
} }
.rolesbox { .rolesbox {
@@ -1583,7 +1624,7 @@ img.bigembedimg {
padding: 4px 6px; padding: 4px 6px;
background: var(--role-bg); background: var(--role-bg);
color: var(--role-text); color: var(--role-text);
font-size: .9em; font-size: 0.9em;
border: 1px solid var(--black); border: 1px solid var(--black);
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@@ -1634,7 +1675,8 @@ img.bigembedimg {
.emojiSelect:hover { .emojiSelect:hover {
background: var(--secondary-hover); background: var(--secondary-hover);
} }
.emoji-server, .emojiBody .smallemoji { .emoji-server,
.emojiBody .smallemoji {
height: auto; height: auto;
width: auto; width: auto;
max-height: 28px; max-height: 28px;
@@ -1664,13 +1706,15 @@ img.bigembedimg {
max-height: 85svh; max-height: 85svh;
max-width: 85svw; max-width: 85svw;
} }
.centeritem, .accountSwitcher { .centeritem,
.accountSwitcher {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.nonimagecenter, .accountSwitcher { .nonimagecenter,
.accountSwitcher {
max-height: 80svh; max-height: 80svh;
width: 500px; width: 500px;
padding: 12px; padding: 12px;
@@ -1678,16 +1722,20 @@ img.bigembedimg {
background: var(--secondary-bg); background: var(--secondary-bg);
border: none; border: none;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 0 24px var(--shadow), 0 0 1.5px var(--primary-text); box-shadow:
0 0 24px var(--shadow),
0 0 1.5px var(--primary-text);
box-sizing: border-box; box-sizing: border-box;
gap: 8px; gap: 8px;
overflow-y: auto; overflow-y: auto;
} }
.nonimagecenter & .flexttb, .nonimagecenter & .flexltr { .nonimagecenter & .flexttb,
.nonimagecenter & .flexltr {
flex: 1; flex: 1;
gap: 8px; gap: 8px;
} }
.nonimagecenter > .flexttb, .nonimagecenter > .flexltr { .nonimagecenter > .flexttb,
.nonimagecenter > .flexltr {
padding: 0 0 16px 0 !important; padding: 0 0 16px 0 !important;
background: var(--primary-bg); background: var(--primary-bg);
border-radius: 8px; border-radius: 8px;
@@ -1696,7 +1744,8 @@ img.bigembedimg {
padding-bottom: 0; padding-bottom: 0;
border-bottom: none; border-bottom: none;
} }
.nonimagecenter .flexspace, .nonimagecenter .FormSettings { .nonimagecenter .flexspace,
.nonimagecenter .FormSettings {
padding: 0; padding: 0;
} }
.nonimagecenter button { .nonimagecenter button {
@@ -1725,7 +1774,7 @@ fieldset input[type="radio"] {
margin-right: 8px; margin-right: 8px;
} }
.serverURL { .serverURL {
font-size: .9em; font-size: 0.9em;
color: var(--primary-text-soft); color: var(--primary-text-soft);
} }
.accountSwitcher { .accountSwitcher {
@@ -1811,7 +1860,7 @@ fieldset input[type="radio"] {
} }
.discovery-guild p { .discovery-guild p {
margin: 0 12px; margin: 0 12px;
font-size: .9em; font-size: 0.9em;
color: var(--secondary-text-soft); color: var(--secondary-text-soft);
word-break: break-word; word-break: break-word;
} }
@@ -1839,7 +1888,7 @@ fieldset input[type="radio"] {
.settingbuttons.flexltr { .settingbuttons.flexltr {
width: 100%; width: 100%;
border-right: 0; border-right: 0;
border-radius: .04in; border-radius: 0.04in;
.SettingsButton { .SettingsButton {
width: auto; width: auto;
} }
@@ -1890,7 +1939,8 @@ fieldset input[type="radio"] {
.FormSettings { .FormSettings {
padding-bottom: 32px; padding-bottom: 32px;
} }
.optionElement, .FormSettings > button { .optionElement,
.FormSettings > button {
margin: 16px 16px 0 16px; margin: 16px 16px 0 16px;
word-break: break-word; word-break: break-word;
overflow: hidden; overflow: hidden;
@@ -1917,7 +1967,8 @@ fieldset input[type="radio"] {
.optionElement .fileinputdiv { .optionElement .fileinputdiv {
width: 500px; width: 500px;
} }
#app-list-container div, #connection-container div { #app-list-container div,
#connection-container div {
max-width: 500px; max-width: 500px;
padding: 8px; padding: 8px;
margin-top: 12px; margin-top: 12px;
@@ -1958,8 +2009,8 @@ fieldset input[type="radio"] {
font-size: 1.15rem; font-size: 1.15rem;
} }
.setting p { .setting p {
font-size: .9em; font-size: 0.9em;
color: var(--secondary-text-soft) color: var(--secondary-text-soft);
} }
.tritoggle { .tritoggle {
display: inline; display: inline;
@@ -1986,7 +2037,9 @@ fieldset input[type="radio"] {
color: var(--secondary-text); color: var(--secondary-text);
border-radius: 8px; border-radius: 8px;
align-items: center; align-items: center;
box-shadow: 0 0 24px var(--shadow), 0 0 1px var(--primary-text); box-shadow:
0 0 24px var(--shadow),
0 0 1px var(--primary-text);
box-sizing: border-box; box-sizing: border-box;
} }
.savediv button { .savediv button {
@@ -2006,7 +2059,8 @@ fieldset input[type="radio"] {
} }
/* Jank Mobile */ /* Jank Mobile */
#maintoggle, #maintoggleicon { #maintoggle,
#maintoggleicon {
display: none; display: none;
} }
@@ -2035,7 +2089,9 @@ fieldset input[type="radio"] {
display: block; display: block;
position: fixed; position: fixed;
} }
#servers, .channelflex, #mainarea { #servers,
.channelflex,
#mainarea {
position: absolute; position: absolute;
height: 100svh; height: 100svh;
} }
@@ -2046,18 +2102,19 @@ fieldset input[type="radio"] {
left: 304px; left: 304px;
width: 100svw; width: 100svw;
background: var(--primary-bg); background: var(--primary-bg);
transition: left .3s; transition: left 0.3s;
} }
#sideDiv { #sideDiv {
display: block; display: block;
right: -100svw; right: -100svw;
width: 100svw; width: 100svw;
transition: right .3s; transition: right 0.3s;
} }
.channelnamediv { .channelnamediv {
padding: 0; padding: 0;
} }
#maintoggleicon, #memberlisttoggleicon { #maintoggleicon,
#memberlisttoggleicon {
display: block; display: block;
padding: 12px; padding: 12px;
cursor: pointer; cursor: pointer;
@@ -2102,10 +2159,13 @@ fieldset input[type="radio"] {
left: 15px; left: 15px;
width: 24px; width: 24px;
} }
.replyflex, .reactiondiv { .replyflex,
.reactiondiv {
padding-left: 44px; padding-left: 44px;
} }
#realbox, #logindiv, #pasteimg { #realbox,
#logindiv,
#pasteimg {
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
} }
@@ -2113,13 +2173,17 @@ fieldset input[type="radio"] {
margin-left: 12px; margin-left: 12px;
margin-right: 12px; margin-right: 12px;
} }
.contextmenu, .profile, .emojiPicker { .contextmenu,
.profile,
.emojiPicker {
top: unset !important; top: unset !important;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border-radius: 16px 16px 0 0; border-radius: 16px 16px 0 0;
box-shadow: 0 0 14px var(--shadow), 0 0 28px var(--shadow); box-shadow:
0 0 14px var(--shadow),
0 0 28px var(--shadow);
} }
.contextbutton { .contextbutton {
width: 100%; width: 100%;
@@ -2127,12 +2191,19 @@ fieldset input[type="radio"] {
} }
.profile { .profile {
height: 65%; height: 65%;
box-shadow: 0 0 14px var(--shadow), 0 0 28px var(--shadow), inset 0 132px 64px var(--accent_color); box-shadow:
0 0 14px var(--shadow),
0 0 28px var(--shadow),
inset 0 132px 64px var(--accent_color);
} }
.hypoprofile { .hypoprofile {
border-radius: 8px; border-radius: 8px;
} }
#logindiv, #invitebody, .savediv, .nonimagecenter, .accountSwitcher { #logindiv,
#invitebody,
.savediv,
.nonimagecenter,
.accountSwitcher {
width: 94%; width: 94%;
min-width: unset; min-width: unset;
} }
@@ -2162,41 +2233,40 @@ fieldset input[type="radio"] {
.friendcontainer { .friendcontainer {
display: flex; display: flex;
width: 100%; width: 100%;
padding: .2in; padding: 0.2in;
> div { > div {
background: #00000030; background: #00000030;
margin-bottom:.1in; margin-bottom: 0.1in;
padding:.06in .1in; padding: 0.06in 0.1in;
border-radius:.1in; border-radius: 0.1in;
border: solid 1px var(--black); border: solid 1px var(--black);
} }
} }
.fixedsearch { .fixedsearch {
position: absolute; position: absolute;
background: var(--primary-bg); background: var(--primary-bg);
min-height: .2in; min-height: 0.2in;
padding:.05in; padding: 0.05in;
border:solid .03in var(--black); border: solid 0.03in var(--black);
border-radius:.05in; border-radius: 0.05in;
span { span {
margin-top:.1in; margin-top: 0.1in;
width: 100%; width: 100%;
padding:.03in; padding: 0.03in;
border:solid .03in var(--black); border: solid 0.03in var(--black);
box-sizing: border-box; box-sizing: border-box;
border-radius:.05in; border-radius: 0.05in;
cursor: pointer; cursor: pointer;
} }
} }
.suberror { .suberror {
animation: goout 6s forwards; animation: goout 6s forwards;
} }
.suberrora { .suberrora {
background: var(--channel-hover); background: var(--channel-hover);
border-radius:.1in; border-radius: 0.1in;
position: absolute; position: absolute;
border:solid var(--primary-text) .02in; border: solid var(--primary-text) 0.02in;
color: color-mix(in hsl, var(--yellow), var(--red)); color: color-mix(in hsl, var(--yellow), var(--red));
font-weight: bold; font-weight: bold;
opacity: 0; opacity: 0;
@@ -2206,37 +2276,39 @@ fieldset input[type="radio"] {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
justify-content: space-evenly; justify-content: space-evenly;
padding: .075in; padding: 0.075in;
box-sizing: border-box; box-sizing: border-box;
pointer-events: none; pointer-events: none;
} }
@keyframes goout { @keyframes goout {
0%,100%{ 0%,
100% {
opacity: 0; opacity: 0;
} }
5%,90%{ 5%,
90% {
opacity: 1; opacity: 1;
} }
} }
.friendsbutton { .friendsbutton {
transition: background-color .2s; transition: background-color 0.2s;
background-color: #00000050; background-color: #00000050;
padding: .08in; padding: 0.08in;
} }
.bigemoji { .bigemoji {
width:.6in; width: 0.6in;
object-fit: contain; object-fit: contain;
height: .6in; height: 0.6in;
} }
.friendlyButton { .friendlyButton {
padding: .07in; padding: 0.07in;
background: #00000045; background: #00000045;
transition:background .2s; transition: background 0.2s;
border-radius: 1in; border-radius: 1in;
border: solid 1px var(--black); border: solid 1px var(--black);
width: 24px; width: 24px;
height: 24px; height: 24px;
margin: 0 .05in; margin: 0 0.05in;
} }
.friendlyButton:hover { .friendlyButton:hover {
background: black; background: black;
@@ -2245,23 +2317,23 @@ fieldset input[type="radio"] {
border: solid 1px var(--black); border: solid 1px var(--black);
display: flex; display: flex;
align-items: center; align-items: center;
padding: .075in; padding: 0.075in;
margin-bottom: .2in; margin-bottom: 0.2in;
border-radius: .1in; border-radius: 0.1in;
background: var(--primary-hover); background: var(--primary-hover);
position: relative; position: relative;
input { input {
width: 2in !important; width: 2in !important;
height:.3in; height: 0.3in;
} }
.bigemoji { .bigemoji {
padding-right:.5in; padding-right: 0.5in;
} }
} }
.deleteEmoji { .deleteEmoji {
width: .3in; width: 0.3in;
height: .3in; height: 0.3in;
position: absolute; position: absolute;
right: .2in; right: 0.2in;
cursor: pointer; cursor: pointer;
} }

View File

@@ -29,8 +29,7 @@ class User extends SnowFlake{
premium_type!: number; premium_type!: number;
theme_colors!: string; theme_colors!: string;
badge_ids!: string[]; badge_ids!: string[];
members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> = members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> = new WeakMap();
new WeakMap();
status!: string; status!: string;
resolving: false | Promise<any> = false; resolving: false | Promise<any> = false;
@@ -38,7 +37,7 @@ class User extends SnowFlake{
super(userjson.id); super(userjson.id);
this.owner = owner; this.owner = owner;
if (localStorage.getItem("logbad") && owner.user && owner.user.id !== userjson.id) { if (localStorage.getItem("logbad") && owner.user && owner.user.id !== userjson.id) {
this.checkfortmi(userjson) this.checkfortmi(userjson);
} }
if (!owner) { if (!owner) {
console.error("missing localuser"); console.error("missing localuser");
@@ -66,9 +65,18 @@ class User extends SnowFlake{
*/ */
checkfortmi(json: any) { checkfortmi(json: any) {
if (json.data) { if (json.data) {
console.error("Server sent *way* too much info, this is really bad, it sent data") console.error("Server sent *way* too much info, this is really bad, it sent data");
} }
const bad=new Set(["fingerprints", "extended_settings", "mfa_enabled", "nsfw_allowed", "premium_usage_flags", "totp_last_ticket", "totp_secret", "webauthn_enabled"]); const bad = new Set([
"fingerprints",
"extended_settings",
"mfa_enabled",
"nsfw_allowed",
"premium_usage_flags",
"totp_last_ticket",
"totp_secret",
"webauthn_enabled",
]);
for (const thing of bad) { for (const thing of bad) {
if (json.hasOwnProperty(thing)) { if (json.hasOwnProperty(thing)) {
console.error(thing + " should not be exposed to the client"); console.error(thing + " should not be exposed to the client");
@@ -91,16 +99,13 @@ class User extends SnowFlake{
theme_colors: this.theme_colors, theme_colors: this.theme_colors,
pronouns: this.pronouns, pronouns: this.pronouns,
badge_ids: this.badge_ids, badge_ids: this.badge_ids,
} };
} }
clone(): User { clone(): User {
const json = this.tojson(); const json = this.tojson();
json.id += "#clone"; json.id += "#clone";
return new User( return new User(json, this.owner);
json,
this.owner
);
} }
public getPresence(presence: presencejson | undefined): void { public getPresence(presence: presencejson | undefined): void {
@@ -111,7 +116,7 @@ class User extends SnowFlake{
} }
} }
get online() { get online() {
return (this.status)&&(this.status!="offline"); return this.status && this.status != "offline";
} }
setstatus(status: string): void { setstatus(status: string): void {
this.status = status; this.status = status;
@@ -134,8 +139,8 @@ class User extends SnowFlake{
body: JSON.stringify({recipients: [this.id]}), body: JSON.stringify({recipients: [this.id]}),
headers: this.localuser.headers, headers: this.localuser.headers,
}) })
.then(res=>res.json()) .then((res) => res.json())
.then(json=>{ .then((json) => {
this.localuser.goToChannel(json.id); this.localuser.goToChannel(json.id);
}); });
return; return;
@@ -152,18 +157,24 @@ class User extends SnowFlake{
} else { } else {
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "DELETE", method: "DELETE",
headers: this.owner.headers headers: this.owner.headers,
}); });
} }
this.relationshipType = type; this.relationshipType = type;
} }
static setUpContextMenu(): void { static setUpContextMenu(): void {
this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(this: User){ this.contextmenu.addbutton(
() => I18n.getTranslation("user.copyId"),
function (this: User) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); },
this.contextmenu.addbutton(()=>I18n.getTranslation("user.message"), function(this: User){ );
this.contextmenu.addbutton(
() => I18n.getTranslation("user.message"),
function (this: User) {
this.opendm(); this.opendm();
}); },
);
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("user.block"), () => I18n.getTranslation("user.block"),
function (this: User) { function (this: User) {
@@ -172,7 +183,7 @@ class User extends SnowFlake{
null, null,
function () { function () {
return this.relationshipType !== 2; return this.relationshipType !== 2;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
@@ -183,25 +194,35 @@ class User extends SnowFlake{
null, null,
function () { function () {
return this.relationshipType === 2; return this.relationshipType === 2;
} },
); );
this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){ this.contextmenu.addbutton(
() => I18n.getTranslation("user.friendReq"),
function (this: User) {
this.changeRelationship(1); this.changeRelationship(1);
},null,function(){ },
null,
function () {
return this.relationshipType === 0 || this.relationshipType === 3; return this.relationshipType === 0 || this.relationshipType === 3;
}); },
this.contextmenu.addbutton(()=>I18n.getTranslation("friends.removeFriend"), function(this: User){ );
this.contextmenu.addbutton(
() => I18n.getTranslation("friends.removeFriend"),
function (this: User) {
this.changeRelationship(0); this.changeRelationship(0);
},null,function(){ },
null,
function () {
return this.relationshipType === 1; return this.relationshipType === 1;
}); },
);
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("user.kick"), () => I18n.getTranslation("user.kick"),
function (this: User, member: Member | undefined) { function (this: User, member: Member | undefined) {
member?.kick(); member?.kick();
}, },
null, null,
member=>{ (member) => {
if (!member) return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
if (member.id === us.id) { if (member.id === us.id) {
@@ -211,7 +232,7 @@ class User extends SnowFlake{
return false; return false;
} }
return us.hasPermission("KICK_MEMBERS") || false; return us.hasPermission("KICK_MEMBERS") || false;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
@@ -223,7 +244,7 @@ class User extends SnowFlake{
null, null,
function (member) { function (member) {
return member?.id === this.localuser.user.id; return member?.id === this.localuser.user.id;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("user.ban"), () => I18n.getTranslation("user.ban"),
@@ -231,7 +252,7 @@ class User extends SnowFlake{
member?.ban(); member?.ban();
}, },
null, null,
member=>{ (member) => {
if (!member) return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
if (member.id === us.id) { if (member.id === us.id) {
@@ -241,7 +262,7 @@ class User extends SnowFlake{
return false; return false;
} }
return us.hasPermission("BAN_MEMBERS") || false; return us.hasPermission("BAN_MEMBERS") || false;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("user.addRole"), () => I18n.getTranslation("user.addRole"),
@@ -262,12 +283,12 @@ class User extends SnowFlake{
} }
}, },
null, null,
member=>{ (member) => {
if (!member) return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
console.log(us.hasPermission("MANAGE_ROLES")) console.log(us.hasPermission("MANAGE_ROLES"));
return us.hasPermission("MANAGE_ROLES") || false; return us.hasPermission("MANAGE_ROLES") || false;
} },
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
() => I18n.getTranslation("user.removeRole"), () => I18n.getTranslation("user.removeRole"),
@@ -288,12 +309,12 @@ class User extends SnowFlake{
} }
}, },
null, null,
member=>{ (member) => {
if (!member) return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
console.log(us.hasPermission("MANAGE_ROLES")) console.log(us.hasPermission("MANAGE_ROLES"));
return us.hasPermission("MANAGE_ROLES") || false; return us.hasPermission("MANAGE_ROLES") || false;
} },
); );
} }
@@ -327,12 +348,12 @@ class User extends SnowFlake{
return await fetch( return await fetch(
`${this.info.api}/users/${this.id.replace( `${this.info.api}/users/${this.id.replace(
"#clone", "#clone",
"" "",
)}/profile?with_mutual_guilds=true&with_mutual_friends=true`, )}/profile?with_mutual_guilds=true&with_mutual_friends=true`,
{ {
headers: this.localuser.headers, headers: this.localuser.headers,
} },
).then(res=>res.json()); ).then((res) => res.json());
} }
async getBadge(id: string): Promise<any> { async getBadge(id: string): Promise<any> {
@@ -364,22 +385,20 @@ class User extends SnowFlake{
if (guild) { if (guild) {
(async () => { (async () => {
if (guild instanceof Guild) { if (guild instanceof Guild) {
const memb= await Member.resolveMember(this,guild) const memb = await Member.resolveMember(this, guild);
if (!memb) return; if (!memb) return;
pfp.src = memb.getpfpsrc(); pfp.src = memb.getpfpsrc();
} else { } else {
pfp.src = guild.getpfpsrc(); pfp.src = guild.getpfpsrc();
} }
})(); })();
} }
return pfp; return pfp;
} }
async buildstatuspfp(guild: Guild | void | Member | null): Promise<HTMLDivElement> { async buildstatuspfp(guild: Guild | void | Member | null): Promise<HTMLDivElement> {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("pfpDiv") div.classList.add("pfpDiv");
const pfp = this.buildpfp(guild); const pfp = this.buildpfp(guild);
div.append(pfp); div.append(pfp);
const status = document.createElement("div"); const status = document.createElement("div");
@@ -406,7 +425,7 @@ class User extends SnowFlake{
bind(html: HTMLElement, guild: Guild | null = null, error = true): void { bind(html: HTMLElement, guild: Guild | null = null, error = true): void {
if (guild && guild.id !== "@me") { if (guild && guild.id !== "@me") {
Member.resolveMember(this, guild) Member.resolveMember(this, guild)
.then(member=>{ .then((member) => {
User.contextmenu.bindContextmenu(html, this, member); User.contextmenu.bindContextmenu(html, this, member);
if (member === undefined && error) { if (member === undefined && error) {
const errorSpan = document.createElement("span"); const errorSpan = document.createElement("span");
@@ -421,7 +440,7 @@ class User extends SnowFlake{
User.contextmenu.bindContextmenu(html, this, undefined); User.contextmenu.bindContextmenu(html, this, undefined);
} }
}) })
.catch(err=>{ .catch((err) => {
console.log(err); console.log(err);
}); });
} else { } else {
@@ -435,10 +454,9 @@ class User extends SnowFlake{
} }
static async resolve(id: string, localuser: Localuser): Promise<User> { static async resolve(id: string, localuser: Localuser): Promise<User> {
const json = await fetch( const json = await fetch(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.user, localuser); return new User(json.user, localuser);
} }
@@ -446,11 +464,9 @@ class User extends SnowFlake{
this.avatar = update; this.avatar = update;
this.hypotheticalpfp = false; this.hypotheticalpfp = false;
const src = this.getpfpsrc(); const src = this.getpfpsrc();
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((element) => {
element=>{
(element as HTMLImageElement).src = src; (element as HTMLImageElement).src = src;
} });
);
} }
async block() { async block() {
@@ -480,15 +496,13 @@ class User extends SnowFlake{
return this.avatar; return this.avatar;
} }
if (guild) { if (guild) {
const member=this.members.get(guild) const member = this.members.get(guild);
if (member instanceof Member) { if (member instanceof Member) {
return member.getpfpsrc(); return member.getpfpsrc();
} }
} }
if (this.avatar !== null) { if (this.avatar !== null) {
return`${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${this.avatar}.png`;
this.avatar
}.png`;
} else { } else {
const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n);
return `${this.info.cdn}/embed/avatars/${int}.png`; return `${this.info.cdn}/embed/avatars/${int}.png`;
@@ -498,7 +512,7 @@ class User extends SnowFlake{
async buildprofile( async buildprofile(
x: number, x: number,
y: number, y: number,
guild: Guild | null | Member = null guild: Guild | null | Member = null,
): Promise<HTMLDivElement> { ): Promise<HTMLDivElement> {
if (Contextmenu.currentmenu != "") { if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
@@ -507,33 +521,33 @@ class User extends SnowFlake{
if (!guild) return; if (!guild) return;
let member: Member | undefined; let member: Member | undefined;
if (guild instanceof Guild) { if (guild instanceof Guild) {
member=await Member.resolveMember(this,guild) member = await Member.resolveMember(this, guild);
} else { } else {
member = guild; member = guild;
} }
return member; return member;
})() })();
const div = document.createElement("div"); const div = document.createElement("div");
if (this.accent_color) { if (this.accent_color) {
div.style.setProperty( div.style.setProperty(
"--accent_color", "--accent_color",
`#${this.accent_color.toString(16).padStart(6, "0")}` `#${this.accent_color.toString(16).padStart(6, "0")}`,
); );
} else { } else {
div.style.setProperty("--accent_color", "transparent"); div.style.setProperty("--accent_color", "transparent");
} }
const banner = this.getBanner(guild); const banner = this.getBanner(guild);
div.append(banner); div.append(banner);
membres.then(member=>{ membres.then((member) => {
if (!member) return; if (!member) return;
if (member.accent_color && member.accent_color !== 0) { if (member.accent_color && member.accent_color !== 0) {
div.style.setProperty( div.style.setProperty(
"--accent_color", "--accent_color",
`#${member.accent_color.toString(16).padStart(6, "0")}` `#${member.accent_color.toString(16).padStart(6, "0")}`,
); );
} }
}) });
if (x !== -1) { if (x !== -1) {
div.style.left = `${x}px`; div.style.left = `${x}px`;
@@ -584,7 +598,7 @@ class User extends SnowFlake{
pronounshtml.classList.add("pronouns"); pronounshtml.classList.add("pronouns");
userbody.appendChild(pronounshtml); userbody.appendChild(pronounshtml);
membres.then(member=>{ membres.then((member) => {
if (!member) return; if (!member) return;
if (member.pronouns && member.pronouns !== "") { if (member.pronouns && member.pronouns !== "") {
pronounshtml.textContent = member.pronouns; pronounshtml.textContent = member.pronouns;
@@ -596,7 +610,7 @@ class User extends SnowFlake{
const biohtml = this.bio.makeHTML(); const biohtml = this.bio.makeHTML();
userbody.appendChild(biohtml); userbody.appendChild(biohtml);
membres.then(member=>{ membres.then((member) => {
if (!member) return; if (!member) return;
if (member.bio && member.bio !== "") { if (member.bio && member.bio !== "") {
//TODO make markdown take Guild //TODO make markdown take Guild
@@ -606,7 +620,7 @@ class User extends SnowFlake{
}); });
if (guild) { if (guild) {
membres.then(member=>{ membres.then((member) => {
if (!member) return; if (!member) return;
usernamehtml.textContent = member.name; usernamehtml.textContent = member.name;
const roles = document.createElement("div"); const roles = document.createElement("div");
@@ -617,10 +631,7 @@ class User extends SnowFlake{
roleDiv.classList.add("rolediv"); roleDiv.classList.add("rolediv");
const color = document.createElement("div"); const color = document.createElement("div");
roleDiv.append(color); roleDiv.append(color);
color.style.setProperty( color.style.setProperty("--role-color", `#${role.color.toString(16).padStart(6, "0")}`);
"--role-color",
`#${role.color.toString(16).padStart(6, "0")}`
);
color.classList.add("colorrolediv"); color.classList.add("colorrolediv");
const span = document.createElement("span"); const span = document.createElement("span");
roleDiv.append(span); roleDiv.append(span);
@@ -654,24 +665,22 @@ class User extends SnowFlake{
banner.classList.add("banner"); banner.classList.add("banner");
} }
} else { } else {
Member.resolveMember(this,guild).then(memb=>{ Member.resolveMember(this, guild).then((memb) => {
if (!memb) return; if (!memb) return;
const bsrc = memb.getBannerUrl(); const bsrc = memb.getBannerUrl();
if (bsrc) { if (bsrc) {
banner.src = bsrc; banner.src = bsrc;
banner.classList.add("banner"); banner.classList.add("banner");
} }
}) });
} }
} }
return banner return banner;
} }
getBannerUrl(): string | undefined { getBannerUrl(): string | undefined {
if (this.banner) { if (this.banner) {
if (!this.hypotheticalbanner) { if (!this.hypotheticalbanner) {
return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${this.banner}.png`;
this.banner
}.png`;
} else { } else {
return this.banner; return this.banner;
} }

View File

@@ -2,7 +2,7 @@ class BinRead{
private i = 0; private i = 0;
private view: DataView; private view: DataView;
constructor(buffer: ArrayBuffer) { constructor(buffer: ArrayBuffer) {
this.view=new DataView(buffer, 0) this.view = new DataView(buffer, 0);
} }
read16() { read16() {
const int = this.view.getUint16(this.i); const int = this.view.getUint16(this.i);
@@ -85,4 +85,4 @@ class BinWrite{
return buf; return buf;
} }
} }
export {BinRead,BinWrite} export {BinRead, BinWrite};

View File

@@ -31,7 +31,7 @@ export function setDefaults() {
notifications: false, notifications: false,
notisound: "three", notisound: "three",
}, },
}) }),
); );
userinfos = getBulkInfo(); userinfos = getBulkInfo();
} }
@@ -41,10 +41,7 @@ export function setDefaults() {
if (userinfos.accent_color === undefined) { if (userinfos.accent_color === undefined) {
userinfos.accent_color = "#3096f7"; userinfos.accent_color = "#3096f7";
} }
document.documentElement.style.setProperty( document.documentElement.style.setProperty("--accent-color", userinfos.accent_color);
"--accent-color",
userinfos.accent_color
);
if (userinfos.preferences === undefined) { if (userinfos.preferences === undefined) {
userinfos.preferences = { userinfos.preferences = {
theme: "Dark", theme: "Dark",
@@ -79,27 +76,17 @@ export class Specialuser {
let apistring = new URL(json.serverurls.api).toString(); let apistring = new URL(json.serverurls.api).toString();
apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9";
this.serverurls.api = apistring; this.serverurls.api = apistring;
this.serverurls.cdn = new URL(json.serverurls.cdn) this.serverurls.cdn = new URL(json.serverurls.cdn).toString().replace(/\/$/, "");
.toString() this.serverurls.gateway = new URL(json.serverurls.gateway).toString().replace(/\/$/, "");
.replace(/\/$/, ""); this.serverurls.wellknown = new URL(json.serverurls.wellknown).toString().replace(/\/$/, "");
this.serverurls.gateway = new URL(json.serverurls.gateway) this.serverurls.login = new URL(json.serverurls.login).toString().replace(/\/$/, "");
.toString()
.replace(/\/$/, "");
this.serverurls.wellknown = new URL(json.serverurls.wellknown)
.toString()
.replace(/\/$/, "");
this.serverurls.login = new URL(json.serverurls.login)
.toString()
.replace(/\/$/, "");
this.email = json.email; this.email = json.email;
this.token = json.token; this.token = json.token;
this.loggedin = json.loggedin; this.loggedin = json.loggedin;
this.json = json; this.json = json;
this.json.localuserStore ??= {}; this.json.localuserStore ??= {};
if (!this.serverurls || !this.email || !this.token) { if (!this.serverurls || !this.email || !this.token) {
console.error( console.error("There are fundamentally missing pieces of info missing from this user");
"There are fundamentally missing pieces of info missing from this user"
);
} }
} }
set pfpsrc(e) { set pfpsrc(e) {
@@ -144,10 +131,7 @@ export class Specialuser {
} }
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
export{ export {mobile, iOS};
mobile,
iOS,
}
let instances: let instances:
| { | {
name: string; name: string;
@@ -170,9 +154,10 @@ let instances:
const datalist = document.getElementById("instances"); const datalist = document.getElementById("instances");
console.warn(datalist); console.warn(datalist);
const instancefetch = fetch("/instances.json") const instancefetch = fetch("/instances.json")
.then(res=>res.json()) .then((res) => res.json())
.then( .then(
(json: { (
json: {
name: string; name: string;
description?: string; description?: string;
descriptionLong?: string; descriptionLong?: string;
@@ -187,8 +172,8 @@ const instancefetch=fetch("/instances.json")
cdn: string; cdn: string;
gateway: string; gateway: string;
login?: string; login?: string;
} };
}[] }[],
) => { ) => {
instances = json; instances = json;
if (datalist) { if (datalist) {
@@ -223,7 +208,7 @@ const instancefetch=fetch("/instances.json")
} }
checkInstance(""); checkInstance("");
} }
} },
); );
const stringURLMap = new Map<string, string>(); const stringURLMap = new Map<string, string>();
@@ -238,7 +223,7 @@ const stringURLsMap = new Map<
} }
>(); >();
export async function getapiurls(str: string): Promise< export async function getapiurls(str: string): Promise<
{ | {
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
@@ -248,12 +233,12 @@ export async function getapiurls(str: string): Promise<
| false | false
> { > {
function appendApi(str: string) { function appendApi(str: string) {
return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api"); return str.includes("api") ? "" : str.endsWith("/") ? "api" : "/api";
} }
if (!URL.canParse(str)) { if (!URL.canParse(str)) {
const val = stringURLMap.get(str); const val = stringURLMap.get(str);
if (stringURLMap.size === 0) { if (stringURLMap.size === 0) {
await new Promise<void>(res=>{ await new Promise<void>((res) => {
setInterval(() => { setInterval(() => {
if (stringURLMap.size !== 0) { if (stringURLMap.size !== 0) {
res(); res();
@@ -266,9 +251,7 @@ export async function getapiurls(str: string): Promise<
} else { } else {
const val = stringURLsMap.get(str); const val = stringURLsMap.get(str);
if (val) { if (val) {
const responce = await fetch( const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
val.api + (val.api.endsWith("/") ? "" : "/") + "ping"
);
if (responce.ok) { if (responce.ok) {
if (val.login) { if (val.login) {
return val as { return val as {
@@ -297,8 +280,7 @@ export async function getapiurls(str: string): Promise<
} }
let api: string; let api: string;
try { try {
const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json() const info = await fetch(`${str}.well-known/spacebar`).then((x) => x.json());
);
api = info.api; api = info.api;
} catch { } catch {
api = str; api = str;
@@ -309,10 +291,8 @@ export async function getapiurls(str: string): Promise<
const url = new URL(api); const url = new URL(api);
try { try {
const info = await fetch( const info = await fetch(
`${api}${ `${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
url.pathname.includes("api") ? "" : "api" ).then((x) => x.json());
}/policies/instance/domains`
).then(x=>x.json());
const apiurl = new URL(info.apiEndpoint); const apiurl = new URL(info.apiEndpoint);
return { return {
api: info.apiEndpoint + appendApi(apiurl.pathname), api: info.apiEndpoint + appendApi(apiurl.pathname),
@@ -324,9 +304,7 @@ export async function getapiurls(str: string): Promise<
} catch { } catch {
const val = stringURLsMap.get(str); const val = stringURLsMap.get(str);
if (val) { if (val) {
const responce = await fetch( const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
val.api + (val.api.endsWith("/") ? "" : "/") + "ping"
);
if (responce.ok) { if (responce.ok) {
if (val.login) { if (val.login) {
return val as { return val as {
@@ -410,9 +388,11 @@ export class SW{
} }
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service.js", { navigator.serviceWorker
.register("/service.js", {
scope: "/", scope: "/",
}).then((registration) => { })
.then((registration) => {
let serviceWorker: ServiceWorker | undefined; let serviceWorker: ServiceWorker | undefined;
if (registration.installing) { if (registration.installing) {
serviceWorker = registration.installing; serviceWorker = registration.installing;
@@ -432,5 +412,5 @@ if ("serviceWorker" in navigator){
console.log(serviceWorker.state); console.log(serviceWorker.state);
}); });
} }
}) });
} }

View File

@@ -8,7 +8,7 @@ class VoiceFactory{
voices = new Map<string, Map<string, Voice>>(); voices = new Map<string, Map<string, Voice>>();
voiceChannels = new Map<string, Voice>(); voiceChannels = new Map<string, Voice>();
currentVoice?: Voice; currentVoice?: Voice;
guildUrlMap=new Map<string,{url?:string,geturl:Promise<void>,gotUrl:()=>void}>(); guildUrlMap = new Map<string, {url?: string; geturl: Promise<void>; gotUrl: () => void}>();
makeVoice(guildid: string, channelId: string, settings: Voice["settings"]) { makeVoice(guildid: string, channelId: string, settings: Voice["settings"]) {
let guild = this.voices.get(guildid); let guild = this.voices.get(guildid);
if (!guild) { if (!guild) {
@@ -41,14 +41,13 @@ class VoiceFactory{
self_mute: true, //todo self_mute: true, //todo
self_deaf: false, //todo self_deaf: false, //todo
self_video: false, //What is this? I have some guesses self_video: false, //What is this? I have some guesses
flags: 2//????? flags: 2, //?????
}, },
op:4 op: 4,
} };
} }
userMap = new Map<string, Voice>(); userMap = new Map<string, Voice>();
voiceStateUpdate(update: voiceupdate) { voiceStateUpdate(update: voiceupdate) {
const prev = this.userMap.get(update.d.user_id); const prev = this.userMap.get(update.d.user_id);
console.log(prev, this.userMap); console.log(prev, this.userMap);
if (prev) { if (prev) {
@@ -62,9 +61,11 @@ class VoiceFactory{
} }
} }
private setUpGuild(id: string) { private setUpGuild(id: string) {
const obj:{url?:string,geturl?:Promise<void>,gotUrl?:()=>void}={}; const obj: {url?: string; geturl?: Promise<void>; gotUrl?: () => void} = {};
obj.geturl=new Promise<void>(res=>{obj.gotUrl=res}); obj.geturl = new Promise<void>((res) => {
this.guildUrlMap.set(id,obj as {geturl:Promise<void>,gotUrl:()=>void}); obj.gotUrl = res;
});
this.guildUrlMap.set(id, obj as {geturl: Promise<void>; gotUrl: () => void});
} }
voiceServerUpdate(update: voiceserverupdate) { voiceServerUpdate(update: voiceserverupdate) {
const obj = this.guildUrlMap.get(update.d.guild_id); const obj = this.guildUrlMap.get(update.d.guild_id);
@@ -86,7 +87,7 @@ class Voice{
} }
readonly userid: string; readonly userid: string;
settings: {bitrate: number}; settings: {bitrate: number};
urlobj:{url?:string,geturl:Promise<void>,gotUrl:()=>void}; urlobj: {url?: string; geturl: Promise<void>; gotUrl: () => void};
constructor(userid: string, settings: Voice["settings"], urlobj: Voice["urlobj"]) { constructor(userid: string, settings: Voice["settings"], urlobj: Voice["urlobj"]) {
this.userid = userid; this.userid = userid;
this.settings = settings; this.settings = settings;
@@ -128,7 +129,7 @@ class Voice{
this.onMemberChange(userid, false); this.onMemberChange(userid, false);
} }
packet(message: MessageEvent) { packet(message: MessageEvent) {
const data=message.data const data = message.data;
if (typeof data === "string") { if (typeof data === "string") {
const json: webRTCSocket = JSON.parse(data); const json: webRTCSocket = JSON.parse(data);
switch (json.op) { switch (json.op) {
@@ -164,7 +165,7 @@ class Voice{
offer?: string; offer?: string;
cleanServerSDP(sdp: string): string { cleanServerSDP(sdp: string): string {
const pc = this.pc; const pc = this.pc;
if(!pc) throw new Error("pc isn't defined") if (!pc) throw new Error("pc isn't defined");
const ld = pc.localDescription; const ld = pc.localDescription;
if (!ld) throw new Error("localDescription isn't defined"); if (!ld) throw new Error("localDescription isn't defined");
const parsed = Voice.parsesdp(ld.sdp); const parsed = Voice.parsesdp(ld.sdp);
@@ -175,21 +176,22 @@ class Voice{
console.log(bundles); console.log(bundles);
if (!this.offer) throw new Error("Offer is missing :P"); if (!this.offer) throw new Error("Offer is missing :P");
let cline=sdp.split("\n").find(line=>line.startsWith("c=")); let cline = sdp.split("\n").find((line) => line.startsWith("c="));
if (!cline) throw new Error("c line wasn't found"); if (!cline) throw new Error("c line wasn't found");
const parsed1 = Voice.parsesdp(sdp).medias[0]; const parsed1 = Voice.parsesdp(sdp).medias[0];
//const parsed2=Voice.parsesdp(this.offer); //const parsed2=Voice.parsesdp(this.offer);
const rtcport = (parsed1.atr.get("rtcp") as Set<string>).values().next().value as string; 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_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 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 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; const candidate = (parsed1.atr.get("candidate") as Set<string>).values().next().value as string;
let build = `v=0\r let build = `v=0\r
o=- 1420070400000 0 IN IP4 127.0.0.1\r o=- 1420070400000 0 IN IP4 127.0.0.1\r
s=-\r s=-\r
t=0 0\r t=0 0\r
a=msid-semantic: WMS *\r a=msid-semantic: WMS *\r
a=group:BUNDLE ${bundles.join(" ")}\r` a=group:BUNDLE ${bundles.join(" ")}\r`;
let i = 0; let i = 0;
for (const grouping of parsed.medias) { for (const grouping of parsed.medias) {
let mode = "recvonly"; let mode = "recvonly";
@@ -216,7 +218,7 @@ a=ice-ufrag:${ICE_UFRAG}\r
a=ice-pwd:${ICE_PWD}\r a=ice-pwd:${ICE_PWD}\r
a=fingerprint:${FINGERPRINT}\r a=fingerprint:${FINGERPRINT}\r
a=candidate:${candidate}\r a=candidate:${candidate}\r
a=rtcp-mux\r` a=rtcp-mux\r`;
} else { } else {
build += ` build += `
m=video ${rtcport} UDP/TLS/RTP/SAVPF 102 103\r m=video ${rtcport} UDP/TLS/RTP/SAVPF 102 103\r
@@ -244,7 +246,7 @@ a=fingerprint:${FINGERPRINT}\r
a=candidate:${candidate}\r a=candidate:${candidate}\r
a=rtcp-mux\r`; a=rtcp-mux\r`;
} }
i++ i++;
} }
build += "\n"; build += "\n";
return build; return build;
@@ -254,20 +256,25 @@ a=rtcp-mux\r`;
if (this.pc && this.offer) { if (this.pc && this.offer) {
const pc = this.pc; const pc = this.pc;
pc.addEventListener("negotiationneeded", async () => { pc.addEventListener("negotiationneeded", async () => {
this.offer=(await pc.createOffer({ this.offer = (
await pc.createOffer({
offerToReceiveAudio: true, offerToReceiveAudio: true,
offerToReceiveVideo: true offerToReceiveVideo: true,
})).sdp; })
).sdp;
await pc.setLocalDescription({sdp: this.offer}); await pc.setLocalDescription({sdp: this.offer});
if (!this.counter) throw new Error("counter isn't defined"); if (!this.counter) throw new Error("counter isn't defined");
const counter = this.counter; const counter = this.counter;
const remote:{sdp:string,type:RTCSdpType}={sdp:this.cleanServerSDP(counter),type:"answer"}; const remote: {sdp: string; type: RTCSdpType} = {
sdp: this.cleanServerSDP(counter),
type: "answer",
};
console.log(remote); console.log(remote);
await pc.setRemoteDescription(remote); await pc.setRemoteDescription(remote);
const senders = this.senders.difference(this.ssrcMap); const senders = this.senders.difference(this.ssrcMap);
for (const sender of senders) { for (const sender of senders) {
for(const thing of (await sender.getStats() as Map<string, any>)){ for (const thing of (await sender.getStats()) as Map<string, any>) {
if (thing[1].ssrc) { if (thing[1].ssrc) {
this.ssrcMap.set(sender, thing[1].ssrc); this.ssrcMap.set(sender, thing[1].ssrc);
this.makeOp12(sender); this.makeOp12(sender);
@@ -278,13 +285,16 @@ a=rtcp-mux\r`;
}); });
} }
} }
async makeOp12(sender:RTCRtpSender|undefined|[RTCRtpSender,number]=(this.ssrcMap.entries().next().value)){ async makeOp12(
sender: RTCRtpSender | undefined | [RTCRtpSender, number] = this.ssrcMap.entries().next().value,
) {
if (!sender) throw new Error("sender doesn't exist"); if (!sender) throw new Error("sender doesn't exist");
if (sender instanceof Array) { if (sender instanceof Array) {
sender = sender[0]; sender = sender[0];
} }
if (this.ws) { if (this.ws) {
this.ws.send(JSON.stringify({ this.ws.send(
JSON.stringify({
op: 12, op: 12,
d: { d: {
audio_ssrc: this.ssrcMap.get(sender), audio_ssrc: this.ssrcMap.get(sender),
@@ -303,12 +313,13 @@ a=rtcp-mux\r`;
max_resolution: { max_resolution: {
type: "fixed", type: "fixed",
width: 0, //TODO width: 0, //TODO
height: 0//TODO height: 0, //TODO
} },
} },
] ],
} },
})); }),
);
this.status = "Sending audio streams"; this.status = "Sending audio streams";
} }
} }
@@ -336,7 +347,7 @@ a=rtcp-mux\r`;
if (this.speaking) { if (this.speaking) {
this.speaking = false; this.speaking = false;
this.sendSpeaking(); this.sendSpeaking();
console.log("not speaking") console.log("not speaking");
} }
} else if (!this.speaking) { } else if (!this.speaking) {
console.log("speaking"); console.log("speaking");
@@ -348,15 +359,17 @@ a=rtcp-mux\r`;
async sendSpeaking() { async sendSpeaking() {
if (!this.ws) return; if (!this.ws) return;
const pair = this.ssrcMap.entries().next().value; const pair = this.ssrcMap.entries().next().value;
if(!pair) return if (!pair) return;
this.ws.send(JSON.stringify({ this.ws.send(
JSON.stringify({
op: 5, op: 5,
d: { d: {
speaking: +this.speaking, speaking: +this.speaking,
delay: 5, //not sure delay: 5, //not sure
ssrc:pair[1] ssrc: pair[1],
} },
})) }),
);
} }
async continueWebRTC(data: sdpback) { async continueWebRTC(data: sdpback) {
if (this.pc && this.offer) { if (this.pc && this.offer) {
@@ -370,20 +383,20 @@ a=rtcp-mux\r`;
this.setupMic(audioStream); this.setupMic(audioStream);
const sender = pc.addTrack(track); const sender = pc.addTrack(track);
this.senders.add(sender); this.senders.add(sender);
console.log(sender) console.log(sender);
} }
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
pc.addTransceiver("audio", { pc.addTransceiver("audio", {
direction: "recvonly", direction: "recvonly",
streams: [], streams: [],
sendEncodings:[{active:true,maxBitrate:this.settings.bitrate}] sendEncodings: [{active: true, maxBitrate: this.settings.bitrate}],
}); });
} }
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
pc.addTransceiver("video", { pc.addTransceiver("video", {
direction: "recvonly", direction: "recvonly",
streams: [], streams: [],
sendEncodings:[{active:true,maxBitrate:this.settings.bitrate}] sendEncodings: [{active: true, maxBitrate: this.settings.bitrate}],
}); });
} }
this.counter = data.d.sdp; this.counter = data.d.sdp;
@@ -405,21 +418,20 @@ a=rtcp-mux\r`;
console.log(media); console.log(media);
ss.connect(context.destination); ss.connect(context.destination);
new Audio().srcObject = media; //weird I know, but it's for chromium/webkit bug new Audio().srcObject = media; //weird I know, but it's for chromium/webkit bug
this.recivers.add(e.receiver) this.recivers.add(e.receiver);
}; };
} else { } else {
this.status = "Connection failed"; this.status = "Connection failed";
} }
} }
reciverMap=new Map<number,RTCRtpReceiver>() reciverMap = new Map<number, RTCRtpReceiver>();
async figureRecivers() { async figureRecivers() {
await new Promise(res=>setTimeout(res,500)); await new Promise((res) => setTimeout(res, 500));
for (const reciver of this.recivers) { for (const reciver of this.recivers) {
const stats=await reciver.getStats() as Map<string,any>; const stats = (await reciver.getStats()) as Map<string, any>;
for(const thing of (stats)){ for (const thing of stats) {
if (thing[1].ssrc) { if (thing[1].ssrc) {
this.reciverMap.set(thing[1].ssrc,reciver) this.reciverMap.set(thing[1].ssrc, reciver);
} }
} }
} }
@@ -431,7 +443,7 @@ a=rtcp-mux\r`;
this.pc = pc; this.pc = pc;
const offer = await pc.createOffer({ const offer = await pc.createOffer({
offerToReceiveAudio: true, offerToReceiveAudio: true,
offerToReceiveVideo: true offerToReceiveVideo: true,
}); });
this.status = "Starting RTC connection"; this.status = "Starting RTC connection";
const sdp = offer.sdp; const sdp = offer.sdp;
@@ -453,12 +465,11 @@ a=rtcp-mux\r`;
const rtpmap = thing.atr.get("rtpmap"); const rtpmap = thing.atr.get("rtpmap");
if (!rtpmap) continue; if (!rtpmap) continue;
for (const codecpair of rtpmap) { for (const codecpair of rtpmap) {
const [port, codec] = codecpair.split(" "); const [port, codec] = codecpair.split(" ");
if (cur && codec.split("/")[0] === "rtx") { if (cur && codec.split("/")[0] === "rtx") {
cur[1] = Number(port); cur[1] = Number(port);
cur = undefined; cur = undefined;
continue continue;
} }
if (video.has(codec.split("/")[0])) continue; if (video.has(codec.split("/")[0])) continue;
cur = [Number(port), -1]; cur = [Number(port), -1];
@@ -469,7 +480,9 @@ a=rtcp-mux\r`;
if (!rtpmap) continue; if (!rtpmap) continue;
for (const codecpair of rtpmap) { for (const codecpair of rtpmap) {
const [port, codec] = codecpair.split(" "); const [port, codec] = codecpair.split(" ");
if(audio.has(codec.split("/")[0])) { continue}; if (audio.has(codec.split("/")[0])) {
continue;
}
audio.set(codec.split("/")[0], Number(port)); audio.set(codec.split("/")[0], Number(port));
} }
} }
@@ -479,14 +492,14 @@ a=rtcp-mux\r`;
} }
const codecs: { const codecs: {
name: string, name: string;
type: "video"|"audio", type: "video" | "audio";
priority: number, priority: number;
payload_type: number, payload_type: number;
rtx_payload_type: number|null rtx_payload_type: number | null;
}[] = []; }[] = [];
const include = new Set<string>(); const include = new Set<string>();
const audioAlloweds=new Map([["opus",{priority:1000,}]]); const audioAlloweds = new Map([["opus", {priority: 1000}]]);
for (const thing of audio) { for (const thing of audio) {
if (audioAlloweds.has(thing[0])) { if (audioAlloweds.has(thing[0])) {
include.add(thing[0]); include.add(thing[0]);
@@ -495,11 +508,15 @@ a=rtcp-mux\r`;
type: "audio", type: "audio",
priority: audioAlloweds.get(thing[0])?.priority as number, priority: audioAlloweds.get(thing[0])?.priority as number,
payload_type: thing[1], payload_type: thing[1],
rtx_payload_type:null rtx_payload_type: null,
}); });
} }
} }
const videoAlloweds=new Map([["H264",{priority:1000}],["VP8",{priority:2000}],["VP9",{priority:3000}]]); const videoAlloweds = new Map([
["H264", {priority: 1000}],
["VP8", {priority: 2000}],
["VP9", {priority: 3000}],
]);
for (const thing of video) { for (const thing of video) {
if (videoAlloweds.has(thing[0])) { if (videoAlloweds.has(thing[0])) {
include.add(thing[0]); include.add(thing[0]);
@@ -508,15 +525,16 @@ a=rtcp-mux\r`;
type: "video", type: "video",
priority: videoAlloweds.get(thing[0])?.priority as number, priority: videoAlloweds.get(thing[0])?.priority as number,
payload_type: thing[1][0], payload_type: thing[1][0],
rtx_payload_type:thing[1][1] rtx_payload_type: thing[1][1],
}); });
} }
} }
let sendsdp = "a=extmap-allow-mixed"; let sendsdp = "a=extmap-allow-mixed";
let first = true; let first = true;
for (const media of parsed.medias) { for (const media of parsed.medias) {
for (const thing of first
for(const thing of first?["ice-ufrag","ice-pwd","ice-options","fingerprint","extmap","rtpmap"]:["extmap","rtpmap"]){ ? ["ice-ufrag", "ice-pwd", "ice-options", "fingerprint", "extmap", "rtpmap"]
: ["extmap", "rtpmap"]) {
const thing2 = media.atr.get(thing); const thing2 = media.atr.get(thing);
if (!thing2) continue; if (!thing2) continue;
for (const thing3 of thing2) { for (const thing3 of thing2) {
@@ -534,20 +552,32 @@ a=rtcp-mux\r`;
first = false; first = false;
} }
if (this.ws) { if (this.ws) {
this.ws.send(JSON.stringify({ this.ws.send(
JSON.stringify({
d: { d: {
codecs, codecs,
protocol: "webrtc", protocol: "webrtc",
data: sendsdp, data: sendsdp,
sdp:sendsdp sdp: sendsdp,
}, },
op:1 op: 1,
})); }),
);
} }
} }
static parsesdp(sdp: string) { static parsesdp(sdp: string) {
let currentA = new Map<string, Set<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}; 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")) { for (const line of sdp.split("\n")) {
const [code, setinfo] = line.split("="); const [code, setinfo] = line.split("=");
switch (code) { switch (code) {
@@ -578,7 +608,7 @@ a=rtcp-mux\r`;
open = false; open = false;
async join() { async join() {
console.warn("Joining"); console.warn("Joining");
this.open=true this.open = true;
this.status = "waiting for main WS"; this.status = "waiting for main WS";
} }
onMemberChange = (_member: memberjson | string, _joined: boolean) => {}; onMemberChange = (_member: memberjson | string, _joined: boolean) => {};
@@ -591,49 +621,54 @@ a=rtcp-mux\r`;
if (!update) { if (!update) {
this.status = "bad responce from WS"; this.status = "bad responce from WS";
return; return;
}; }
if (!this.urlobj.url) { if (!this.urlobj.url) {
this.status = "waiting for Voice URL"; this.status = "waiting for Voice URL";
await this.urlobj.geturl; await this.urlobj.geturl;
if(!this.open){this.leave();return} if (!this.open) {
this.leave();
return;
}
} }
const ws=new WebSocket("ws://"+this.urlobj.url as string); const ws = new WebSocket(("ws://" + this.urlobj.url) as string);
this.ws = ws; this.ws = ws;
ws.onclose = () => { ws.onclose = () => {
this.leave(); this.leave();
} };
this.status = "waiting for WS to open"; this.status = "waiting for WS to open";
ws.addEventListener("message", (m) => { ws.addEventListener("message", (m) => {
this.packet(m); this.packet(m);
}) });
await new Promise<void>(res=>{ await new Promise<void>((res) => {
ws.addEventListener("open", () => { ws.addEventListener("open", () => {
res() res();
}) });
}); });
if (!this.ws) { if (!this.ws) {
this.leave(); this.leave();
return; return;
} }
this.status = "waiting for WS to authorize"; this.status = "waiting for WS to authorize";
ws.send(JSON.stringify({ ws.send(
"op": 0, JSON.stringify({
"d": { op: 0,
d: {
server_id: update.d.guild_id, server_id: update.d.guild_id,
user_id: update.d.user_id, user_id: update.d.user_id,
session_id: update.d.session_id, session_id: update.d.session_id,
token: update.d.token, token: update.d.token,
video: false, video: false,
"streams": [ streams: [
{ {
type: "video", type: "video",
rid: "100", rid: "100",
quality: 100 quality: 100,
} },
] ],
} },
})); }),
);
} }
} }
async leave() { async leave() {