formatting updates
This commit is contained in:
parent
ffe21e6d6c
commit
d2d0f45c81
50 changed files with 7783 additions and 7432 deletions
|
@ -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",
|
||||
|
|
56
src/index.ts
56
src/index.ts
|
@ -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, () => {
|
||||
|
|
34
src/stats.ts
34
src/stats.ts
|
@ -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;
|
||||
|
|
10
src/utils.ts
10
src/utils.ts
|
@ -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}` : ""}`
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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!");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
})();
|
||||
|
|
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue