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",
"rimraf": "^6.0.1"
},
"prettier":{
"useTabs":true,
"printWidth":100,
"semi":true,
"bracketSpacing":false
},
"devDependencies": {
"@eslint/js": "^9.10.0",
"@html-eslint/eslint-plugin": "^0.25.0",

View file

@ -22,31 +22,33 @@ interface Instance {
const app = express();
type instace = {
name:string,
description?:string,
descriptionLong?:string,
image?:string,
url?:string,
language:string,
country:string,
display:boolean,
name: string;
description?: string;
descriptionLong?: string;
image?: string;
url?: string;
language: string;
country: string;
display: boolean;
urls?: {
wellknown:string,
api:string,
cdn:string,
gateway:string,
login?:string
},
wellknown: string;
api: string;
cdn: string;
gateway: string;
login?: string;
};
contactInfo?: {
discord?:string,
github?:string,
email?:string,
spacebar?:string,
matrix?:string,
mastodon?:string
}
}
const instances=JSON.parse(readFileSync(process.env.JANK_INSTANCES_PATH||(__dirname+"/webpage/instances.json")).toString()) as instace[];
discord?: string;
github?: string;
email?: string;
spacebar?: string;
matrix?: string;
mastodon?: string;
};
};
const instances = JSON.parse(
readFileSync(process.env.JANK_INSTANCES_PATH || __dirname + "/webpage/instances.json").toString(),
) as instace[];
const instanceNames = new Map<string, Instance>();
@ -58,7 +60,9 @@ app.use(compression());
async function updateInstances(): Promise<void> {
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[];
for (const instance of json) {
if (instanceNames.has(instance.name)) {
@ -110,7 +114,7 @@ app.use("/", async (req: Request, res: Response)=>{
const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`;
res.set(
"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;
app.listen(PORT, () => {

View file

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

View file

@ -24,12 +24,12 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
url += "/";
}
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 apiUrl = new URL(api);
const policies: any = await fetch(
`${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`
).then(res=>res.json());
`${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
).then((res) => res.json());
return {
api: policies.apiEndpoint,
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");
}
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 description = invite.inviter
? `${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 {
const name = read.readString8();
const length = read.read16();
const tracks:(Track|number)[]=[]
const tracks: (Track | number)[] = [];
for (let i = 0; i < length; i++) {
let index = read.read16();
if (index === 0) {
@ -20,7 +20,7 @@ export class Audio{
tracks.push(trackarr[index - 1]);
}
}
return new Audio(name,tracks)
return new Audio(name, tracks);
}
async play() {
for (const thing of this.tracks) {

View file

@ -1,26 +1,39 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Audio</title>
<meta content="Jank Sound" property="og:title">
<meta content="A sound editor for jank clients sound format .jasf" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Sound" property="og:title" />
<meta content="A sound editor for jank clients sound format .jasf" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme" style="overflow-y: scroll;">
<body class="no-theme" style="overflow-y: scroll">
<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>
<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>
</body>
<script src="/audio/page.js" type="module"></script>
</html>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,39 +1,44 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme" style="overflow-y: scroll;">
<body class="no-theme" style="overflow-y: scroll">
<div id="titleDiv">
<img src="/logo.svg" width="40">
<img src="/logo.svg" width="40" />
<h1 id="pageTitle">Jank Client</h1>
<a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat"
class="TitleButtons">
<a href="/invite/USgYJo?instance=https%3A%2F%2Fspacebar.chat" class="TitleButtons">
Spacebar Guild
</a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
Github
</a>
<a href="/channels/@me" class="TitleButtons" id="openClient">
Open Client
</a>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
<a href="/channels/@me" class="TitleButtons" id="openClient"> Open Client </a>
</div>
<div id="homePage">
<h1 class="pagehead" id="welcomeJank">Welcome to Jank Client</h1>
<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">
<li>Direct Messaging</li>
<li>Reactions support</li>
@ -47,18 +52,18 @@
</div>
<div class="pagebox">
<h2 id="compatableInstances">Spacebar-Compatible Instances:</h2>
<div id="instancebox">
</div>
<div id="instancebox"></div>
</div>
<div class="pagebox">
<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>
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons">
Github
</a>
<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 />
<a href="https://github.com/MathMan05/JankClient" class="TitleButtons"> Github </a>
</div>
</div>
</body>
<script src="/home.js" type="module"></script>
</html>

View file

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

View file

@ -12,7 +12,7 @@ class Hover{
elm.addEventListener("mouseover", () => {
timeOut = setTimeout(async () => {
elm2 = await this.makeHover(elm);
},750)
}, 750);
});
elm.addEventListener("mouseout", () => {
clearTimeout(timeOut);
@ -22,14 +22,15 @@ class Hover{
if (e[0].removedNodes) {
clearTimeout(timeOut);
elm2.remove();
};
}
}).observe(elm, {childList: true});
}
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");
if (this.str instanceof MarkDown) {
div.append(this.str.makeHTML())
div.append(this.str.makeHTML());
} else if (this.str instanceof Function) {
const hover = await this.str();
if (hover instanceof MarkDown) {
@ -41,17 +42,17 @@ class Hover{
div.innerText = this.str;
}
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.classList.add("hoverthing");
div.style.opacity = "0";
setTimeout(() => {
div.style.opacity = "1";
},10)
}, 10);
document.body.append(div);
Contextmenu.keepOnScreen(div);
console.log(div, elm);
return div;
}
}
export{Hover}
export {Hover};

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,26 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Invite" property="og:title">
<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 content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Invite" property="og:title" />
<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 content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme">
<div>

View file

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

View file

@ -63,7 +63,12 @@ type readyjson = {
};
user_guild_settings: {
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;
flags: number;
hide_muted_channels: boolean;
@ -154,7 +159,7 @@ type memberjson = {
guild: {
id: string;
} | null;
presence?:presencejson
presence?: presencejson;
nick?: string;
roles: string[];
joined_at: string;
@ -171,16 +176,15 @@ type emojijson = {
emoji?: string;
};
type emojipjson = emojijson & {
available: boolean,
guild_id:string,
user_id:string,
managed:boolean,
require_colons:boolean,
roles:string[],
groups:null//TODO figure out what this means lol
available: boolean;
guild_id: string;
user_id: string;
managed: boolean;
require_colons: boolean;
roles: string[];
groups: null; //TODO figure out what this means lol
};
type guildjson = {
application_command_counts: {[key: string]: number};
channels: channeljson[];
@ -405,16 +409,17 @@ type messageCreateJson = {
t: "MESSAGE_CREATE";
};
type roleCreate = {
op: 0,
t: "GUILD_ROLE_CREATE",
op: 0;
t: "GUILD_ROLE_CREATE";
d: {
guild_id: string,
role: rolesjson
},
s: 6
}
guild_id: string;
role: rolesjson;
};
s: 6;
};
type wsjson =
roleCreate | {
| roleCreate
| {
op: 0;
d: any;
s: number;
@ -492,77 +497,89 @@ roleCreate | {
emoji: emojijson;
};
s: number;
}|{
op: 0,
t: "GUILD_ROLE_UPDATE",
}
| {
op: 0;
t: "GUILD_ROLE_UPDATE";
d: {
guild_id: string,
role: rolesjson
},
"s": number
}|{
op: 0,
t: "GUILD_ROLE_DELETE",
d: {
guild_id: string,
role_id: string
},
s:number
}|{
op: 0,
t: "GUILD_MEMBER_UPDATE",
d: memberjson,
s: 3
}|{
op:9,
d:boolean,
s:number
}|memberlistupdatejson|voiceupdate|voiceserverupdate|{
op: 0,
t: "RELATIONSHIP_ADD",
d: {
id: string,
type: 0|1|2|3|4|5|6,
user: userjson
},
s: number
}|{
op: 0,
t: "RELATIONSHIP_REMOVE",
d: {
id: string,
type: number,
nickname: null
},
s: number
}|{
op: 0,
t: "PRESENCE_UPDATE",
d: presencejson,
s:number
}|{
op:0,
t:"GUILD_MEMBER_ADD",
d:memberjson,
s:number
}|{
op:0,
t:"GUILD_MEMBER_REMOVE",
d:{
guild_id:string,
user:userjson
},
s:number
}|{
op: 0,
t: "GUILD_EMOJIS_UPDATE",
d: {
guild_id: string,
emojis: emojipjson[]
},
s: number
guild_id: string;
role: rolesjson;
};
s: number;
}
| {
op: 0;
t: "GUILD_ROLE_DELETE";
d: {
guild_id: string;
role_id: string;
};
s: number;
}
| {
op: 0;
t: "GUILD_MEMBER_UPDATE";
d: memberjson;
s: 3;
}
| {
op: 9;
d: boolean;
s: number;
}
| memberlistupdatejson
| voiceupdate
| voiceserverupdate
| {
op: 0;
t: "RELATIONSHIP_ADD";
d: {
id: string;
type: 0 | 1 | 2 | 3 | 4 | 5 | 6;
user: userjson;
};
s: number;
}
| {
op: 0;
t: "RELATIONSHIP_REMOVE";
d: {
id: string;
type: number;
nickname: null;
};
s: number;
}
| {
op: 0;
t: "PRESENCE_UPDATE";
d: presencejson;
s: number;
}
| {
op: 0;
t: "GUILD_MEMBER_ADD";
d: memberjson;
s: number;
}
| {
op: 0;
t: "GUILD_MEMBER_REMOVE";
d: {
guild_id: string;
user: userjson;
};
s: number;
}
| {
op: 0;
t: "GUILD_EMOJIS_UPDATE";
d: {
guild_id: string;
emojis: emojipjson[];
};
s: number;
};
type memberChunk = {
guild_id: string;
@ -574,132 +591,138 @@ type memberChunk = {
not_found: string[];
};
type voiceupdate = {
op: 0,
t: "VOICE_STATE_UPDATE",
op: 0;
t: "VOICE_STATE_UPDATE";
d: {
guild_id: string,
channel_id: string,
user_id: string,
member: memberjson,
session_id: string,
token: string,
deaf: boolean,
mute: boolean,
self_deaf: boolean,
self_mute: boolean,
self_video: boolean,
suppress: boolean
},
s: number
guild_id: string;
channel_id: string;
user_id: string;
member: memberjson;
session_id: string;
token: string;
deaf: boolean;
mute: boolean;
self_deaf: boolean;
self_mute: boolean;
self_video: boolean;
suppress: boolean;
};
s: number;
};
type voiceserverupdate = {
op: 0,
t: "VOICE_SERVER_UPDATE",
op: 0;
t: "VOICE_SERVER_UPDATE";
d: {
token: string,
guild_id: string,
endpoint: string
},
s: 6
token: string;
guild_id: string;
endpoint: string;
};
s: 6;
};
type memberlistupdatejson = {
op: 0,
s: number,
t: "GUILD_MEMBER_LIST_UPDATE",
op: 0;
s: number;
t: "GUILD_MEMBER_LIST_UPDATE";
d: {
ops: [
{
items:({
items: (
| {
group: {
count:number,
id:string
count: number;
id: string;
};
}
}|{
member:memberjson
})[]
op: "SYNC",
range: [
number,
number
]
| {
member: memberjson;
}
],
online_count: number,
member_count: number,
id: string,
guild_id: string,
)[];
op: "SYNC";
range: [number, number];
},
];
online_count: number;
member_count: number;
id: string;
guild_id: string;
groups: {
count: number,
id: string
}[]
}
}
type webRTCSocket= {
op: 8,
count: number;
id: string;
}[];
};
};
type webRTCSocket =
| {
op: 8;
d: {
heartbeat_interval: number
heartbeat_interval: number;
};
}
}|{
op:6,
d:{t: number}
}|{
op: 2,
| {
op: 6;
d: {t: number};
}
| {
op: 2;
d: {
ssrc: number,
"streams": {
type: "video",//probally more options, but idk
rid: string,
quality: number,
ssrc: number,
rtx_ssrc:number
}[],
ip: number,
port: number,
"modes": [],//no clue
"experiments": []//no clue
ssrc: number;
streams: {
type: "video"; //probally more options, but idk
rid: string;
quality: number;
ssrc: number;
rtx_ssrc: number;
}[];
ip: number;
port: number;
modes: []; //no clue
experiments: []; //no clue
};
}
}|sdpback|opRTC12|{
op: 5,
| sdpback
| opRTC12
| {
op: 5;
d: {
user_id: string,
speaking: 0,
ssrc: 940464811
}
user_id: string;
speaking: 0;
ssrc: 940464811;
};
};
type sdpback = {
op: 4,
op: 4;
d: {
audioCodec: string,
videoCodec: string,
media_session_id: string,
sdp: string
}
audioCodec: string;
videoCodec: string;
media_session_id: string;
sdp: string;
};
};
type opRTC12 = {
op: 12,
op: 12;
d: {
user_id: string,
audio_ssrc: number,
video_ssrc: number,
user_id: string;
audio_ssrc: number;
video_ssrc: number;
streams: [
{
type: "video",
rid: "100",
ssrc: number,
active: boolean,
quality: 100,
rtx_ssrc: number,
max_bitrate: 2500000,
max_framerate: number,
type: "video";
rid: "100";
ssrc: number;
active: boolean;
quality: 100;
rtx_ssrc: number;
max_bitrate: 2500000;
max_framerate: number;
max_resolution: {
type: "fixed",
width: number,
height: number
}
}
]
}
}
type: "fixed";
width: number;
height: number;
};
},
];
};
};
export {
readyjson,
dirrectjson,
@ -725,5 +748,5 @@ export{
webRTCSocket,
sdpback,
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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta
content="A spacebar client that has DMs, replying and more"
property="og:description"
>
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme">
<div id="logindiv">
@ -29,25 +35,13 @@
id="instancein"
value=""
required
>
/>
<label for="uname" id="emailField"><b>Email:</b></label>
<input
type="text"
placeholder="Enter email address"
name="uname"
id="uname"
required
>
<input type="text" placeholder="Enter email address" name="uname" id="uname" required />
<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 />
<p class="wrongred" id="wrong"></p>
<div id="h-captcha"></div>

View file

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

View file

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

View file

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

View file

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

View file

@ -4,46 +4,46 @@ import { getBulkUsers, Specialuser } from "../utils/utils.js";
import {Permissions} from "../permissions.js";
type botjsonfetch = {
guilds: {
id: string,
name: string,
icon: string,
mfa_level: number,
permissions: string
}[],
"user": {
id: string,
username: string,
avatar: string,
avatar_decoration?: string,
discriminator: string,
public_flags: number
},
id: string;
name: string;
icon: string;
mfa_level: number;
permissions: string;
}[];
user: {
id: string;
username: string;
avatar: string;
avatar_decoration?: string;
discriminator: string;
public_flags: number;
};
application: {
id: string,
name: string,
icon: string|null,
description: string,
summary: string,
type: null,//not sure what this means :P
hook: boolean,
guild_id: null|string,
bot_public: boolean,
bot_require_code_grant: boolean,
verify_key: "IMPLEMENTME",//no clue what this is meant to be :P
flags: number
},
id: string;
name: string;
icon: string | null;
description: string;
summary: string;
type: null; //not sure what this means :P
hook: boolean;
guild_id: null | string;
bot_public: boolean;
bot_require_code_grant: boolean;
verify_key: "IMPLEMENTME"; //no clue what this is meant to be :P
flags: number;
};
bot: {
id: string,
username: string,
avatar: string|null,
avatar_decoration: null|string,
discriminator: string,
public_flags: number,
bot: boolean,
approximated_guild_count: number
},
authorized: boolean
}
id: string;
username: string;
avatar: string | null;
avatar_decoration: null | string;
discriminator: string;
public_flags: number;
bot: boolean;
approximated_guild_count: number;
};
authorized: boolean;
};
(async () => {
const users = getBulkUsers();
const params = new URLSearchParams(window.location.search);
@ -77,9 +77,7 @@ type botjsonfetch={
}
}
} else {
throw new Error(
"Someone needs to handle the case where the servers don't exist"
);
throw new Error("Someone needs to handle the case where the servers don't exist");
}
} else {
urls = joinable[0].serverurls;
@ -93,12 +91,14 @@ type botjsonfetch={
if (!urls) return;
fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers: {
Authorization:user.token
}
}).then(_=>_.json()).then((json:botjsonfetch)=>{
Authorization: user.token,
},
})
.then((_) => _.json())
.then((json: botjsonfetch) => {
const guilds: botjsonfetch["guilds"] = [];
for (const guild of json.guilds) {
const permisions=new Permissions(guild.permissions)
const permisions = new Permissions(guild.permissions);
if (permisions.hasPermission("MANAGE_GUILD")) {
guilds.push(guild);
}
@ -128,26 +128,26 @@ type botjsonfetch={
button.onclick = () => {
const id = select.value;
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(), {
method: "POST",
body: JSON.stringify({
authorize: true,
guild_id: id,
permissions:permstr
permissions: permstr,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
Authorization: user.token,
}
}).then(req=>{
},
}).then((req) => {
if (req.ok) {
alert("Bot added successfully");
}
})
}
});
};
document.body.append(dialog);
})
});
}
function showAccounts(): void {
const table = document.createElement("dialog");
@ -168,9 +168,7 @@ type botjsonfetch={
userDiv.append(document.createElement("br"));
const span = document.createElement("span");
span.textContent = user.serverurls.wellknown
.replace("https://", "")
.replace("http://", "");
span.textContent = user.serverurls.wellknown.replace("https://", "").replace("http://", "");
span.classList.add("serverURL");
userDiv.append(span);
@ -211,12 +209,14 @@ type botjsonfetch={
}
fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
headers: {
Authorization:user.token
}
}).then(_=>_.json()).then((json:botjsonfetch)=>{
Authorization: user.token,
},
})
.then((_) => _.json())
.then((json: botjsonfetch) => {
const title = document.getElementById("invitename");
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");
if (desc) {
@ -232,8 +232,8 @@ type botjsonfetch={
const perms = document.getElementById("permissions") as HTMLDivElement;
if (perms && permstr) {
perms.children[0].textContent=I18n.getTranslation("htmlPages.idpermissions")
const permisions=new Permissions(permstr)
perms.children[0].textContent = I18n.getTranslation("htmlPages.idpermissions");
const permisions = new Permissions(permstr);
for (const perm of Permissions.info()) {
if (permisions.hasPermission(perm.name, false)) {
const div = document.createElement("div");
@ -245,10 +245,10 @@ type botjsonfetch={
}
}
}
})
});
const AcceptInvite = document.getElementById("AcceptInvite");
if (AcceptInvite) {
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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Bot Invite" property="og:title">
<meta name="description" content="Invite this bot to your server!">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Bot Invite" property="og:title" />
<meta name="description" content="Invite this bot to your server!" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme">
<div>

View file

@ -13,7 +13,7 @@ class Permissions{
this.allow = 0n;
this.deny = 0n;
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,
readableName: I18n.getTranslation("permissions.readableNames." + thing),
description: I18n.getTranslation("permissions.descriptions." + thing),
}
};
}
}
static permisions = [
@ -83,7 +83,7 @@ class Permissions{
"USE_EXTERNAL_SOUNDS",
"SEND_VOICE_MESSAGES",
"SEND_POLLS",
"USE_EXTERNAL_APPS"
"USE_EXTERNAL_APPS",
];
getPermission(name: string): number {
if (undefined === Permissions.permisions.indexOf(name)) {
@ -91,9 +91,7 @@ class Permissions{
}
if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) {
return 1;
}else if(
this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)
){
} else if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)) {
return -1;
} else {
return 0;
@ -102,11 +100,10 @@ class Permissions{
hasPermission(name: string, adminOverride = true): boolean {
if (this.deny) {
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))
return true;
if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) return true;
if (name !== "ADMINISTRATOR" && adminOverride) return this.hasPermission("ADMINISTRATOR");
return false;
}
@ -114,11 +111,7 @@ class Permissions{
const bit = Permissions.permisions.indexOf(name);
if (bit === undefined) {
return console.error(
"Tried to set permission to " +
setto +
" for " +
name +
" but it doesn't exist"
"Tried to set permission to " + setto + " for " + name + " but it doesn't exist",
);
}

View file

@ -1,16 +1,25 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jank Client</title>
<meta content="Jank Client" property="og:title">
<meta content="A spacebar client that has DMs, replying and more" property="og:description">
<meta content="/logo.webp" property="og:image">
<meta content="#4b458c" data-react-helmet="true" name="theme-color">
<link href="/style.css" rel="stylesheet">
<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>
<meta content="Jank Client" property="og:title" />
<meta content="A spacebar client that has DMs, replying and more" property="og:description" />
<meta content="/logo.webp" property="og:image" />
<meta content="#4b458c" data-react-helmet="true" name="theme-color" />
<link href="/style.css" rel="stylesheet" />
<link href="/themes.css" rel="stylesheet" id="lightcss" />
<style>
body.no-theme {
background: #16191b;
}
@media (prefers-color-scheme: light) {
body.no-theme {
background: #9397bd;
}
}
</style>
</head>
<body class="no-theme">
<div id="logindiv">
@ -19,41 +28,53 @@
<div>
<label for="instance" id="instanceField"><b>Instance:</b></label>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<p class="wrongred" id="wrong"></p>
<div id="h-captcha">
</div>
<div id="h-captcha"></div>
<button type="submit" class="dontgrow" id="createAccount">Create account</button>
</form>
<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 {adduser} from "./login.js";
import {MarkDown} from "./markdown.js";
await I18n.done
await I18n.done;
const registerElement = document.getElementById("register");
if (registerElement) {
registerElement.addEventListener("submit", registertry);
@ -15,17 +15,16 @@ if(registerElement){
const createAccount = document.getElementById("createAccount");
const alreadyHave = document.getElementById("alreadyHave");
if (userField && pw2Field && alreadyHave && createAccount && dobField) {
userField.textContent=I18n.getTranslation("htmlPages.userField")
pw2Field.textContent=I18n.getTranslation("htmlPages.pw2Field")
dobField.textContent=I18n.getTranslation("htmlPages.dobField")
createAccount.textContent=I18n.getTranslation("htmlPages.createAccount")
alreadyHave.textContent=I18n.getTranslation("htmlPages.alreadyHave")
userField.textContent = I18n.getTranslation("htmlPages.userField");
pw2Field.textContent = I18n.getTranslation("htmlPages.pw2Field");
dobField.textContent = I18n.getTranslation("htmlPages.dobField");
createAccount.textContent = I18n.getTranslation("htmlPages.createAccount");
alreadyHave.textContent = I18n.getTranslation("htmlPages.alreadyHave");
}
})()
})();
async function registertry(e: Event) {
e.preventDefault();
const elements = (e.target as HTMLFormElement)
.elements as HTMLFormControlsCollection;
const elements = (e.target as HTMLFormElement).elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] 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;
if (password !== confirmPassword) {
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation("localuser.PasswordsNoMatch");
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation(
"localuser.PasswordsNoMatch",
);
return;
}
@ -99,22 +100,22 @@ function handleErrors(errors: any, elements: HTMLFormControlsCollection){
} else if (errors.password) {
showError(
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) {
showError(
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) {
showError(
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) {
showError(
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 {
(document.getElementById("wrong") as HTMLElement).textContent =
@ -124,9 +125,7 @@ errors[Object.keys(errors)[0]]._errors[0].message;
function showError(element: HTMLElement, message: string) {
const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName(
"suberror"
)[0] as HTMLElement;
let errorElement = parent.getElementsByClassName("suberror")[0] as HTMLElement;
if (!errorElement) {
const div = document.createElement("div");
div.classList.add("suberror", "suberrora");

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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