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",
|
||||
|
|
152
src/index.ts
152
src/index.ts
|
@ -1,176 +1,180 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import compression from"compression";
|
||||
import express, { Request, Response }from"express";
|
||||
import fs from"node:fs/promises";
|
||||
import path from"node:path";
|
||||
import{ observe, uptime }from"./stats.js";
|
||||
import{ getApiUrls, inviteResponse }from"./utils.js";
|
||||
import{ fileURLToPath }from"node:url";
|
||||
import compression from "compression";
|
||||
import express, {Request, Response} from "express";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {observe, uptime} from "./stats.js";
|
||||
import {getApiUrls, inviteResponse} from "./utils.js";
|
||||
import {fileURLToPath} from "node:url";
|
||||
import {readFileSync} from "fs";
|
||||
import process from"node:process";
|
||||
import process from "node:process";
|
||||
|
||||
const devmode = (process.env.NODE_ENV || "development") === "development";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
interface Instance {
|
||||
name: string;
|
||||
[key: string]: any;
|
||||
name: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
type instace={
|
||||
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
|
||||
},
|
||||
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[];
|
||||
type instace = {
|
||||
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;
|
||||
};
|
||||
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[];
|
||||
|
||||
const instanceNames = new Map<string, Instance>();
|
||||
|
||||
for(const instance of instances){
|
||||
for (const instance of instances) {
|
||||
instanceNames.set(instance.name, instance);
|
||||
}
|
||||
|
||||
app.use(compression());
|
||||
|
||||
async function updateInstances(): Promise<void>{
|
||||
try{
|
||||
const response = await fetch("https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json");
|
||||
async function updateInstances(): Promise<void> {
|
||||
try {
|
||||
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)){
|
||||
for (const instance of json) {
|
||||
if (instanceNames.has(instance.name)) {
|
||||
const existingInstance = instanceNames.get(instance.name);
|
||||
if(existingInstance){
|
||||
for(const key of Object.keys(instance)){
|
||||
if(!existingInstance[key]){
|
||||
if (existingInstance) {
|
||||
for (const key of Object.keys(instance)) {
|
||||
if (!existingInstance[key]) {
|
||||
existingInstance[key] = instance[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
instances.push(instance as any);
|
||||
}
|
||||
}
|
||||
observe(instances);
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error updating instances:", error);
|
||||
}
|
||||
}
|
||||
|
||||
updateInstances();
|
||||
|
||||
app.use("/getupdates", async (_req: Request, res: Response)=>{
|
||||
try{
|
||||
app.use("/getupdates", async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const stats = await fs.stat(path.join(__dirname, "webpage"));
|
||||
res.send(stats.mtimeMs.toString());
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error getting updates:", error);
|
||||
res.status(500).send("Error getting updates");
|
||||
}
|
||||
});
|
||||
|
||||
app.use("/services/oembed", (req: Request, res: Response)=>{
|
||||
app.use("/services/oembed", (req: Request, res: Response) => {
|
||||
inviteResponse(req, res);
|
||||
});
|
||||
|
||||
app.use("/uptime", (req: Request, res: Response)=>{
|
||||
app.use("/uptime", (req: Request, res: Response) => {
|
||||
const instanceUptime = uptime.get(req.query.name as string);
|
||||
res.send(instanceUptime);
|
||||
});
|
||||
|
||||
app.use("/", async (req: Request, res: Response)=>{
|
||||
app.use("/", async (req: Request, res: Response) => {
|
||||
const scheme = req.secure ? "https" : "http";
|
||||
const host = `${scheme}://${req.get("Host")}`;
|
||||
const ref = host + req.originalUrl;
|
||||
|
||||
if(host && ref){
|
||||
if (host && ref) {
|
||||
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"`,
|
||||
);
|
||||
}
|
||||
|
||||
if(req.path === "/"){
|
||||
if (req.path === "/") {
|
||||
res.sendFile(path.join(__dirname, "webpage", "home.html"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(req.path.startsWith("/instances.json")){
|
||||
if (req.path.startsWith("/instances.json")) {
|
||||
res.json(instances);
|
||||
return;
|
||||
}
|
||||
|
||||
if(req.path.startsWith("/invite/")){
|
||||
if (req.path.startsWith("/invite/")) {
|
||||
res.sendFile(path.join(__dirname, "webpage", "invite.html"));
|
||||
return;
|
||||
}
|
||||
const filePath = path.join(__dirname, "webpage", req.path);
|
||||
try{
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
if(devmode){
|
||||
if (devmode) {
|
||||
const filePath2 = path.join(__dirname, "../src/webpage", req.path);
|
||||
try{
|
||||
try {
|
||||
await fs.access(filePath2);
|
||||
res.sendFile(filePath2);
|
||||
return;
|
||||
}catch{}
|
||||
} catch {}
|
||||
}
|
||||
res.sendFile(filePath);
|
||||
}catch{
|
||||
try{
|
||||
} catch {
|
||||
try {
|
||||
await fs.access(`${filePath}.html`);
|
||||
if(devmode){
|
||||
if (devmode) {
|
||||
const filePath2 = path.join(__dirname, "../src/webpage", req.path);
|
||||
try{
|
||||
try {
|
||||
await fs.access(filePath2 + ".html");
|
||||
res.sendFile(filePath2 + ".html");
|
||||
return;
|
||||
}catch{}
|
||||
} catch {}
|
||||
}
|
||||
res.sendFile(`${filePath}.html`);
|
||||
}catch{
|
||||
if(req.path.startsWith("/src/webpage")){
|
||||
} catch {
|
||||
if (req.path.startsWith("/src/webpage")) {
|
||||
const filePath2 = path.join(__dirname, "..", req.path);
|
||||
try{
|
||||
try {
|
||||
await fs.access(filePath2);
|
||||
res.sendFile(filePath2);
|
||||
return;
|
||||
}catch{}
|
||||
} catch {}
|
||||
}
|
||||
res.sendFile(path.join(__dirname, "webpage", "index.html"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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, ()=>{
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
|
||||
export{ getApiUrls };
|
||||
export {getApiUrls};
|
||||
|
|
170
src/stats.ts
170
src/stats.ts
|
@ -1,39 +1,39 @@
|
|||
import fs from"node:fs";
|
||||
import path from"node:path";
|
||||
import{ getApiUrls }from"./utils.js";
|
||||
import{ fileURLToPath }from"node:url";
|
||||
import{ setTimeout, clearTimeout }from"node:timers";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {getApiUrls} from "./utils.js";
|
||||
import {fileURLToPath} from "node:url";
|
||||
import {setTimeout, clearTimeout} from "node:timers";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
interface UptimeEntry {
|
||||
time: number;
|
||||
online: boolean;
|
||||
time: number;
|
||||
online: boolean;
|
||||
}
|
||||
|
||||
interface Instance {
|
||||
name: string;
|
||||
urls?: { api: string };
|
||||
url?: string;
|
||||
online?: boolean;
|
||||
uptime?: {
|
||||
daytime: number;
|
||||
weektime: number;
|
||||
alltime: number;
|
||||
};
|
||||
name: string;
|
||||
urls?: {api: string};
|
||||
url?: string;
|
||||
online?: boolean;
|
||||
uptime?: {
|
||||
daytime: number;
|
||||
weektime: number;
|
||||
alltime: number;
|
||||
};
|
||||
}
|
||||
|
||||
const uptimeObject: Map<string, UptimeEntry[]> = loadUptimeObject();
|
||||
export{ uptimeObject as uptime };
|
||||
export {uptimeObject as uptime};
|
||||
|
||||
function loadUptimeObject(): Map<string, UptimeEntry[]>{
|
||||
const filePath = process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json");
|
||||
if(fs.existsSync(filePath)){
|
||||
try{
|
||||
function loadUptimeObject(): Map<string, UptimeEntry[]> {
|
||||
const filePath = process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json");
|
||||
if (fs.existsSync(filePath)) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
return new Map(Object.entries(data));
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error reading uptime.json:", error);
|
||||
return new Map();
|
||||
}
|
||||
|
@ -43,26 +43,26 @@ function loadUptimeObject(): Map<string, UptimeEntry[]>{
|
|||
|
||||
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function saveUptimeObject(): void{
|
||||
if(saveTimeout){
|
||||
function saveUptimeObject(): void {
|
||||
if (saveTimeout) {
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
saveTimeout = setTimeout(()=>{
|
||||
saveTimeout = setTimeout(() => {
|
||||
const data = Object.fromEntries(uptimeObject);
|
||||
fs.writeFile(
|
||||
process.env.JANK_UPTIME_JSON_PATH||path.join(__dirname, "..", "uptime.json"),
|
||||
process.env.JANK_UPTIME_JSON_PATH || path.join(__dirname, "..", "uptime.json"),
|
||||
JSON.stringify(data),
|
||||
error=>{
|
||||
if(error){
|
||||
(error) => {
|
||||
if (error) {
|
||||
console.error("Error saving uptime.json:", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}, 5000); // Batch updates every 5 seconds
|
||||
}
|
||||
|
||||
function removeUndefinedKey(): void{
|
||||
if(uptimeObject.has("undefined")){
|
||||
function removeUndefinedKey(): void {
|
||||
if (uptimeObject.has("undefined")) {
|
||||
uptimeObject.delete("undefined");
|
||||
saveUptimeObject();
|
||||
}
|
||||
|
@ -70,101 +70,89 @@ function removeUndefinedKey(): void{
|
|||
|
||||
removeUndefinedKey();
|
||||
|
||||
export async function observe(instances: Instance[]): Promise<void>{
|
||||
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>{
|
||||
try{
|
||||
async function resolveInstance(instance: Instance, activeInstances: Set<string>): Promise<void> {
|
||||
try {
|
||||
calcStats(instance);
|
||||
const api = await getApiUrl(instance);
|
||||
if(!api){
|
||||
if (!api) {
|
||||
handleUnresolvedApi(instance);
|
||||
return;
|
||||
}
|
||||
activeInstances.add(instance.name);
|
||||
await checkHealth(instance, api);
|
||||
scheduleHealthCheck(instance, api);
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error resolving instance:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getApiUrl(instance: Instance): Promise<string | null>{
|
||||
if(instance.urls){
|
||||
async function getApiUrl(instance: Instance): Promise<string | null> {
|
||||
if (instance.urls) {
|
||||
return instance.urls.api;
|
||||
}
|
||||
if(instance.url){
|
||||
if (instance.url) {
|
||||
const urls = await getApiUrls(instance.url);
|
||||
return urls ? urls.api : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleUnresolvedApi(instance: Instance): void{
|
||||
function handleUnresolvedApi(instance: Instance): void {
|
||||
setStatus(instance, false);
|
||||
console.warn(`${instance.name} does not resolve api URL`, instance);
|
||||
setTimeout(()=>resolveInstance(instance, new Set()), 1000 * 60 * 30);
|
||||
setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30);
|
||||
}
|
||||
|
||||
function scheduleHealthCheck(instance: Instance, api: string): void{
|
||||
function scheduleHealthCheck(instance: Instance, api: string): void {
|
||||
const checkInterval = 1000 * 60 * 30;
|
||||
const initialDelay = Math.random() * 1000 * 60 * 10;
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => {
|
||||
checkHealth(instance, api);
|
||||
setInterval(()=>checkHealth(instance, api), checkInterval);
|
||||
setInterval(() => checkHealth(instance, api), checkInterval);
|
||||
}, initialDelay);
|
||||
}
|
||||
|
||||
async function checkHealth(
|
||||
instance: Instance,
|
||||
api: string,
|
||||
tries = 0
|
||||
): Promise<void>{
|
||||
try{
|
||||
const response = await fetch(`${api}/ping`, { method: "HEAD" });
|
||||
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}`);
|
||||
if(response.ok || tries > 3){
|
||||
if (response.ok || tries > 3) {
|
||||
setStatus(instance, response.ok);
|
||||
}else{
|
||||
} else {
|
||||
retryHealthCheck(instance, api, tries);
|
||||
}
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error(`Error checking health for ${instance.name}:`, error);
|
||||
if(tries > 3){
|
||||
if (tries > 3) {
|
||||
setStatus(instance, false);
|
||||
}else{
|
||||
} else {
|
||||
retryHealthCheck(instance, api, tries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function retryHealthCheck(
|
||||
instance: Instance,
|
||||
api: string,
|
||||
tries: number
|
||||
): void{
|
||||
setTimeout(()=>checkHealth(instance, api, tries + 1), 30000);
|
||||
function retryHealthCheck(instance: Instance, api: string, tries: number): void {
|
||||
setTimeout(() => checkHealth(instance, api, tries + 1), 30000);
|
||||
}
|
||||
|
||||
function updateInactiveInstances(activeInstances: Set<string>): void{
|
||||
for(const key of uptimeObject.keys()){
|
||||
if(!activeInstances.has(key)){
|
||||
function updateInactiveInstances(activeInstances: Set<string>): void {
|
||||
for (const key of uptimeObject.keys()) {
|
||||
if (!activeInstances.has(key)) {
|
||||
setStatus(key, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calcStats(instance: Instance): void{
|
||||
function calcStats(instance: Instance): void {
|
||||
const obj = uptimeObject.get(instance.name);
|
||||
if(!obj)return;
|
||||
if (!obj) return;
|
||||
|
||||
const now = Date.now();
|
||||
const day = now - 1000 * 60 * 60 * 24;
|
||||
|
@ -176,7 +164,7 @@ function calcStats(instance: Instance): void{
|
|||
let weektime = 0;
|
||||
let online = false;
|
||||
|
||||
for(let i = 0; i < obj.length; i++){
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
const entry = obj[i];
|
||||
online = entry.online;
|
||||
const stamp = entry.time;
|
||||
|
@ -186,11 +174,11 @@ function calcStats(instance: Instance): void{
|
|||
totalTimePassed += timePassed;
|
||||
alltime += Number(online) * timePassed;
|
||||
|
||||
if(stamp + timePassed > week){
|
||||
if (stamp + timePassed > week) {
|
||||
const weekTimePassed = Math.min(timePassed, nextStamp - week);
|
||||
weektime += Number(online) * weekTimePassed;
|
||||
|
||||
if(stamp + timePassed > day){
|
||||
if (stamp + timePassed > day) {
|
||||
const dayTimePassed = Math.min(weekTimePassed, nextStamp - day);
|
||||
daytime += Number(online) * dayTimePassed;
|
||||
}
|
||||
|
@ -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,46 +194,46 @@ function calculateUptimeStats(
|
|||
alltime: number,
|
||||
daytime: number,
|
||||
weektime: number,
|
||||
online: boolean
|
||||
): { daytime: number; weektime: number; alltime: number }{
|
||||
online: boolean,
|
||||
): {daytime: number; weektime: number; alltime: number} {
|
||||
const dayInMs = 1000 * 60 * 60 * 24;
|
||||
const weekInMs = dayInMs * 7;
|
||||
|
||||
alltime /= totalTimePassed;
|
||||
|
||||
if(totalTimePassed > dayInMs){
|
||||
if (totalTimePassed > dayInMs) {
|
||||
daytime = daytime || (online ? dayInMs : 0);
|
||||
daytime /= dayInMs;
|
||||
|
||||
if(totalTimePassed > weekInMs){
|
||||
if (totalTimePassed > weekInMs) {
|
||||
weektime = weektime || (online ? weekInMs : 0);
|
||||
weektime /= weekInMs;
|
||||
}else{
|
||||
} else {
|
||||
weektime = alltime;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
weektime = alltime;
|
||||
daytime = alltime;
|
||||
}
|
||||
|
||||
return{ daytime, weektime, alltime };
|
||||
return {daytime, weektime, alltime};
|
||||
}
|
||||
|
||||
function setStatus(instance: string | Instance, status: boolean): void{
|
||||
function setStatus(instance: string | Instance, status: boolean): void {
|
||||
const name = typeof instance === "string" ? instance : instance.name;
|
||||
let obj = uptimeObject.get(name);
|
||||
|
||||
if(!obj){
|
||||
if (!obj) {
|
||||
obj = [];
|
||||
uptimeObject.set(name, obj);
|
||||
}
|
||||
|
||||
const lastEntry = obj.at(-1);
|
||||
if(!lastEntry || lastEntry.online !== status){
|
||||
obj.push({ time: Date.now(), online: status });
|
||||
if (!lastEntry || lastEntry.online !== status) {
|
||||
obj.push({time: Date.now(), online: status});
|
||||
saveUptimeObject();
|
||||
|
||||
if(typeof instance !== "string"){
|
||||
if (typeof instance !== "string") {
|
||||
calcStats(instance);
|
||||
}
|
||||
}
|
||||
|
|
66
src/utils.ts
66
src/utils.ts
|
@ -1,74 +1,76 @@
|
|||
import{ Request, Response }from"express";
|
||||
import {Request, Response} from "express";
|
||||
|
||||
interface ApiUrls {
|
||||
api: string;
|
||||
gateway: string;
|
||||
cdn: string;
|
||||
wellknown: string;
|
||||
api: string;
|
||||
gateway: string;
|
||||
cdn: string;
|
||||
wellknown: string;
|
||||
}
|
||||
|
||||
interface Invite {
|
||||
guild: {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
id: string;
|
||||
};
|
||||
inviter?: {
|
||||
username: string;
|
||||
};
|
||||
guild: {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
id: string;
|
||||
};
|
||||
inviter?: {
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function getApiUrls(url: string): Promise<ApiUrls | null>{
|
||||
if(!url.endsWith("/")){
|
||||
export async function getApiUrls(url: string): Promise<ApiUrls | null> {
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
try{
|
||||
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json());
|
||||
try {
|
||||
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());
|
||||
return{
|
||||
`${api}${apiUrl.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
|
||||
).then((res) => res.json());
|
||||
return {
|
||||
api: policies.apiEndpoint,
|
||||
gateway: policies.gateway,
|
||||
cdn: policies.cdn,
|
||||
wellknown: url,
|
||||
};
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error fetching API URLs:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function inviteResponse(req: Request, res: Response): Promise<void>{
|
||||
export async function inviteResponse(req: Request, res: Response): Promise<void> {
|
||||
let url: URL;
|
||||
try{
|
||||
try {
|
||||
url = new URL(req.query.url as string);
|
||||
}catch{
|
||||
} catch {
|
||||
const scheme = req.secure ? "https" : "http";
|
||||
const host = `${scheme}://${req.get("Host")}`;
|
||||
url = new URL(host);
|
||||
}
|
||||
|
||||
try{
|
||||
if(url.pathname.startsWith("invite")){
|
||||
try {
|
||||
if (url.pathname.startsWith("invite")) {
|
||||
throw new Error("Invalid invite URL");
|
||||
}
|
||||
|
||||
const code = url.pathname.split("/")[2];
|
||||
const instance = url.searchParams.get("instance");
|
||||
if(!instance){
|
||||
if (!instance) {
|
||||
throw new Error("Instance not specified");
|
||||
}
|
||||
|
||||
const urls = await getApiUrls(instance);
|
||||
if(!urls){
|
||||
if (!urls) {
|
||||
throw new Error("Failed to get API URLs");
|
||||
}
|
||||
|
||||
const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise<Invite>);
|
||||
const invite = await fetch(`${urls.api}/invites/${code}`).then(
|
||||
(json) => json.json() as Promise<Invite>,
|
||||
);
|
||||
const title = invite.guild.name;
|
||||
const description = invite.inviter
|
||||
? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`
|
||||
|
@ -84,7 +86,7 @@ export async function inviteResponse(req: Request, res: Response): Promise<void>
|
|||
thumbnail,
|
||||
description,
|
||||
});
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error processing invite response:", error);
|
||||
res.json({
|
||||
type: "link",
|
||||
|
@ -95,4 +97,4 @@ export async function inviteResponse(req: Request, res: Response): Promise<void>
|
|||
url: url.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
import { BinRead } from "../utils/binaryUtils.js";
|
||||
import { Track } from "./track.js";
|
||||
import {BinRead} from "../utils/binaryUtils.js";
|
||||
import {Track} from "./track.js";
|
||||
|
||||
export class Audio{
|
||||
name:string;
|
||||
tracks:(Track|number)[];
|
||||
constructor(name:string,tracks:(Track|number)[]){
|
||||
this.tracks=tracks;
|
||||
this.name=name;
|
||||
}
|
||||
static parse(read:BinRead,trackarr:Track[]):Audio{
|
||||
const name=read.readString8();
|
||||
const length=read.read16();
|
||||
const tracks:(Track|number)[]=[]
|
||||
for(let i=0;i<length;i++){
|
||||
let index=read.read16();
|
||||
if(index===0){
|
||||
tracks.push(read.readFloat32());
|
||||
}else{
|
||||
tracks.push(trackarr[index-1]);
|
||||
}
|
||||
}
|
||||
return new Audio(name,tracks)
|
||||
}
|
||||
async play(){
|
||||
for(const thing of this.tracks){
|
||||
if(thing instanceof Track){
|
||||
thing.play();
|
||||
}else{
|
||||
await new Promise((res)=>setTimeout(res,thing));
|
||||
}
|
||||
}
|
||||
}
|
||||
export class Audio {
|
||||
name: string;
|
||||
tracks: (Track | number)[];
|
||||
constructor(name: string, tracks: (Track | number)[]) {
|
||||
this.tracks = tracks;
|
||||
this.name = name;
|
||||
}
|
||||
static parse(read: BinRead, trackarr: Track[]): Audio {
|
||||
const name = read.readString8();
|
||||
const length = read.read16();
|
||||
const tracks: (Track | number)[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
let index = read.read16();
|
||||
if (index === 0) {
|
||||
tracks.push(read.readFloat32());
|
||||
} else {
|
||||
tracks.push(trackarr[index - 1]);
|
||||
}
|
||||
}
|
||||
return new Audio(name, tracks);
|
||||
}
|
||||
async play() {
|
||||
for (const thing of this.tracks) {
|
||||
if (thing instanceof Track) {
|
||||
thing.play();
|
||||
} else {
|
||||
await new Promise((res) => setTimeout(res, thing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { BinWrite } from "../utils/binaryUtils.js";
|
||||
import { setTheme } from "../utils/utils.js";
|
||||
import { Play } from "./play.js";
|
||||
import {BinWrite} from "../utils/binaryUtils.js";
|
||||
import {setTheme} from "../utils/utils.js";
|
||||
import {Play} from "./play.js";
|
||||
|
||||
setTheme();
|
||||
const w=new BinWrite(2**12);
|
||||
const w = new BinWrite(2 ** 12);
|
||||
w.writeStringNo("jasf");
|
||||
w.write8(4);
|
||||
|
||||
|
@ -18,100 +18,103 @@ w.writeString8("custom");
|
|||
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(4); //sin
|
||||
w.write8(5); //times
|
||||
{
|
||||
w.write8(9);//Power
|
||||
w.write8(9); //Power
|
||||
|
||||
{
|
||||
w.write8(6);//adding
|
||||
w.write8(1);//t
|
||||
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(6); //adding
|
||||
w.write8(1); //t
|
||||
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(5)//times
|
||||
w.write8(5)//times
|
||||
w.write8(3);//PI
|
||||
w.write8(0);w.write32Float(2);//2
|
||||
w.write8(2);//freq
|
||||
w.write8(5); //times
|
||||
w.write8(5); //times
|
||||
w.write8(3); //PI
|
||||
w.write8(0);
|
||||
w.write32Float(2); //2
|
||||
w.write8(2); //freq
|
||||
}
|
||||
|
||||
w.write16(4);//3 tracks
|
||||
w.write16(4); //3 tracks
|
||||
|
||||
w.write16(1);//zip
|
||||
w.write16(1); //zip
|
||||
w.write8(4);
|
||||
w.write32Float(1)
|
||||
w.write32Float(700)
|
||||
w.write32Float(1);
|
||||
w.write32Float(700);
|
||||
|
||||
w.write16(3);//beep
|
||||
w.write16(3); //beep
|
||||
{
|
||||
w.write8(1);
|
||||
w.write32Float(1)
|
||||
w.write32Float(700);
|
||||
w.write32Float(50);
|
||||
w.write8(1);
|
||||
w.write32Float(1);
|
||||
w.write32Float(700);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(0);
|
||||
w.write32Float(100);
|
||||
w.write8(0);
|
||||
w.write32Float(100);
|
||||
|
||||
w.write8(1);
|
||||
w.write32Float(1)
|
||||
w.write32Float(700);
|
||||
w.write32Float(50);
|
||||
w.write8(1);
|
||||
w.write32Float(1);
|
||||
w.write32Float(700);
|
||||
w.write32Float(50);
|
||||
}
|
||||
|
||||
w.write16(5);//three
|
||||
w.write16(5); //three
|
||||
{
|
||||
w.write8(1);
|
||||
w.write32Float(1)
|
||||
w.write32Float(800);
|
||||
w.write32Float(50);
|
||||
w.write8(1);
|
||||
w.write32Float(1);
|
||||
w.write32Float(800);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(1);
|
||||
w.write32Float(1)
|
||||
w.write32Float(1000);
|
||||
w.write32Float(50);
|
||||
w.write8(1);
|
||||
w.write32Float(1);
|
||||
w.write32Float(1000);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(1);
|
||||
w.write32Float(1)
|
||||
w.write32Float(1300);
|
||||
w.write32Float(50);
|
||||
w.write8(1);
|
||||
w.write32Float(1);
|
||||
w.write32Float(1300);
|
||||
w.write32Float(50);
|
||||
}
|
||||
|
||||
w.write16(5);//square
|
||||
w.write16(5); //square
|
||||
{
|
||||
w.write8(3);
|
||||
w.write32Float(1)
|
||||
w.write32Float(600);
|
||||
w.write32Float(50);
|
||||
w.write8(3);
|
||||
w.write32Float(1);
|
||||
w.write32Float(600);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(3);
|
||||
w.write32Float(1)
|
||||
w.write32Float(800);
|
||||
w.write32Float(50);
|
||||
w.write8(3);
|
||||
w.write32Float(1);
|
||||
w.write32Float(800);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
w.write8(0);
|
||||
w.write32Float(50);
|
||||
|
||||
w.write8(3);
|
||||
w.write32Float(1)
|
||||
w.write32Float(1000);
|
||||
w.write32Float(50);
|
||||
w.write8(3);
|
||||
w.write32Float(1);
|
||||
w.write32Float(1000);
|
||||
w.write32Float(50);
|
||||
}
|
||||
w.write16(4);//2 audio
|
||||
w.write16(4); //2 audio
|
||||
|
||||
w.writeString8("zip");
|
||||
w.write16(1);
|
||||
|
@ -128,8 +131,8 @@ w.write16(3);
|
|||
w.writeString8("square");
|
||||
w.write16(1);
|
||||
w.write16(4);
|
||||
const buff=w.getBuffer();
|
||||
const play=Play.parseBin(buff);
|
||||
const buff = w.getBuffer();
|
||||
const play = Play.parseBin(buff);
|
||||
/*
|
||||
const zip=play.audios.get("square");
|
||||
if(zip){
|
||||
|
@ -140,18 +143,18 @@ if(zip){
|
|||
console.log(play.voices[3][0].info.wave)
|
||||
};
|
||||
*/
|
||||
console.log(play,buff);
|
||||
console.log(play, buff);
|
||||
|
||||
const download=document.getElementById("download");
|
||||
if(download){
|
||||
download.onclick=()=>{
|
||||
const blob = new Blob([buff], { type: "binary" });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = "sounds.jasf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
const download = document.getElementById("download");
|
||||
if (download) {
|
||||
download.onclick = () => {
|
||||
const blob = new Blob([buff], {type: "binary"});
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = "sounds.jasf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
import { BinRead } from "../utils/binaryUtils.js";
|
||||
import { Track } from "./track.js";
|
||||
import { AVoice } from "./voice.js";
|
||||
import { Audio } from "./audio.js";
|
||||
export class Play{
|
||||
voices:[AVoice,string][]
|
||||
tracks:Track[]
|
||||
audios:Map<string,Audio>;
|
||||
constructor(voices:[AVoice,string][],tracks:Track[],audios:Map<string,Audio>){
|
||||
this.voices=voices;
|
||||
this.tracks=tracks;
|
||||
this.audios=audios;
|
||||
}
|
||||
static parseBin(buffer:ArrayBuffer){
|
||||
const read=new BinRead(buffer);
|
||||
if(read.readStringNo(4)!=="jasf") throw new Error("this is not a jasf file");
|
||||
let voices=read.read8();
|
||||
let six=false;
|
||||
if(voices===255){
|
||||
voices=read.read16();
|
||||
six=true;
|
||||
}
|
||||
const voiceArr:[AVoice,string][]=[];
|
||||
for(let i=0;i<voices;i++){
|
||||
voiceArr.push(AVoice.getVoice(read));
|
||||
}
|
||||
import {BinRead} from "../utils/binaryUtils.js";
|
||||
import {Track} from "./track.js";
|
||||
import {AVoice} from "./voice.js";
|
||||
import {Audio} from "./audio.js";
|
||||
export class Play {
|
||||
voices: [AVoice, string][];
|
||||
tracks: Track[];
|
||||
audios: Map<string, Audio>;
|
||||
constructor(voices: [AVoice, string][], tracks: Track[], audios: Map<string, Audio>) {
|
||||
this.voices = voices;
|
||||
this.tracks = tracks;
|
||||
this.audios = audios;
|
||||
}
|
||||
static parseBin(buffer: ArrayBuffer) {
|
||||
const read = new BinRead(buffer);
|
||||
if (read.readStringNo(4) !== "jasf") throw new Error("this is not a jasf file");
|
||||
let voices = read.read8();
|
||||
let six = false;
|
||||
if (voices === 255) {
|
||||
voices = read.read16();
|
||||
six = true;
|
||||
}
|
||||
const voiceArr: [AVoice, string][] = [];
|
||||
for (let i = 0; i < voices; i++) {
|
||||
voiceArr.push(AVoice.getVoice(read));
|
||||
}
|
||||
|
||||
const tracks=read.read16();
|
||||
const trackArr:Track[]=[];
|
||||
for(let i=0;i<tracks;i++){
|
||||
trackArr.push(Track.parse(read,voiceArr,six));
|
||||
}
|
||||
const tracks = read.read16();
|
||||
const trackArr: Track[] = [];
|
||||
for (let i = 0; i < tracks; i++) {
|
||||
trackArr.push(Track.parse(read, voiceArr, six));
|
||||
}
|
||||
|
||||
const audios=read.read16();
|
||||
const audioArr=new Map<string,Audio>();
|
||||
for(let i=0;i<audios;i++){
|
||||
const a=Audio.parse(read,trackArr);
|
||||
audioArr.set(a.name,a)
|
||||
}
|
||||
const audios = read.read16();
|
||||
const audioArr = new Map<string, Audio>();
|
||||
for (let i = 0; i < audios; i++) {
|
||||
const a = Audio.parse(read, trackArr);
|
||||
audioArr.set(a.name, a);
|
||||
}
|
||||
|
||||
return new Play(voiceArr,trackArr,audioArr);
|
||||
}
|
||||
static async playURL(url:string){
|
||||
const res=await fetch(url);
|
||||
const arr=await res.arrayBuffer();
|
||||
return this.parseBin(arr);
|
||||
}
|
||||
return new Play(voiceArr, trackArr, audioArr);
|
||||
}
|
||||
static async playURL(url: string) {
|
||||
const res = await fetch(url);
|
||||
const arr = await res.arrayBuffer();
|
||||
return this.parseBin(arr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,45 @@
|
|||
import { BinRead } from "../utils/binaryUtils.js";
|
||||
import { AVoice } from "./voice.js";
|
||||
import {BinRead} from "../utils/binaryUtils.js";
|
||||
import {AVoice} from "./voice.js";
|
||||
|
||||
export class Track{
|
||||
seq:(AVoice|number)[];
|
||||
constructor(playing:(AVoice|number)[]){
|
||||
this.seq=playing;
|
||||
}
|
||||
static parse(read:BinRead,play:[AVoice,string][],six:boolean):Track{
|
||||
const length=read.read16();
|
||||
const play2:(AVoice|number)[]=[];
|
||||
for(let i=0;i<length;i++){
|
||||
let index:number;
|
||||
if(six){
|
||||
index=read.read16();
|
||||
}else{
|
||||
index=read.read8();
|
||||
}
|
||||
if(index===0){
|
||||
play2.push(read.readFloat32());
|
||||
continue;
|
||||
}
|
||||
index--;
|
||||
if(!play[index]) throw new Error("voice not found");
|
||||
const [voice]=play[index];
|
||||
let temp:AVoice;
|
||||
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);
|
||||
}
|
||||
async play(){
|
||||
for(const thing of this.seq){
|
||||
if(thing instanceof AVoice){
|
||||
thing.playL();
|
||||
}else{
|
||||
await new Promise((res)=>setTimeout(res,thing));
|
||||
}
|
||||
}
|
||||
}
|
||||
export class Track {
|
||||
seq: (AVoice | number)[];
|
||||
constructor(playing: (AVoice | number)[]) {
|
||||
this.seq = playing;
|
||||
}
|
||||
static parse(read: BinRead, play: [AVoice, string][], six: boolean): Track {
|
||||
const length = read.read16();
|
||||
const play2: (AVoice | number)[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
let index: number;
|
||||
if (six) {
|
||||
index = read.read16();
|
||||
} else {
|
||||
index = read.read8();
|
||||
}
|
||||
if (index === 0) {
|
||||
play2.push(read.readFloat32());
|
||||
continue;
|
||||
}
|
||||
index--;
|
||||
if (!play[index]) throw new Error("voice not found");
|
||||
const [voice] = play[index];
|
||||
let temp: AVoice;
|
||||
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);
|
||||
}
|
||||
async play() {
|
||||
for (const thing of this.seq) {
|
||||
if (thing instanceof AVoice) {
|
||||
thing.playL();
|
||||
} else {
|
||||
await new Promise((res) => setTimeout(res, thing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { BinRead } from "../utils/binaryUtils.js";
|
||||
import {BinRead} from "../utils/binaryUtils.js";
|
||||
|
||||
class AVoice{
|
||||
class AVoice {
|
||||
audioCtx: AudioContext;
|
||||
info: { wave: string | Function; freq: number };
|
||||
info: {wave: string | Function; freq: number};
|
||||
playing: boolean;
|
||||
myArrayBuffer: AudioBuffer;
|
||||
gainNode: GainNode;
|
||||
buffer: Float32Array;
|
||||
source: AudioBufferSourceNode;
|
||||
length=1;
|
||||
constructor(wave: string | Function, freq: number, volume = 1,length=1000){
|
||||
this.length=length;
|
||||
length = 1;
|
||||
constructor(wave: string | Function, freq: number, volume = 1, length = 1000) {
|
||||
this.length = length;
|
||||
this.audioCtx = new window.AudioContext();
|
||||
this.info = { wave, freq };
|
||||
this.info = {wave, freq};
|
||||
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;
|
||||
|
@ -29,193 +29,193 @@ class AVoice{
|
|||
this.source.start();
|
||||
this.updateWave();
|
||||
}
|
||||
clone(volume:number,freq:number,length=this.length){
|
||||
return new AVoice(this.wave,freq,volume,length);
|
||||
clone(volume: number, freq: number, length = this.length) {
|
||||
return new AVoice(this.wave, freq, volume, length);
|
||||
}
|
||||
get wave(): string | Function{
|
||||
get wave(): string | Function {
|
||||
return this.info.wave;
|
||||
}
|
||||
get freq(): number{
|
||||
get freq(): number {
|
||||
return this.info.freq;
|
||||
}
|
||||
set wave(wave: string | Function){
|
||||
set wave(wave: string | Function) {
|
||||
this.info.wave = wave;
|
||||
this.updateWave();
|
||||
}
|
||||
set freq(freq: number){
|
||||
set freq(freq: number) {
|
||||
this.info.freq = freq;
|
||||
this.updateWave();
|
||||
}
|
||||
updateWave(): void{
|
||||
updateWave(): void {
|
||||
const func = this.waveFunction();
|
||||
for(let i = 0; i < this.buffer.length; i++){
|
||||
for (let i = 0; i < this.buffer.length; i++) {
|
||||
this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq);
|
||||
}
|
||||
}
|
||||
waveFunction(): Function{
|
||||
if(typeof this.wave === "function"){
|
||||
waveFunction(): Function {
|
||||
if (typeof this.wave === "function") {
|
||||
return this.wave;
|
||||
}
|
||||
switch(this.wave){
|
||||
case"sin":
|
||||
return(t: number, freq: number)=>{
|
||||
return Math.sin(t * Math.PI * 2 * freq);
|
||||
};
|
||||
case"triangle":
|
||||
return(t: number, freq: number)=>{
|
||||
return Math.abs(((4 * t * freq) % 4) - 2) - 1;
|
||||
};
|
||||
case"sawtooth":
|
||||
return(t: number, freq: number)=>{
|
||||
return((t * freq) % 1) * 2 - 1;
|
||||
};
|
||||
case"square":
|
||||
return(t: number, freq: number)=>{
|
||||
return(t * freq) % 2 < 1 ? 1 : -1;
|
||||
};
|
||||
case"white":
|
||||
return(_t: number, _freq: number)=>{
|
||||
return Math.random() * 2 - 1;
|
||||
};
|
||||
switch (this.wave) {
|
||||
case "sin":
|
||||
return (t: number, freq: number) => {
|
||||
return Math.sin(t * Math.PI * 2 * freq);
|
||||
};
|
||||
case "triangle":
|
||||
return (t: number, freq: number) => {
|
||||
return Math.abs(((4 * t * freq) % 4) - 2) - 1;
|
||||
};
|
||||
case "sawtooth":
|
||||
return (t: number, freq: number) => {
|
||||
return ((t * freq) % 1) * 2 - 1;
|
||||
};
|
||||
case "square":
|
||||
return (t: number, freq: number) => {
|
||||
return (t * freq) % 2 < 1 ? 1 : -1;
|
||||
};
|
||||
case "white":
|
||||
return (_t: number, _freq: number) => {
|
||||
return Math.random() * 2 - 1;
|
||||
};
|
||||
}
|
||||
return new Function();
|
||||
}
|
||||
play(): void{
|
||||
if(this.playing){
|
||||
play(): void {
|
||||
if (this.playing) {
|
||||
return;
|
||||
}
|
||||
this.source.connect(this.gainNode);
|
||||
this.playing = true;
|
||||
}
|
||||
playL(){
|
||||
playL() {
|
||||
this.play();
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => {
|
||||
this.stop();
|
||||
},this.length);
|
||||
}, this.length);
|
||||
}
|
||||
stop(): void{
|
||||
if(this.playing){
|
||||
stop(): void {
|
||||
if (this.playing) {
|
||||
this.source.disconnect();
|
||||
this.playing = false;
|
||||
}
|
||||
}
|
||||
static noises(noise: string): void{
|
||||
switch(noise){
|
||||
case"three": {
|
||||
const voicy = new AVoice("sin", 800);
|
||||
voicy.play();
|
||||
setTimeout(_=>{
|
||||
voicy.freq = 1000;
|
||||
}, 50);
|
||||
setTimeout(_=>{
|
||||
voicy.freq = 1300;
|
||||
}, 100);
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case"zip": {
|
||||
const voicy = new AVoice((t: number, freq: number)=>{
|
||||
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
|
||||
}, 700);
|
||||
voicy.play();
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case"square": {
|
||||
const voicy = new AVoice("square", 600, 0.4);
|
||||
voicy.play();
|
||||
setTimeout(_=>{
|
||||
voicy.freq = 800;
|
||||
}, 50);
|
||||
setTimeout(_=>{
|
||||
voicy.freq = 1000;
|
||||
}, 100);
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case"beep": {
|
||||
const voicy = new AVoice("sin", 800);
|
||||
voicy.play();
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 50);
|
||||
setTimeout(_=>{
|
||||
static noises(noise: string): void {
|
||||
switch (noise) {
|
||||
case "three": {
|
||||
const voicy = new AVoice("sin", 800);
|
||||
voicy.play();
|
||||
}, 100);
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case "join":{
|
||||
const voicy = new AVoice("triangle", 600,.1);
|
||||
voicy.play();
|
||||
setTimeout(_=>{
|
||||
voicy.freq=800;
|
||||
}, 75);
|
||||
setTimeout(_=>{
|
||||
voicy.freq=1000;
|
||||
}, 150);
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 200);
|
||||
break;
|
||||
}
|
||||
case "leave":{
|
||||
const voicy = new AVoice("triangle", 850,.5);
|
||||
voicy.play();
|
||||
setTimeout(_=>{
|
||||
voicy.freq=700;
|
||||
}, 100);
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
voicy.freq=400;
|
||||
}, 180);
|
||||
setTimeout(_=>{
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 1000;
|
||||
}, 50);
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 1300;
|
||||
}, 100);
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case "zip": {
|
||||
const voicy = new AVoice((t: number, freq: number) => {
|
||||
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
|
||||
}, 700);
|
||||
voicy.play();
|
||||
}, 200);
|
||||
setTimeout(_=>{
|
||||
voicy.stop();
|
||||
}, 250);
|
||||
break;
|
||||
}
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case "square": {
|
||||
const voicy = new AVoice("square", 600, 0.4);
|
||||
voicy.play();
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 800;
|
||||
}, 50);
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 1000;
|
||||
}, 100);
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case "beep": {
|
||||
const voicy = new AVoice("sin", 800);
|
||||
voicy.play();
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 50);
|
||||
setTimeout((_) => {
|
||||
voicy.play();
|
||||
}, 100);
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 150);
|
||||
break;
|
||||
}
|
||||
case "join": {
|
||||
const voicy = new AVoice("triangle", 600, 0.1);
|
||||
voicy.play();
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 800;
|
||||
}, 75);
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 1000;
|
||||
}, 150);
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 200);
|
||||
break;
|
||||
}
|
||||
case "leave": {
|
||||
const voicy = new AVoice("triangle", 850, 0.5);
|
||||
voicy.play();
|
||||
setTimeout((_) => {
|
||||
voicy.freq = 700;
|
||||
}, 100);
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
voicy.freq = 400;
|
||||
}, 180);
|
||||
setTimeout((_) => {
|
||||
voicy.play();
|
||||
}, 200);
|
||||
setTimeout((_) => {
|
||||
voicy.stop();
|
||||
}, 250);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
static get sounds(){
|
||||
return["three", "zip", "square", "beep"];
|
||||
static get sounds() {
|
||||
return ["three", "zip", "square", "beep"];
|
||||
}
|
||||
static getVoice(read:BinRead):[AVoice,string]{
|
||||
static getVoice(read: BinRead): [AVoice, string] {
|
||||
const name = read.readString8();
|
||||
let length=read.readFloat32();
|
||||
let special:Function|string
|
||||
if(length!==0){
|
||||
special=this.parseExpression(read);
|
||||
}else{
|
||||
special=name;
|
||||
length=1;
|
||||
let length = read.readFloat32();
|
||||
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)};`);
|
||||
static parseExpression(read: BinRead): Function {
|
||||
return new Function("t", "f", `return ${this.PEHelper(read)};`);
|
||||
}
|
||||
static PEHelper(read:BinRead):string{
|
||||
let state=read.read8();
|
||||
switch(state){
|
||||
static PEHelper(read: BinRead): string {
|
||||
let state = read.read8();
|
||||
switch (state) {
|
||||
case 0:
|
||||
return ""+read.readFloat32();
|
||||
return "" + read.readFloat32();
|
||||
case 1:
|
||||
return "t";
|
||||
case 2:
|
||||
return "f";
|
||||
case 3:
|
||||
return `Math.PI`
|
||||
return `Math.PI`;
|
||||
case 4:
|
||||
return `Math.sin(${this.PEHelper(read)})`;
|
||||
case 5:
|
||||
|
@ -238,9 +238,8 @@ class AVoice{
|
|||
return `Math.cos(${this.PEHelper(read)})`;
|
||||
default:
|
||||
throw new Error("unexpected case found!");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export{ AVoice as AVoice };
|
||||
export {AVoice as AVoice};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,82 +1,82 @@
|
|||
import{ iOS }from"./utils/utils.js";
|
||||
class Contextmenu<x, y>{
|
||||
import {iOS} from "./utils/utils.js";
|
||||
class Contextmenu<x, y> {
|
||||
static currentmenu: HTMLElement | "";
|
||||
name: string;
|
||||
buttons: [
|
||||
string|(()=>string),
|
||||
string | (() => string),
|
||||
(this: x, arg: y, e: MouseEvent) => void,
|
||||
string | null,
|
||||
(this: x, arg: y) => boolean,
|
||||
(this: x, arg: y) => boolean,
|
||||
string
|
||||
string,
|
||||
][];
|
||||
div!: HTMLDivElement;
|
||||
static setup(){
|
||||
static setup() {
|
||||
Contextmenu.currentmenu = "";
|
||||
document.addEventListener("click", event=>{
|
||||
if(Contextmenu.currentmenu === ""){
|
||||
document.addEventListener("click", (event) => {
|
||||
if (Contextmenu.currentmenu === "") {
|
||||
return;
|
||||
}
|
||||
if(!Contextmenu.currentmenu.contains(event.target as Node)){
|
||||
if (!Contextmenu.currentmenu.contains(event.target as Node)) {
|
||||
Contextmenu.currentmenu.remove();
|
||||
Contextmenu.currentmenu = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
constructor(name: string){
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.buttons = [];
|
||||
}
|
||||
addbutton(
|
||||
text: string|(()=>string),
|
||||
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{};
|
||||
return {};
|
||||
}
|
||||
addsubmenu(
|
||||
text: string|(()=>string),
|
||||
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{};
|
||||
return {};
|
||||
}
|
||||
private makemenu(x: number, y: number, addinfo: x, other: y){
|
||||
private makemenu(x: number, y: number, addinfo: x, other: y) {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("contextmenu", "flexttb");
|
||||
|
||||
let visibleButtons = 0;
|
||||
for(const thing of this.buttons){
|
||||
if(!thing[3].call(addinfo, other))continue;
|
||||
for (const thing of this.buttons) {
|
||||
if (!thing[3].call(addinfo, other)) continue;
|
||||
visibleButtons++;
|
||||
|
||||
const intext = document.createElement("button");
|
||||
intext.disabled = !thing[4].call(addinfo, other);
|
||||
intext.classList.add("contextbutton");
|
||||
if(thing[0] instanceof Function){
|
||||
if (thing[0] instanceof Function) {
|
||||
intext.textContent = thing[0]();
|
||||
}else{
|
||||
} else {
|
||||
intext.textContent = thing[0];
|
||||
}
|
||||
console.log(thing);
|
||||
if(thing[5] === "button" || thing[5] === "submenu"){
|
||||
intext.onclick = (e)=>{
|
||||
if (thing[5] === "button" || thing[5] === "submenu") {
|
||||
intext.onclick = (e) => {
|
||||
div.remove();
|
||||
thing[1].call(addinfo, other,e)
|
||||
thing[1].call(addinfo, other, e);
|
||||
};
|
||||
}
|
||||
|
||||
div.appendChild(intext);
|
||||
}
|
||||
if(visibleButtons == 0)return;
|
||||
if (visibleButtons == 0) return;
|
||||
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
if (Contextmenu.currentmenu != "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
div.style.top = y + "px";
|
||||
|
@ -87,64 +87,74 @@ 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=()=>{}){
|
||||
const func = (event: MouseEvent)=>{
|
||||
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();
|
||||
this.makemenu(event.clientX, event.clientY, addinfo, other);
|
||||
};
|
||||
obj.addEventListener("contextmenu", func);
|
||||
if(iOS){
|
||||
let hold:NodeJS.Timeout|undefined;
|
||||
let x!:number;
|
||||
let y!:number;
|
||||
obj.addEventListener("touchstart",(event: TouchEvent)=>{
|
||||
x=event.touches[0].pageX;
|
||||
y=event.touches[0].pageY;
|
||||
if(event.touches.length > 1){
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
|
||||
}else{
|
||||
//
|
||||
event.stopImmediatePropagation();
|
||||
hold=setTimeout(()=>{
|
||||
if(lastx**2+lasty**2>10**2) return;
|
||||
if (iOS) {
|
||||
let hold: NodeJS.Timeout | undefined;
|
||||
let x!: number;
|
||||
let y!: number;
|
||||
obj.addEventListener(
|
||||
"touchstart",
|
||||
(event: TouchEvent) => {
|
||||
x = event.touches[0].pageX;
|
||||
y = event.touches[0].pageY;
|
||||
if (event.touches.length > 1) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
|
||||
console.log(obj);
|
||||
},500)
|
||||
}
|
||||
},{passive: false});
|
||||
let lastx=0;
|
||||
let lasty=0;
|
||||
obj.addEventListener("touchend",()=>{
|
||||
if(hold){
|
||||
} else {
|
||||
//
|
||||
event.stopImmediatePropagation();
|
||||
hold = setTimeout(() => {
|
||||
if (lastx ** 2 + lasty ** 2 > 10 ** 2) return;
|
||||
this.makemenu(event.touches[0].clientX, event.touches[0].clientY, addinfo, other);
|
||||
console.log(obj);
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
{passive: false},
|
||||
);
|
||||
let lastx = 0;
|
||||
let lasty = 0;
|
||||
obj.addEventListener("touchend", () => {
|
||||
if (hold) {
|
||||
clearTimeout(hold);
|
||||
}
|
||||
touchEnd(lastx,lasty);
|
||||
touchEnd(lastx, lasty);
|
||||
});
|
||||
obj.addEventListener("touchmove",(event)=>{
|
||||
lastx=event.touches[0].pageX-x;
|
||||
lasty=event.touches[0].pageY-y;
|
||||
touchDrag(lastx,lasty);
|
||||
obj.addEventListener("touchmove", (event) => {
|
||||
lastx = event.touches[0].pageX - x;
|
||||
lasty = event.touches[0].pageY - y;
|
||||
touchDrag(lastx, lasty);
|
||||
});
|
||||
}
|
||||
return func;
|
||||
}
|
||||
static keepOnScreen(obj: HTMLElement){
|
||||
static keepOnScreen(obj: HTMLElement) {
|
||||
const html = document.documentElement.getBoundingClientRect();
|
||||
const docheight = html.height;
|
||||
const docwidth = html.width;
|
||||
const box = obj.getBoundingClientRect();
|
||||
console.log(box, docheight, docwidth);
|
||||
if(box.right > docwidth){
|
||||
if (box.right > docwidth) {
|
||||
console.log("test");
|
||||
obj.style.left = docwidth - box.width + "px";
|
||||
}
|
||||
if(box.bottom > docheight){
|
||||
if (box.bottom > docheight) {
|
||||
obj.style.top = docheight - box.height + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
Contextmenu.setup();
|
||||
export{ Contextmenu };
|
||||
export {Contextmenu};
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import{ Guild }from"./guild.js";
|
||||
import{ Channel }from"./channel.js";
|
||||
import{ Message }from"./message.js";
|
||||
import{ Localuser }from"./localuser.js";
|
||||
import{ User }from"./user.js";
|
||||
import{channeljson,dirrectjson,memberjson,messagejson}from"./jsontypes.js";
|
||||
import{ Permissions }from"./permissions.js";
|
||||
import{ SnowFlake }from"./snowflake.js";
|
||||
import{ Contextmenu }from"./contextmenu.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
import { Float, FormError } from "./settings.js";
|
||||
import {Guild} from "./guild.js";
|
||||
import {Channel} from "./channel.js";
|
||||
import {Message} from "./message.js";
|
||||
import {Localuser} from "./localuser.js";
|
||||
import {User} from "./user.js";
|
||||
import {channeljson, dirrectjson, memberjson, messagejson} from "./jsontypes.js";
|
||||
import {Permissions} from "./permissions.js";
|
||||
import {SnowFlake} from "./snowflake.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {Float, FormError} from "./settings.js";
|
||||
|
||||
class Direct extends Guild{
|
||||
declare channelids: { [key: string]: Group };
|
||||
class Direct extends Guild {
|
||||
declare channelids: {[key: string]: Group};
|
||||
channels: Group[];
|
||||
getUnixTime(): number{
|
||||
getUnixTime(): number {
|
||||
throw new Error("Do not call this for Direct, it does not make sense");
|
||||
}
|
||||
constructor(json: dirrectjson[], owner: Localuser){
|
||||
constructor(json: dirrectjson[], owner: Localuser) {
|
||||
super(-1, owner, null);
|
||||
this.message_notifications = 0;
|
||||
this.owner = owner;
|
||||
|
@ -29,7 +29,7 @@ class Direct extends Guild{
|
|||
this.roleids = new Map();
|
||||
this.prevchannel = undefined;
|
||||
this.properties.name = I18n.getTranslation("DMs.name");
|
||||
for(const thing of json){
|
||||
for (const thing of json) {
|
||||
const temp = new Group(thing, this);
|
||||
this.channels.push(temp);
|
||||
this.channelids[temp.id] = temp;
|
||||
|
@ -37,7 +37,7 @@ class Direct extends Guild{
|
|||
}
|
||||
this.headchannels = this.channels;
|
||||
}
|
||||
createChannelpac(json: any){
|
||||
createChannelpac(json: any) {
|
||||
const thischannel = new Group(json, this);
|
||||
this.channelids[thischannel.id] = thischannel;
|
||||
this.channels.push(thischannel);
|
||||
|
@ -46,266 +46,270 @@ class Direct extends Guild{
|
|||
this.printServers();
|
||||
return thischannel;
|
||||
}
|
||||
delChannel(json: channeljson){
|
||||
delChannel(json: channeljson) {
|
||||
const channel = this.channelids[json.id];
|
||||
super.delChannel(json);
|
||||
if(channel){
|
||||
if (channel) {
|
||||
channel.del();
|
||||
}
|
||||
}
|
||||
getHTML(){
|
||||
const ddiv=document.createElement("div");
|
||||
const build=super.getHTML();
|
||||
const freindDiv=document.createElement("div");
|
||||
freindDiv.classList.add("liststyle","flexltr","friendsbutton");
|
||||
getHTML() {
|
||||
const ddiv = document.createElement("div");
|
||||
const build = super.getHTML();
|
||||
const freindDiv = document.createElement("div");
|
||||
freindDiv.classList.add("liststyle", "flexltr", "friendsbutton");
|
||||
|
||||
const icon=document.createElement("span");
|
||||
icon.classList.add("svgicon","svg-friends","space");
|
||||
const icon = document.createElement("span");
|
||||
icon.classList.add("svgicon", "svg-friends", "space");
|
||||
freindDiv.append(icon);
|
||||
|
||||
freindDiv.append(I18n.getTranslation("friends.friends"));
|
||||
ddiv.append(freindDiv);
|
||||
freindDiv.onclick=()=>{
|
||||
freindDiv.onclick = () => {
|
||||
this.loadChannel(null);
|
||||
}
|
||||
};
|
||||
|
||||
ddiv.append(build);
|
||||
return ddiv;
|
||||
}
|
||||
noChannel(addstate:boolean){
|
||||
if(addstate){
|
||||
history.pushState([this.id,undefined], "", "/channels/" + this.id);
|
||||
noChannel(addstate: boolean) {
|
||||
if (addstate) {
|
||||
history.pushState([this.id, undefined], "", "/channels/" + this.id);
|
||||
}
|
||||
this.localuser.pageTitle(I18n.getTranslation("friends.friendlist"));
|
||||
const channelTopic = document.getElementById("channelTopic") as HTMLSpanElement;
|
||||
channelTopic.removeAttribute("hidden");
|
||||
channelTopic.textContent="";
|
||||
channelTopic.textContent = "";
|
||||
|
||||
const loading = document.getElementById("loadingdiv") as HTMLDivElement;
|
||||
loading.classList.remove("loading");
|
||||
this.localuser.getSidePannel();
|
||||
|
||||
const messages = document.getElementById("channelw") as HTMLDivElement;
|
||||
for(const thing of Array.from(messages.getElementsByClassName("messagecontainer"))){
|
||||
for (const thing of Array.from(messages.getElementsByClassName("messagecontainer"))) {
|
||||
thing.remove();
|
||||
}
|
||||
const container=document.createElement("div");
|
||||
container.classList.add("messagecontainer","flexttb","friendcontainer")
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("messagecontainer", "flexttb", "friendcontainer");
|
||||
|
||||
messages.append(container);
|
||||
const checkVoid=()=>{
|
||||
if(this.localuser.channelfocus!==undefined||this.localuser.lookingguild!==this){
|
||||
this.localuser.relationshipsUpdate=()=>{};
|
||||
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");
|
||||
};
|
||||
function genuserstrip(user: User, icons: HTMLElement): HTMLElement {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("flexltr", "liststyle");
|
||||
user.bind(div);
|
||||
div.append(user.buildpfp());
|
||||
|
||||
const userinfos=document.createElement("div");
|
||||
const userinfos = document.createElement("div");
|
||||
userinfos.classList.add("flexttb");
|
||||
const username=document.createElement("span");
|
||||
username.textContent=user.name;
|
||||
userinfos.append(username,user.getStatus());
|
||||
const username = document.createElement("span");
|
||||
username.textContent = user.name;
|
||||
userinfos.append(username, user.getStatus());
|
||||
div.append(userinfos);
|
||||
User.contextmenu.bindContextmenu(div,user,undefined);
|
||||
userinfos.style.flexGrow="1";
|
||||
User.contextmenu.bindContextmenu(div, user, undefined);
|
||||
userinfos.style.flexGrow = "1";
|
||||
|
||||
div.append(icons);
|
||||
return div;
|
||||
}
|
||||
{
|
||||
//TODO update on users coming online
|
||||
const online=document.createElement("button");
|
||||
online.textContent=I18n.getTranslation("friends.online");
|
||||
const online = document.createElement("button");
|
||||
online.textContent = I18n.getTranslation("friends.online");
|
||||
channelTopic.append(online);
|
||||
const genOnline=()=>{
|
||||
this.localuser.relationshipsUpdate=genOnline;
|
||||
const genOnline = () => {
|
||||
this.localuser.relationshipsUpdate = genOnline;
|
||||
checkVoid();
|
||||
container.innerHTML="";
|
||||
container.innerHTML = "";
|
||||
container.append(I18n.getTranslation("friends.online:"));
|
||||
for(const user of this.localuser.inrelation){
|
||||
if(user.relationshipType===1&&user.online){
|
||||
const buttonc=document.createElement("div");
|
||||
const button1=document.createElement("span");
|
||||
button1.classList.add("svg-frmessage","svgicon");
|
||||
for (const user of this.localuser.inrelation) {
|
||||
if (user.relationshipType === 1 && user.online) {
|
||||
const buttonc = document.createElement("div");
|
||||
const button1 = document.createElement("span");
|
||||
button1.classList.add("svg-frmessage", "svgicon");
|
||||
buttonc.append(button1);
|
||||
buttonc.classList.add("friendlyButton");
|
||||
buttonc.onclick=(e)=>{
|
||||
buttonc.onclick = (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
user.opendm();
|
||||
}
|
||||
container.append(genuserstrip(user,buttonc));
|
||||
};
|
||||
container.append(genuserstrip(user, buttonc));
|
||||
}
|
||||
}
|
||||
}
|
||||
online.onclick=genOnline;
|
||||
};
|
||||
online.onclick = genOnline;
|
||||
genOnline();
|
||||
}
|
||||
{
|
||||
const all=document.createElement("button");
|
||||
all.textContent=I18n.getTranslation("friends.all");
|
||||
const genAll=()=>{
|
||||
this.localuser.relationshipsUpdate=genAll;
|
||||
const all = document.createElement("button");
|
||||
all.textContent = I18n.getTranslation("friends.all");
|
||||
const genAll = () => {
|
||||
this.localuser.relationshipsUpdate = genAll;
|
||||
checkVoid();
|
||||
container.innerHTML="";
|
||||
container.innerHTML = "";
|
||||
container.append(I18n.getTranslation("friends.all:"));
|
||||
for(const user of this.localuser.inrelation){
|
||||
if(user.relationshipType===1){
|
||||
const buttonc=document.createElement("div");
|
||||
const button1=document.createElement("span");
|
||||
button1.classList.add("svg-frmessage","svgicon");
|
||||
for (const user of this.localuser.inrelation) {
|
||||
if (user.relationshipType === 1) {
|
||||
const buttonc = document.createElement("div");
|
||||
const button1 = document.createElement("span");
|
||||
button1.classList.add("svg-frmessage", "svgicon");
|
||||
buttonc.append(button1);
|
||||
buttonc.classList.add("friendlyButton");
|
||||
buttonc.onclick=(e)=>{
|
||||
buttonc.onclick = (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
user.opendm();
|
||||
}
|
||||
container.append(genuserstrip(user,buttonc));
|
||||
};
|
||||
container.append(genuserstrip(user, buttonc));
|
||||
}
|
||||
}
|
||||
}
|
||||
all.onclick=genAll;
|
||||
};
|
||||
all.onclick = genAll;
|
||||
channelTopic.append(all);
|
||||
}
|
||||
{
|
||||
const pending=document.createElement("button");
|
||||
pending.textContent=I18n.getTranslation("friends.pending");
|
||||
const genPending=()=>{
|
||||
this.localuser.relationshipsUpdate=genPending;
|
||||
const pending = document.createElement("button");
|
||||
pending.textContent = I18n.getTranslation("friends.pending");
|
||||
const genPending = () => {
|
||||
this.localuser.relationshipsUpdate = genPending;
|
||||
checkVoid();
|
||||
container.innerHTML="";
|
||||
container.innerHTML = "";
|
||||
container.append(I18n.getTranslation("friends.pending:"));
|
||||
for(const user of this.localuser.inrelation){
|
||||
if(user.relationshipType===3||user.relationshipType===4){
|
||||
const buttons=document.createElement("div");
|
||||
for (const user of this.localuser.inrelation) {
|
||||
if (user.relationshipType === 3 || user.relationshipType === 4) {
|
||||
const buttons = document.createElement("div");
|
||||
buttons.classList.add("flexltr");
|
||||
const buttonc=document.createElement("div");
|
||||
const button1=document.createElement("span");
|
||||
button1.classList.add("svgicon","svg-x");
|
||||
if(user.relationshipType===3){
|
||||
const buttonc=document.createElement("div");
|
||||
const button2=document.createElement("span");
|
||||
button2.classList.add("svgicon","svg-x");
|
||||
const buttonc = document.createElement("div");
|
||||
const button1 = document.createElement("span");
|
||||
button1.classList.add("svgicon", "svg-x");
|
||||
if (user.relationshipType === 3) {
|
||||
const buttonc = document.createElement("div");
|
||||
const button2 = document.createElement("span");
|
||||
button2.classList.add("svgicon", "svg-x");
|
||||
button2.classList.add("svg-addfriend");
|
||||
buttonc.append(button2);
|
||||
buttonc.classList.add("friendlyButton");
|
||||
buttonc.append(button2);
|
||||
buttons.append(buttonc);
|
||||
buttonc.onclick=(e)=>{
|
||||
buttonc.onclick = (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
user.changeRelationship(1);
|
||||
outerDiv.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
buttonc.append(button1);
|
||||
buttonc.classList.add("friendlyButton");
|
||||
buttonc.onclick=(e)=>{
|
||||
buttonc.onclick = (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
user.changeRelationship(0);
|
||||
outerDiv.remove();
|
||||
}
|
||||
};
|
||||
buttons.append(buttonc);
|
||||
const outerDiv=genuserstrip(user,buttons);
|
||||
const outerDiv = genuserstrip(user, buttons);
|
||||
container.append(outerDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
pending.onclick=genPending;
|
||||
};
|
||||
pending.onclick = genPending;
|
||||
channelTopic.append(pending);
|
||||
}
|
||||
{
|
||||
const blocked=document.createElement("button");
|
||||
blocked.textContent=I18n.getTranslation("friends.blocked");
|
||||
const blocked = document.createElement("button");
|
||||
blocked.textContent = I18n.getTranslation("friends.blocked");
|
||||
|
||||
const genBlocked=()=>{
|
||||
this.localuser.relationshipsUpdate=genBlocked;
|
||||
const genBlocked = () => {
|
||||
this.localuser.relationshipsUpdate = genBlocked;
|
||||
checkVoid();
|
||||
container.innerHTML="";
|
||||
container.innerHTML = "";
|
||||
container.append(I18n.getTranslation("friends.blockedusers"));
|
||||
for(const user of this.localuser.inrelation){
|
||||
if(user.relationshipType===2){
|
||||
const buttonc=document.createElement("div");
|
||||
const button1=document.createElement("span");
|
||||
button1.classList.add("svg-x","svgicon");
|
||||
for (const user of this.localuser.inrelation) {
|
||||
if (user.relationshipType === 2) {
|
||||
const buttonc = document.createElement("div");
|
||||
const button1 = document.createElement("span");
|
||||
button1.classList.add("svg-x", "svgicon");
|
||||
buttonc.append(button1);
|
||||
buttonc.classList.add("friendlyButton");
|
||||
buttonc.onclick=(e)=>{
|
||||
buttonc.onclick = (e) => {
|
||||
user.changeRelationship(0);
|
||||
e.stopImmediatePropagation();
|
||||
outerDiv.remove();
|
||||
}
|
||||
const outerDiv=genuserstrip(user,buttonc);
|
||||
};
|
||||
const outerDiv = genuserstrip(user, buttonc);
|
||||
container.append(outerDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
blocked.onclick=genBlocked;
|
||||
};
|
||||
blocked.onclick = genBlocked;
|
||||
channelTopic.append(blocked);
|
||||
}
|
||||
{
|
||||
const add=document.createElement("button");
|
||||
add.textContent=I18n.getTranslation("friends.addfriend");
|
||||
add.onclick=()=>{
|
||||
this.localuser.relationshipsUpdate=()=>{};
|
||||
container.innerHTML="";
|
||||
const float=new Float("");
|
||||
const options=float.options;
|
||||
const form=options.addForm("",(e:any)=>{
|
||||
console.log(e);
|
||||
if(e.code===404){
|
||||
throw new FormError(text,I18n.getTranslation("friends.notfound"));
|
||||
}else if(e.code===400){
|
||||
throw new FormError(text,e.message.split("Error: ")[1]);
|
||||
}else{
|
||||
const box=text.input.deref();
|
||||
if(!box)return;
|
||||
box.value="";
|
||||
}
|
||||
},{
|
||||
method:"POST",
|
||||
fetchURL:this.info.api+"/users/@me/relationships",
|
||||
headers:this.headers
|
||||
});
|
||||
const text=form.addTextInput(I18n.getTranslation("friends.addfriendpromt"),"username");
|
||||
form.addPreprocessor((obj:any)=>{
|
||||
const [username,discriminator]=obj.username.split("#");
|
||||
obj.username=username;
|
||||
obj.discriminator=discriminator;
|
||||
if(!discriminator){
|
||||
throw new FormError(text,I18n.getTranslation("friends.discnotfound"));
|
||||
const add = document.createElement("button");
|
||||
add.textContent = I18n.getTranslation("friends.addfriend");
|
||||
add.onclick = () => {
|
||||
this.localuser.relationshipsUpdate = () => {};
|
||||
container.innerHTML = "";
|
||||
const float = new Float("");
|
||||
const options = float.options;
|
||||
const form = options.addForm(
|
||||
"",
|
||||
(e: any) => {
|
||||
console.log(e);
|
||||
if (e.code === 404) {
|
||||
throw new FormError(text, I18n.getTranslation("friends.notfound"));
|
||||
} else if (e.code === 400) {
|
||||
throw new FormError(text, e.message.split("Error: ")[1]);
|
||||
} else {
|
||||
const box = text.input.deref();
|
||||
if (!box) return;
|
||||
box.value = "";
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
fetchURL: this.info.api + "/users/@me/relationships",
|
||||
headers: this.headers,
|
||||
},
|
||||
);
|
||||
const text = form.addTextInput(I18n.getTranslation("friends.addfriendpromt"), "username");
|
||||
form.addPreprocessor((obj: any) => {
|
||||
const [username, discriminator] = obj.username.split("#");
|
||||
obj.username = username;
|
||||
obj.discriminator = discriminator;
|
||||
if (!discriminator) {
|
||||
throw new FormError(text, I18n.getTranslation("friends.discnotfound"));
|
||||
}
|
||||
});
|
||||
container.append(float.generateHTML());
|
||||
}
|
||||
};
|
||||
channelTopic.append(add);
|
||||
}
|
||||
}
|
||||
get mentions(){
|
||||
let mentions=0;
|
||||
for(const thing of this.localuser.inrelation){
|
||||
if(thing.relationshipType===3){
|
||||
mentions+=1;
|
||||
get mentions() {
|
||||
let mentions = 0;
|
||||
for (const thing of this.localuser.inrelation) {
|
||||
if (thing.relationshipType === 3) {
|
||||
mentions += 1;
|
||||
}
|
||||
}
|
||||
return mentions;
|
||||
}
|
||||
giveMember(_member: memberjson){
|
||||
giveMember(_member: memberjson) {
|
||||
throw new Error("not a real guild, can't give member object");
|
||||
}
|
||||
getRole(/* ID: string */){
|
||||
getRole(/* ID: string */) {
|
||||
return null;
|
||||
}
|
||||
hasRole(/* r: string */){
|
||||
hasRole(/* r: string */) {
|
||||
return false;
|
||||
}
|
||||
isAdmin(){
|
||||
isAdmin() {
|
||||
return false;
|
||||
}
|
||||
unreaddms(){
|
||||
for(const thing of this.channels){
|
||||
unreaddms() {
|
||||
for (const thing of this.channels) {
|
||||
(thing as Group).unreads();
|
||||
}
|
||||
}
|
||||
|
@ -335,34 +339,46 @@ dmPermissions.setPermission("STREAM", 1);
|
|||
dmPermissions.setPermission("USE_VAD", 1);
|
||||
|
||||
// @ts-ignore I need to look into this lol
|
||||
class Group extends Channel{
|
||||
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){
|
||||
navigator.clipboard.writeText(this.id);
|
||||
});
|
||||
static setupcontextmenu() {
|
||||
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.readbottom();
|
||||
});
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("DMs.markRead"),
|
||||
function (this: Group) {
|
||||
this.readbottom();
|
||||
},
|
||||
);
|
||||
|
||||
this.contextmenu.addbutton(()=>I18n.getTranslation("DMs.close"), function(this: Group){
|
||||
this.deleteChannel();
|
||||
});
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("DMs.close"),
|
||||
function (this: Group) {
|
||||
this.deleteChannel();
|
||||
},
|
||||
);
|
||||
|
||||
this.contextmenu.addbutton(()=>I18n.getTranslation("user.copyId"), function(){
|
||||
navigator.clipboard.writeText(this.user.id);
|
||||
});
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("user.copyId"),
|
||||
function () {
|
||||
navigator.clipboard.writeText(this.user.id);
|
||||
},
|
||||
);
|
||||
}
|
||||
constructor(json: dirrectjson, owner: Direct){
|
||||
constructor(json: dirrectjson, owner: Direct) {
|
||||
super(-1, owner, json.id);
|
||||
this.owner = owner;
|
||||
this.headers = this.guild.headers;
|
||||
this.name = json.recipients[0]?.username;
|
||||
if(json.recipients[0]){
|
||||
if (json.recipients[0]) {
|
||||
this.user = new User(json.recipients[0], this.localuser);
|
||||
}else{
|
||||
} else {
|
||||
this.user = this.localuser.user;
|
||||
}
|
||||
this.name ??= this.localuser.user.username;
|
||||
|
@ -376,26 +392,26 @@ class Group extends Channel{
|
|||
this.setUpInfiniteScroller();
|
||||
this.updatePosition();
|
||||
}
|
||||
updatePosition(){
|
||||
if(this.lastmessageid){
|
||||
updatePosition() {
|
||||
if (this.lastmessageid) {
|
||||
this.position = SnowFlake.stringToUnixTime(this.lastmessageid);
|
||||
}else{
|
||||
} else {
|
||||
this.position = 0;
|
||||
}
|
||||
this.position = -Math.max(this.position, this.getUnixTime());
|
||||
}
|
||||
createguildHTML(){
|
||||
createguildHTML() {
|
||||
const div = document.createElement("div");
|
||||
Group.contextmenu.bindContextmenu(div, this,undefined);
|
||||
Group.contextmenu.bindContextmenu(div, this, undefined);
|
||||
this.html = new WeakRef(div);
|
||||
div.classList.add("flexltr","liststyle");
|
||||
div.classList.add("flexltr", "liststyle");
|
||||
const myhtml = document.createElement("span");
|
||||
myhtml.classList.add("ellipsis");
|
||||
myhtml.textContent = this.name;
|
||||
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;
|
||||
|
@ -403,56 +419,55 @@ class Group extends Channel{
|
|||
|
||||
return div;
|
||||
}
|
||||
async getHTML(addstate=true){
|
||||
async getHTML(addstate = true) {
|
||||
const id = ++Channel.genid;
|
||||
if(this.localuser.channelfocus){
|
||||
if (this.localuser.channelfocus) {
|
||||
this.localuser.channelfocus.infinite.delete();
|
||||
}
|
||||
if(this.guild !== this.localuser.lookingguild){
|
||||
if (this.guild !== this.localuser.lookingguild) {
|
||||
this.guild.loadGuild();
|
||||
}
|
||||
this.guild.prevchannel = this;
|
||||
this.localuser.channelfocus = this;
|
||||
const prom = this.infinite.delete();
|
||||
if(addstate){
|
||||
history.pushState([this.guild_id,this.id], "", "/channels/" + this.guild_id + "/" + this.id);
|
||||
if (addstate) {
|
||||
history.pushState([this.guild_id, this.id], "", "/channels/" + this.guild_id + "/" + this.id);
|
||||
}
|
||||
this.localuser.pageTitle("@" + this.name);
|
||||
(document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden","");
|
||||
(document.getElementById("channelTopic") as HTMLElement).setAttribute("hidden", "");
|
||||
|
||||
const loading = document.getElementById("loadingdiv") as HTMLDivElement;
|
||||
Channel.regenLoadingMessages();
|
||||
|
||||
loading.classList.add("loading");
|
||||
this.rendertyping();
|
||||
(document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true;
|
||||
(document.getElementById("upload") as HTMLElement).style.visibility="visible";
|
||||
(document.getElementById("typediv") as HTMLElement).style.visibility="visible";
|
||||
(document.getElementById("typebox") as HTMLDivElement).contentEditable = "" + true;
|
||||
(document.getElementById("upload") as HTMLElement).style.visibility = "visible";
|
||||
(document.getElementById("typediv") as HTMLElement).style.visibility = "visible";
|
||||
(document.getElementById("typebox") as HTMLDivElement).focus();
|
||||
await this.putmessages();
|
||||
await prom;
|
||||
this.localuser.getSidePannel();
|
||||
if(id !== Channel.genid){
|
||||
if (id !== Channel.genid) {
|
||||
return;
|
||||
}
|
||||
this.buildmessages();
|
||||
|
||||
}
|
||||
messageCreate(messagep: { d: messagejson }){
|
||||
messageCreate(messagep: {d: messagejson}) {
|
||||
this.mentions++;
|
||||
const messagez = new Message(messagep.d, this);
|
||||
if(this.lastmessageid){
|
||||
if (this.lastmessageid) {
|
||||
this.idToNext.set(this.lastmessageid, messagez.id);
|
||||
this.idToPrev.set(messagez.id, this.lastmessageid);
|
||||
}
|
||||
this.lastmessageid = messagez.id;
|
||||
if(messagez.author === this.localuser.user){
|
||||
if (messagez.author === this.localuser.user) {
|
||||
this.lastreadmessageid = messagez.id;
|
||||
if(this.myhtml){
|
||||
if (this.myhtml) {
|
||||
this.myhtml.classList.remove("cunread");
|
||||
}
|
||||
}else{
|
||||
if(this.myhtml){
|
||||
} else {
|
||||
if (this.myhtml) {
|
||||
this.myhtml.classList.add("cunread");
|
||||
}
|
||||
}
|
||||
|
@ -460,55 +475,55 @@ class Group extends Channel{
|
|||
this.updatePosition();
|
||||
this.infinite.addedBottom();
|
||||
this.guild.sortchannels();
|
||||
if(this.myhtml){
|
||||
if (this.myhtml) {
|
||||
const parrent = this.myhtml.parentElement as HTMLElement;
|
||||
parrent.prepend(this.myhtml);
|
||||
}
|
||||
if(this === this.localuser.channelfocus){
|
||||
if(!this.infinitefocus){
|
||||
if (this === this.localuser.channelfocus) {
|
||||
if (!this.infinitefocus) {
|
||||
this.tryfocusinfinate();
|
||||
}
|
||||
this.infinite.addedBottom();
|
||||
}
|
||||
this.unreads();
|
||||
if(messagez.author === this.localuser.user){
|
||||
this.mentions=0;
|
||||
if (messagez.author === this.localuser.user) {
|
||||
this.mentions = 0;
|
||||
return;
|
||||
}
|
||||
if(this.localuser.lookingguild?.prevchannel === this && document.hasFocus()){
|
||||
if (this.localuser.lookingguild?.prevchannel === this && document.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
if(this.notification === "all"){
|
||||
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){
|
||||
notititle(message: Message) {
|
||||
return message.author.username;
|
||||
}
|
||||
readbottom(){
|
||||
readbottom() {
|
||||
super.readbottom();
|
||||
this.unreads();
|
||||
}
|
||||
all: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
|
||||
noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
|
||||
del(){
|
||||
del() {
|
||||
const all = this.all.deref();
|
||||
if(all){
|
||||
if (all) {
|
||||
all.remove();
|
||||
}
|
||||
if(this.myhtml){
|
||||
if (this.myhtml) {
|
||||
this.myhtml.remove();
|
||||
}
|
||||
}
|
||||
unreads(){
|
||||
unreads() {
|
||||
const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime
|
||||
const current = this.all.deref();
|
||||
if(this.hasunreads){
|
||||
if (this.hasunreads) {
|
||||
{
|
||||
const noti = this.noti.deref();
|
||||
if(noti){
|
||||
if (noti) {
|
||||
noti.textContent = this.mentions + "";
|
||||
return;
|
||||
}
|
||||
|
@ -525,24 +540,24 @@ 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;
|
||||
toggle.checked = true;
|
||||
};
|
||||
}else if(current){
|
||||
} else if (current) {
|
||||
current.remove();
|
||||
}else{
|
||||
} else {
|
||||
}
|
||||
}
|
||||
isAdmin(): boolean{
|
||||
isAdmin(): boolean {
|
||||
return false;
|
||||
}
|
||||
hasPermission(name: string): boolean{
|
||||
hasPermission(name: string): boolean {
|
||||
return dmPermissions.hasPermission(name);
|
||||
}
|
||||
}
|
||||
export{ Direct, Group };
|
||||
export {Direct, Group};
|
||||
|
||||
Group.setupcontextmenu()
|
||||
Group.setupcontextmenu();
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
class ImagesDisplay{
|
||||
images:string[];
|
||||
index=0;
|
||||
constructor(srcs:string[],index=0){
|
||||
this.images=srcs;
|
||||
this.index=index;
|
||||
class ImagesDisplay {
|
||||
images: string[];
|
||||
index = 0;
|
||||
constructor(srcs: string[], index = 0) {
|
||||
this.images = srcs;
|
||||
this.index = index;
|
||||
}
|
||||
weakbg=new WeakRef<HTMLElement>(document.createElement("div"));
|
||||
get background():HTMLElement|undefined{
|
||||
weakbg = new WeakRef<HTMLElement>(document.createElement("div"));
|
||||
get background(): HTMLElement | undefined {
|
||||
return this.weakbg.deref();
|
||||
}
|
||||
set background(e:HTMLElement){
|
||||
this.weakbg=new WeakRef(e);
|
||||
set background(e: HTMLElement) {
|
||||
this.weakbg = new WeakRef(e);
|
||||
}
|
||||
makeHTML():HTMLElement{
|
||||
makeHTML(): HTMLElement {
|
||||
//TODO this should be able to display more than one image at a time lol
|
||||
const image= document.createElement("img");
|
||||
image.src=this.images[this.index];
|
||||
image.classList.add("imgfit","centeritem");
|
||||
const image = document.createElement("img");
|
||||
image.src = this.images[this.index];
|
||||
image.classList.add("imgfit", "centeritem");
|
||||
return image;
|
||||
}
|
||||
show(){
|
||||
show() {
|
||||
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);
|
||||
}
|
||||
hide(){
|
||||
if(this.background){
|
||||
hide() {
|
||||
if (this.background) {
|
||||
this.background.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
export{ImagesDisplay}
|
||||
export {ImagesDisplay};
|
||||
|
|
|
@ -1,94 +1,85 @@
|
|||
import{ Message }from"./message.js";
|
||||
import{ MarkDown }from"./markdown.js";
|
||||
import{ embedjson, invitejson }from"./jsontypes.js";
|
||||
import{ getapiurls, getInstances }from"./utils/utils.js";
|
||||
import{ Guild }from"./guild.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
import { ImagesDisplay } from "./disimg.js";
|
||||
import {Message} from "./message.js";
|
||||
import {MarkDown} from "./markdown.js";
|
||||
import {embedjson, invitejson} from "./jsontypes.js";
|
||||
import {getapiurls, getInstances} from "./utils/utils.js";
|
||||
import {Guild} from "./guild.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {ImagesDisplay} from "./disimg.js";
|
||||
|
||||
class Embed{
|
||||
class Embed {
|
||||
type: string;
|
||||
owner: Message;
|
||||
json: embedjson;
|
||||
constructor(json: embedjson, owner: Message){
|
||||
constructor(json: embedjson, owner: Message) {
|
||||
this.type = this.getType(json);
|
||||
this.owner = owner;
|
||||
this.json = json;
|
||||
}
|
||||
getType(json: embedjson){
|
||||
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)){
|
||||
for (const instance of instances) {
|
||||
if (instance.url && URL.canParse(instance.url)) {
|
||||
const IUrl = new URL(instance.url);
|
||||
const params = new URLSearchParams(Url.search);
|
||||
let host: string;
|
||||
if(params.has("instance")){
|
||||
if (params.has("instance")) {
|
||||
const url = params.get("instance") as string;
|
||||
if(URL.canParse(url)){
|
||||
if (URL.canParse(url)) {
|
||||
host = new URL(url).host;
|
||||
}else{
|
||||
} else {
|
||||
host = Url.host;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
host = Url.host;
|
||||
}
|
||||
if(IUrl.host === host){
|
||||
const code =
|
||||
Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
||||
if (IUrl.host === host) {
|
||||
const code = Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
||||
json.invite = {
|
||||
url: instance.url,
|
||||
code,
|
||||
};
|
||||
return"invite";
|
||||
return "invite";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.type || "rich";
|
||||
}
|
||||
generateHTML(){
|
||||
switch(this.type){
|
||||
case"rich":
|
||||
return this.generateRich();
|
||||
case"image":
|
||||
return this.generateImage();
|
||||
case"invite":
|
||||
return this.generateInvite();
|
||||
case"link":
|
||||
return this.generateLink();
|
||||
case"video":
|
||||
case"article":
|
||||
return this.generateArticle();
|
||||
default:
|
||||
console.warn(
|
||||
`unsupported embed type ${this.type}, please add support dev :3`,
|
||||
this.json
|
||||
);
|
||||
return document.createElement("div"); //prevent errors by giving blank div
|
||||
generateHTML() {
|
||||
switch (this.type) {
|
||||
case "rich":
|
||||
return this.generateRich();
|
||||
case "image":
|
||||
return this.generateImage();
|
||||
case "invite":
|
||||
return this.generateInvite();
|
||||
case "link":
|
||||
return this.generateLink();
|
||||
case "video":
|
||||
case "article":
|
||||
return this.generateArticle();
|
||||
default:
|
||||
console.warn(`unsupported embed type ${this.type}, please add support dev :3`, this.json);
|
||||
return document.createElement("div"); //prevent errors by giving blank div
|
||||
}
|
||||
}
|
||||
get message(){
|
||||
get message() {
|
||||
return this.owner;
|
||||
}
|
||||
get channel(){
|
||||
get channel() {
|
||||
return this.message.channel;
|
||||
}
|
||||
get guild(){
|
||||
get guild() {
|
||||
return this.channel.guild;
|
||||
}
|
||||
get localuser(){
|
||||
get localuser() {
|
||||
return this.guild.localuser;
|
||||
}
|
||||
generateRich(){
|
||||
generateRich() {
|
||||
const div = document.createElement("div");
|
||||
if(this.json.color){
|
||||
if (this.json.color) {
|
||||
div.style.backgroundColor = "#" + this.json.color.toString(16);
|
||||
}
|
||||
div.classList.add("embed-color");
|
||||
|
@ -97,9 +88,9 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
embed.classList.add("embed");
|
||||
div.append(embed);
|
||||
|
||||
if(this.json.author){
|
||||
if (this.json.author) {
|
||||
const authorline = document.createElement("div");
|
||||
if(this.json.author.icon_url){
|
||||
if (this.json.author.icon_url) {
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("embedimg");
|
||||
img.src = this.json.author.icon_url;
|
||||
|
@ -107,31 +98,31 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
}
|
||||
const a = document.createElement("a");
|
||||
a.textContent = this.json.author.name as string;
|
||||
if(this.json.author.url){
|
||||
if (this.json.author.url) {
|
||||
MarkDown.safeLink(a, this.json.author.url);
|
||||
}
|
||||
a.classList.add("username");
|
||||
authorline.append(a);
|
||||
embed.append(authorline);
|
||||
}
|
||||
if(this.json.title){
|
||||
if (this.json.title) {
|
||||
const title = document.createElement("a");
|
||||
title.append(new MarkDown(this.json.title, this.channel).makeHTML());
|
||||
if(this.json.url){
|
||||
if (this.json.url) {
|
||||
MarkDown.safeLink(title, this.json.url);
|
||||
}
|
||||
title.classList.add("embedtitle");
|
||||
embed.append(title);
|
||||
}
|
||||
if(this.json.description){
|
||||
if (this.json.description) {
|
||||
const p = document.createElement("p");
|
||||
p.append(new MarkDown(this.json.description, this.channel).makeHTML());
|
||||
embed.append(p);
|
||||
}
|
||||
|
||||
embed.append(document.createElement("br"));
|
||||
if(this.json.fields){
|
||||
for(const thing of this.json.fields){
|
||||
if (this.json.fields) {
|
||||
for (const thing of this.json.fields) {
|
||||
const div = document.createElement("div");
|
||||
const b = document.createElement("b");
|
||||
b.textContent = thing.name;
|
||||
|
@ -141,31 +132,31 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
p.classList.add("embedp");
|
||||
div.append(p);
|
||||
|
||||
if(thing.inline){
|
||||
if (thing.inline) {
|
||||
div.classList.add("inline");
|
||||
}
|
||||
embed.append(div);
|
||||
}
|
||||
}
|
||||
if(this.json.footer || this.json.timestamp){
|
||||
if (this.json.footer || this.json.timestamp) {
|
||||
const footer = document.createElement("div");
|
||||
if(this.json?.footer?.icon_url){
|
||||
if (this.json?.footer?.icon_url) {
|
||||
const img = document.createElement("img");
|
||||
img.src = this.json.footer.icon_url;
|
||||
img.classList.add("embedicon");
|
||||
footer.append(img);
|
||||
}
|
||||
if(this.json?.footer?.text){
|
||||
if (this.json?.footer?.text) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = this.json.footer.text;
|
||||
footer.append(span);
|
||||
}
|
||||
if(this.json?.footer && this.json?.timestamp){
|
||||
if (this.json?.footer && this.json?.timestamp) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = " • ";
|
||||
footer.append(span);
|
||||
}
|
||||
if(this.json?.timestamp){
|
||||
if (this.json?.timestamp) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = new Date(this.json.timestamp).toLocaleString();
|
||||
footer.append(span);
|
||||
|
@ -174,15 +165,15 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
}
|
||||
return div;
|
||||
}
|
||||
generateImage(){
|
||||
generateImage() {
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("messageimg");
|
||||
img.onclick = function(){
|
||||
img.onclick = function () {
|
||||
const full = new ImagesDisplay([img.src]);
|
||||
full.show();
|
||||
};
|
||||
img.src = this.json.thumbnail.proxy_url;
|
||||
if(this.json.thumbnail.width){
|
||||
if (this.json.thumbnail.width) {
|
||||
let scale = 1;
|
||||
const max = 96 * 3;
|
||||
scale = Math.max(scale, this.json.thumbnail.width / max);
|
||||
|
@ -195,12 +186,12 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
console.log(this.json, "Image fix");
|
||||
return img;
|
||||
}
|
||||
generateLink(){
|
||||
generateLink() {
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("embed", "linkembed");
|
||||
const trtop = document.createElement("tr");
|
||||
table.append(trtop);
|
||||
if(this.json.url && this.json.title){
|
||||
if (this.json.url && this.json.title) {
|
||||
const td = document.createElement("td");
|
||||
const a = document.createElement("a");
|
||||
MarkDown.safeLink(a, this.json.url);
|
||||
|
@ -211,9 +202,9 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
{
|
||||
const td = document.createElement("td");
|
||||
const img = document.createElement("img");
|
||||
if(this.json.thumbnail){
|
||||
if (this.json.thumbnail) {
|
||||
img.classList.add("embedimg");
|
||||
img.onclick = function(){
|
||||
img.onclick = function () {
|
||||
const full = new ImagesDisplay([img.src]);
|
||||
full.show();
|
||||
};
|
||||
|
@ -224,7 +215,7 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
}
|
||||
const bottomtr = document.createElement("tr");
|
||||
const td = document.createElement("td");
|
||||
if(this.json.description){
|
||||
if (this.json.description) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = this.json.description;
|
||||
td.append(span);
|
||||
|
@ -233,59 +224,62 @@ Url.pathname.split("/")[Url.pathname.split("/").length - 1];
|
|||
table.append(bottomtr);
|
||||
return table;
|
||||
}
|
||||
invcache: [invitejson, { cdn: string; api: string }] | undefined;
|
||||
generateInvite(){
|
||||
if(this.invcache && (!this.json.invite || !this.localuser)){
|
||||
invcache: [invitejson, {cdn: string; api: string}] | undefined;
|
||||
generateInvite() {
|
||||
if (this.invcache && (!this.json.invite || !this.localuser)) {
|
||||
return this.generateLink();
|
||||
}
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("embed", "inviteEmbed", "flexttb");
|
||||
const json1 = this.json.invite;
|
||||
(async ()=>{
|
||||
(async () => {
|
||||
let json: invitejson;
|
||||
let info: { cdn: string; api: string };
|
||||
if(!this.invcache){
|
||||
if(!json1){
|
||||
div.classList.remove("embed", "inviteEmbed", "flexttb")
|
||||
let info: {cdn: string; api: string};
|
||||
if (!this.invcache) {
|
||||
if (!json1) {
|
||||
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")
|
||||
if (!tempinfo) {
|
||||
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")
|
||||
if (!res.ok) {
|
||||
div.classList.remove("embed", "inviteEmbed", "flexttb");
|
||||
div.append(this.generateLink());
|
||||
return;
|
||||
}
|
||||
json = (await res.json()) as invitejson;
|
||||
this.invcache = [json, info];
|
||||
}else{
|
||||
} else {
|
||||
[json, info] = this.invcache;
|
||||
}
|
||||
if(!json){
|
||||
if (!json) {
|
||||
div.append(this.generateLink());
|
||||
div.classList.remove("embed", "inviteEmbed", "flexttb")
|
||||
div.classList.remove("embed", "inviteEmbed", "flexttb");
|
||||
return;
|
||||
}
|
||||
if(json.guild.banner){
|
||||
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);
|
||||
|
@ -305,30 +299,30 @@ guild as invitejson["guild"] & { info: { cdn: string } }
|
|||
|
||||
div.append(iconrow);
|
||||
const h2 = document.createElement("h2");
|
||||
h2.textContent = I18n.getTranslation("invite.invitedBy",json.inviter.username);
|
||||
h2.textContent = I18n.getTranslation("invite.invitedBy", json.inviter.username);
|
||||
div.append(h2);
|
||||
const button = document.createElement("button");
|
||||
button.textContent = I18n.getTranslation("invite.accept");
|
||||
if(this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)){
|
||||
if (this.localuser.info.api.startsWith(info.api) && this.localuser.guildids.has(guild.id)) {
|
||||
button.textContent = I18n.getTranslation("invite.alreadyJoined");
|
||||
button.disabled = true;
|
||||
}
|
||||
button.classList.add("acceptinvbutton");
|
||||
div.append(button);
|
||||
button.onclick = _=>{
|
||||
if(this.localuser.info.api.startsWith(info.api)){
|
||||
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(_=>{
|
||||
if(_.message){
|
||||
.then((r) => r.json())
|
||||
.then((_) => {
|
||||
if (_.message) {
|
||||
alert(_.message);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
if(this.json.invite){
|
||||
} else {
|
||||
if (this.json.invite) {
|
||||
const params = new URLSearchParams("");
|
||||
params.set("instance", this.json.invite.url);
|
||||
const encoded = params.toString();
|
||||
|
@ -340,33 +334,33 @@ guild as invitejson["guild"] & { info: { cdn: string } }
|
|||
})();
|
||||
return div;
|
||||
}
|
||||
generateArticle(){
|
||||
generateArticle() {
|
||||
const colordiv = document.createElement("div");
|
||||
colordiv.style.backgroundColor = "#000000";
|
||||
colordiv.classList.add("embed-color");
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("embed");
|
||||
if(this.json.provider){
|
||||
if (this.json.provider) {
|
||||
const provider = document.createElement("p");
|
||||
provider.classList.add("provider");
|
||||
provider.textContent = this.json.provider.name;
|
||||
div.append(provider);
|
||||
}
|
||||
const a = document.createElement("a");
|
||||
if(this.json.url && this.json.url){
|
||||
if (this.json.url && this.json.url) {
|
||||
MarkDown.safeLink(a, this.json.url);
|
||||
a.textContent = this.json.url;
|
||||
div.append(a);
|
||||
}
|
||||
if(this.json.description){
|
||||
if (this.json.description) {
|
||||
const description = document.createElement("p");
|
||||
description.textContent = this.json.description;
|
||||
div.append(description);
|
||||
}
|
||||
if(this.json.thumbnail){
|
||||
if (this.json.thumbnail) {
|
||||
const img = document.createElement("img");
|
||||
if(this.json.thumbnail.width && this.json.thumbnail.width){
|
||||
if (this.json.thumbnail.width && this.json.thumbnail.width) {
|
||||
let scale = 1;
|
||||
const inch = 96;
|
||||
scale = Math.max(scale, this.json.thumbnail.width / inch / 4);
|
||||
|
@ -377,21 +371,21 @@ guild as invitejson["guild"] & { info: { cdn: string } }
|
|||
img.style.height = this.json.thumbnail.height + "px";
|
||||
}
|
||||
img.classList.add("bigembedimg");
|
||||
if(this.json.video){
|
||||
img.onclick = async ()=>{
|
||||
if(this.json.video){
|
||||
if (this.json.video) {
|
||||
img.onclick = async () => {
|
||||
if (this.json.video) {
|
||||
img.remove();
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = this.json.video.url + "?autoplay=1";
|
||||
if(this.json.thumbnail.width && this.json.thumbnail.width){
|
||||
if (this.json.thumbnail.width && this.json.thumbnail.width) {
|
||||
iframe.style.width = this.json.thumbnail.width + "px";
|
||||
iframe.style.height = this.json.thumbnail.height + "px";
|
||||
}
|
||||
div.append(iframe);
|
||||
}
|
||||
};
|
||||
}else{
|
||||
img.onclick = async ()=>{
|
||||
} else {
|
||||
img.onclick = async () => {
|
||||
const full = new ImagesDisplay([img.src]);
|
||||
full.show();
|
||||
};
|
||||
|
@ -403,4 +397,4 @@ guild as invitejson["guild"] & { info: { cdn: string } }
|
|||
return colordiv;
|
||||
}
|
||||
}
|
||||
export{ Embed };
|
||||
export {Embed};
|
||||
|
|
|
@ -1,75 +1,73 @@
|
|||
import{ Contextmenu }from"./contextmenu.js";
|
||||
import{ Guild }from"./guild.js";
|
||||
import { emojijson } from "./jsontypes.js";
|
||||
import{ Localuser }from"./localuser.js";
|
||||
import { BinRead } from "./utils/binaryUtils.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
import {Guild} from "./guild.js";
|
||||
import {emojijson} from "./jsontypes.js";
|
||||
import {Localuser} from "./localuser.js";
|
||||
import {BinRead} from "./utils/binaryUtils.js";
|
||||
|
||||
//I need to recompile the emoji format for translation
|
||||
class Emoji{
|
||||
class Emoji {
|
||||
static emojis: {
|
||||
name: string;
|
||||
emojis: {
|
||||
name: string;
|
||||
emoji: string;
|
||||
name: string;
|
||||
emoji: string;
|
||||
}[];
|
||||
}[];
|
||||
name: string;
|
||||
id?: string;
|
||||
emoji?:string;
|
||||
emoji?: string;
|
||||
animated: boolean;
|
||||
owner: Guild | Localuser;
|
||||
get guild(){
|
||||
if(this.owner instanceof Guild){
|
||||
get guild() {
|
||||
if (this.owner instanceof Guild) {
|
||||
return this.owner;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
get localuser(){
|
||||
if(this.owner instanceof Guild){
|
||||
get localuser() {
|
||||
if (this.owner instanceof Guild) {
|
||||
return this.owner.localuser;
|
||||
}else{
|
||||
} else {
|
||||
return this.owner;
|
||||
}
|
||||
}
|
||||
get info(){
|
||||
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;
|
||||
this.animated = json.animated || false;
|
||||
this.owner = owner;
|
||||
this.emoji=json.emoji;
|
||||
this.emoji = json.emoji;
|
||||
}
|
||||
getHTML(bigemoji: boolean = false){
|
||||
if(this.id){
|
||||
getHTML(bigemoji: boolean = false) {
|
||||
if (this.id) {
|
||||
const emojiElem = document.createElement("img");
|
||||
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;
|
||||
}else if(this.emoji){
|
||||
} else if (this.emoji) {
|
||||
const emojiElem = document.createElement("span");
|
||||
emojiElem.classList.add("md-emoji");
|
||||
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
|
||||
emojiElem.textContent=this.emoji;
|
||||
emojiElem.textContent = this.emoji;
|
||||
return emojiElem;
|
||||
}else{
|
||||
} else {
|
||||
throw new Error("This path should *never* be gone down, this means a malformed emoji");
|
||||
}
|
||||
}
|
||||
static decodeEmojiList(buffer: ArrayBuffer){
|
||||
const reader=new BinRead(buffer)
|
||||
const build: { name: string; emojis: { name: string; emoji: string }[] }[] = [];
|
||||
static decodeEmojiList(buffer: ArrayBuffer) {
|
||||
const reader = new BinRead(buffer);
|
||||
const build: {name: string; emojis: {name: string; emoji: string}[]}[] = [];
|
||||
let cats = reader.read16();
|
||||
|
||||
for(; cats !== 0; cats--){
|
||||
for (; cats !== 0; cats--) {
|
||||
const name = reader.readString16();
|
||||
const emojis: {
|
||||
name: string;
|
||||
|
@ -77,7 +75,7 @@ class Emoji{
|
|||
emoji: string;
|
||||
}[] = [];
|
||||
let emojinumber = reader.read16();
|
||||
for(; emojinumber !== 0; emojinumber--){
|
||||
for (; emojinumber !== 0; emojinumber--) {
|
||||
//console.log(emojis);
|
||||
const name = reader.readString8();
|
||||
const len = reader.read8();
|
||||
|
@ -96,22 +94,18 @@ class Emoji{
|
|||
}
|
||||
this.emojis = build;
|
||||
}
|
||||
static grabEmoji(){
|
||||
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,33 +124,39 @@ 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");
|
||||
|
||||
if(guild.properties.icon){
|
||||
if (guild.properties.icon) {
|
||||
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{
|
||||
} 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);
|
||||
}
|
||||
|
||||
selection.append(select);
|
||||
|
||||
const clickEvent = ()=>{
|
||||
const clickEvent = () => {
|
||||
title.textContent = guild.properties.name;
|
||||
body.innerHTML = "";
|
||||
for(const emojit of guild.emojis){
|
||||
for (const emojit of guild.emojis) {
|
||||
const emojiElem = document.createElement("div");
|
||||
emojiElem.classList.add("emojiSelect");
|
||||
|
||||
|
@ -166,14 +166,14 @@ class Emoji{
|
|||
name: emojit.name,
|
||||
animated: emojit.animated as boolean,
|
||||
},
|
||||
localuser
|
||||
localuser,
|
||||
);
|
||||
emojiElem.append(emojiClass.getHTML());
|
||||
body.append(emojiElem);
|
||||
|
||||
emojiElem.addEventListener("click", ()=>{
|
||||
emojiElem.addEventListener("click", () => {
|
||||
res(emojiClass);
|
||||
if(Contextmenu.currentmenu !== ""){
|
||||
if (Contextmenu.currentmenu !== "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
});
|
||||
|
@ -181,14 +181,14 @@ class Emoji{
|
|||
};
|
||||
|
||||
select.addEventListener("click", clickEvent);
|
||||
if(isFirst){
|
||||
if (isFirst) {
|
||||
clickEvent();
|
||||
isFirst = false;
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(()=>{
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
setTimeout(() => {
|
||||
if (Contextmenu.currentmenu != "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
document.body.append(menu);
|
||||
|
@ -197,29 +197,29 @@ class Emoji{
|
|||
}, 10);
|
||||
|
||||
let i = 0;
|
||||
for(const thing of Emoji.emojis){
|
||||
for (const thing of Emoji.emojis) {
|
||||
const select = document.createElement("div");
|
||||
select.textContent = thing.emojis[0].emoji;
|
||||
select.classList.add("emojiSelect");
|
||||
selection.append(select);
|
||||
const clickEvent = ()=>{
|
||||
const clickEvent = () => {
|
||||
title.textContent = thing.name;
|
||||
body.innerHTML = "";
|
||||
for(const emojit of thing.emojis){
|
||||
for (const emojit of thing.emojis) {
|
||||
const emoji = document.createElement("div");
|
||||
emoji.classList.add("emojiSelect");
|
||||
emoji.textContent = emojit.emoji;
|
||||
body.append(emoji);
|
||||
emoji.onclick = _=>{
|
||||
emoji.onclick = (_) => {
|
||||
res(emojit.emoji);
|
||||
if(Contextmenu.currentmenu !== ""){
|
||||
if (Contextmenu.currentmenu !== "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
select.onclick = clickEvent;
|
||||
if(i === 0){
|
||||
if (i === 0) {
|
||||
clickEvent();
|
||||
}
|
||||
i++;
|
||||
|
@ -228,40 +228,39 @@ class Emoji{
|
|||
menu.append(body);
|
||||
return promise;
|
||||
}
|
||||
static searchEmoji(search:string,localuser:Localuser,results=50):[Emoji,number][]{
|
||||
const ranked:[emojijson,number][]=[];
|
||||
function similar(json:emojijson){
|
||||
if(json.name.includes(search)){
|
||||
ranked.push([json,search.length/json.name.length]);
|
||||
static searchEmoji(search: string, localuser: Localuser, results = 50): [Emoji, number][] {
|
||||
const ranked: [emojijson, number][] = [];
|
||||
function similar(json: emojijson) {
|
||||
if (json.name.includes(search)) {
|
||||
ranked.push([json, search.length / json.name.length]);
|
||||
return true;
|
||||
}else if(json.name.toLowerCase().includes(search.toLowerCase())){
|
||||
ranked.push([json,search.length/json.name.length/1.4]);
|
||||
} else if (json.name.toLowerCase().includes(search.toLowerCase())) {
|
||||
ranked.push([json, search.length / json.name.length / 1.4]);
|
||||
return true;
|
||||
}else{
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for(const group of this.emojis){
|
||||
for(const emoji of group.emojis){
|
||||
similar(emoji)
|
||||
for (const group of this.emojis) {
|
||||
for (const emoji of group.emojis) {
|
||||
similar(emoji);
|
||||
}
|
||||
}
|
||||
const weakGuild=new WeakMap<emojijson,Guild>();
|
||||
for(const guild of localuser.guilds){
|
||||
if(guild.id!=="@me"&&guild.emojis.length!==0){
|
||||
for(const emoji of guild.emojis){
|
||||
if(similar(emoji)){
|
||||
weakGuild.set(emoji,guild);
|
||||
};
|
||||
const weakGuild = new WeakMap<emojijson, Guild>();
|
||||
for (const guild of localuser.guilds) {
|
||||
if (guild.id !== "@me" && guild.emojis.length !== 0) {
|
||||
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 [new Emoji(a[0],weakGuild.get(a[0])||localuser),a[1]];
|
||||
|
||||
})
|
||||
ranked.sort((a, b) => b[1] - a[1]);
|
||||
return ranked.splice(0, results).map((a) => {
|
||||
return [new Emoji(a[0], weakGuild.get(a[0]) || localuser), a[1]];
|
||||
});
|
||||
}
|
||||
}
|
||||
Emoji.grabEmoji();
|
||||
export{ Emoji };
|
||||
export {Emoji};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import{ Message }from"./message.js";
|
||||
import{ filejson }from"./jsontypes.js";
|
||||
import { ImagesDisplay } from "./disimg.js";
|
||||
import {Message} from "./message.js";
|
||||
import {filejson} from "./jsontypes.js";
|
||||
import {ImagesDisplay} from "./disimg.js";
|
||||
|
||||
class File{
|
||||
class File {
|
||||
owner: Message | null;
|
||||
id: string;
|
||||
filename: string;
|
||||
|
@ -12,7 +12,7 @@ class File{
|
|||
proxy_url: string | undefined;
|
||||
url: string;
|
||||
size: number;
|
||||
constructor(fileJSON: filejson, owner: Message | null){
|
||||
constructor(fileJSON: filejson, owner: Message | null) {
|
||||
this.owner = owner;
|
||||
this.id = fileJSON.id;
|
||||
this.filename = fileJSON.filename;
|
||||
|
@ -24,9 +24,9 @@ class File{
|
|||
this.content_type = fileJSON.content_type;
|
||||
this.size = fileJSON.size;
|
||||
}
|
||||
getHTML(temp: boolean = false): HTMLElement{
|
||||
getHTML(temp: boolean = false): HTMLElement {
|
||||
const src = this.proxy_url || this.url;
|
||||
if(this.width && this.height){
|
||||
if (this.width && this.height) {
|
||||
let scale = 1;
|
||||
const max = 96 * 3;
|
||||
scale = Math.max(scale, this.width / max);
|
||||
|
@ -34,35 +34,35 @@ class File{
|
|||
this.width /= scale;
|
||||
this.height /= scale;
|
||||
}
|
||||
if(this.content_type.startsWith("image/")){
|
||||
if (this.content_type.startsWith("image/")) {
|
||||
const div = document.createElement("div");
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("messageimg");
|
||||
div.classList.add("messageimgdiv");
|
||||
img.onclick = function(){
|
||||
img.onclick = function () {
|
||||
const full = new ImagesDisplay([img.src]);
|
||||
full.show();
|
||||
};
|
||||
img.src = src;
|
||||
div.append(img);
|
||||
if(this.width){
|
||||
if (this.width) {
|
||||
div.style.width = this.width + "px";
|
||||
div.style.height = this.height + "px";
|
||||
}
|
||||
return div;
|
||||
}else if(this.content_type.startsWith("video/")){
|
||||
} else if (this.content_type.startsWith("video/")) {
|
||||
const video = document.createElement("video");
|
||||
const source = document.createElement("source");
|
||||
source.src = src;
|
||||
video.append(source);
|
||||
source.type = this.content_type;
|
||||
video.controls = !temp;
|
||||
if(this.width && this.height){
|
||||
if (this.width && this.height) {
|
||||
video.width = this.width;
|
||||
video.height = this.height;
|
||||
}
|
||||
return video;
|
||||
}else if(this.content_type.startsWith("audio/")){
|
||||
} else if (this.content_type.startsWith("audio/")) {
|
||||
const audio = document.createElement("audio");
|
||||
const source = document.createElement("source");
|
||||
source.src = src;
|
||||
|
@ -70,11 +70,11 @@ class File{
|
|||
source.type = this.content_type;
|
||||
audio.controls = !temp;
|
||||
return audio;
|
||||
}else{
|
||||
} else {
|
||||
return this.createunknown();
|
||||
}
|
||||
}
|
||||
upHTML(files: Blob[], file: globalThis.File): HTMLElement{
|
||||
upHTML(files: Blob[], file: globalThis.File): HTMLElement {
|
||||
const div = document.createElement("div");
|
||||
const contained = this.getHTML(true);
|
||||
div.classList.add("containedFile");
|
||||
|
@ -82,9 +82,9 @@ class File{
|
|||
const controls = document.createElement("div");
|
||||
const garbage = document.createElement("button");
|
||||
const icon = document.createElement("span");
|
||||
icon.classList.add("svgicon","svg-delete");
|
||||
icon.classList.add("svgicon", "svg-delete");
|
||||
garbage.append(icon);
|
||||
garbage.onclick = _=>{
|
||||
garbage.onclick = (_) => {
|
||||
div.remove();
|
||||
files.splice(files.indexOf(file), 1);
|
||||
};
|
||||
|
@ -93,7 +93,7 @@ class File{
|
|||
controls.append(garbage);
|
||||
return div;
|
||||
}
|
||||
static initFromBlob(file: globalThis.File){
|
||||
static initFromBlob(file: globalThis.File) {
|
||||
return new File(
|
||||
{
|
||||
filename: file.name,
|
||||
|
@ -105,10 +105,10 @@ class File{
|
|||
url: URL.createObjectURL(file),
|
||||
proxy_url: undefined,
|
||||
},
|
||||
null
|
||||
null,
|
||||
);
|
||||
}
|
||||
createunknown(): HTMLElement{
|
||||
createunknown(): HTMLElement {
|
||||
console.log("🗎");
|
||||
const src = this.proxy_url || this.url;
|
||||
const div = document.createElement("table");
|
||||
|
@ -121,12 +121,12 @@ class File{
|
|||
fileicon.classList.add("fileicon");
|
||||
fileicon.rowSpan = 2;
|
||||
const nametd = document.createElement("td");
|
||||
if(src){
|
||||
if (src) {
|
||||
const a = document.createElement("a");
|
||||
a.href = src;
|
||||
a.textContent = this.filename;
|
||||
nametd.append(a);
|
||||
}else{
|
||||
} else {
|
||||
nametd.textContent = this.filename;
|
||||
}
|
||||
|
||||
|
@ -140,11 +140,13 @@ class File{
|
|||
div.appendChild(sizetr);
|
||||
return div;
|
||||
}
|
||||
static filesizehuman(fsize: number){
|
||||
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
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
export{ File };
|
||||
export {File};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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>
|
||||
|
|
|
@ -1,40 +1,56 @@
|
|||
import { I18n } from "./i18n.js";
|
||||
import{ mobile }from"./utils/utils.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {mobile} from "./utils/utils.js";
|
||||
console.log(mobile);
|
||||
const serverbox = document.getElementById("instancebox") as HTMLDivElement;
|
||||
|
||||
(async ()=>{
|
||||
(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){
|
||||
openClient.textContent=I18n.getTranslation("htmlPages.openClient");
|
||||
welcomeJank.textContent=I18n.getTranslation("htmlPages.welcomeJank");
|
||||
box1title.textContent=I18n.getTranslation("htmlPages.box1title");
|
||||
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");
|
||||
|
||||
compatableInstances.textContent=I18n.getTranslation("htmlPages.compatableInstances");
|
||||
box3title.textContent=I18n.getTranslation("htmlPages.box3title");
|
||||
box3description.textContent=I18n.getTranslation("htmlPages.box3description");
|
||||
compatableInstances.textContent = I18n.getTranslation("htmlPages.compatableInstances");
|
||||
box3title.textContent = I18n.getTranslation("htmlPages.box3title");
|
||||
box3description.textContent = I18n.getTranslation("htmlPages.box3description");
|
||||
|
||||
const items=I18n.getTranslation("htmlPages.box1Items").split("|");
|
||||
let i=0;
|
||||
const items = I18n.getTranslation("htmlPages.box1Items").split("|");
|
||||
let i = 0;
|
||||
//@ts-ignore ts is being dumb here
|
||||
for(const item of box1Items.children){
|
||||
(item as HTMLElement).textContent=items[i];
|
||||
for (const item of box1Items.children) {
|
||||
(item as HTMLElement).textContent = items[i];
|
||||
i++;
|
||||
}
|
||||
}else{
|
||||
console.error(openClient,welcomeJank,compatableInstances,box3title,box3description,box1title,box1Items)
|
||||
} else {
|
||||
console.error(
|
||||
openClient,
|
||||
welcomeJank,
|
||||
compatableInstances,
|
||||
box3title,
|
||||
box3description,
|
||||
box1title,
|
||||
box1Items,
|
||||
);
|
||||
}
|
||||
})()
|
||||
})();
|
||||
|
||||
fetch("/instances.json")
|
||||
.then(_=>_.json())
|
||||
.then((_) => _.json())
|
||||
.then(
|
||||
async (
|
||||
json: {
|
||||
|
@ -45,76 +61,77 @@ fetch("/instances.json")
|
|||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: { alltime: number; daytime: number; weektime: number };
|
||||
uptime: {alltime: number; daytime: number; weektime: number};
|
||||
urls: {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
};
|
||||
}[]
|
||||
)=>{
|
||||
}[],
|
||||
) => {
|
||||
await I18n.done;
|
||||
console.warn(json);
|
||||
for(const instance of json){
|
||||
if(instance.display === false){
|
||||
for (const instance of json) {
|
||||
if (instance.display === false) {
|
||||
continue;
|
||||
}
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("flexltr", "instance");
|
||||
if(instance.image){
|
||||
if (instance.image) {
|
||||
const img = document.createElement("img");
|
||||
img.src = instance.image;
|
||||
div.append(img);
|
||||
}
|
||||
const statbox = document.createElement("div");
|
||||
statbox.classList.add("flexttb","flexgrow");
|
||||
statbox.classList.add("flexttb", "flexgrow");
|
||||
|
||||
{
|
||||
const textbox = document.createElement("div");
|
||||
textbox.classList.add("flexttb", "instancetextbox");
|
||||
const title = document.createElement("h2");
|
||||
title.innerText = instance.name;
|
||||
if(instance.online !== undefined){
|
||||
if (instance.online !== undefined) {
|
||||
const status = document.createElement("span");
|
||||
status.innerText = instance.online ? "Online" : "Offline";
|
||||
status.classList.add("instanceStatus");
|
||||
title.append(status);
|
||||
}
|
||||
textbox.append(title);
|
||||
if(instance.description || instance.descriptionLong){
|
||||
if (instance.description || instance.descriptionLong) {
|
||||
const p = document.createElement("p");
|
||||
if(instance.descriptionLong){
|
||||
if (instance.descriptionLong) {
|
||||
p.innerText = instance.descriptionLong;
|
||||
}else if(instance.description){
|
||||
} else if (instance.description) {
|
||||
p.innerText = instance.description;
|
||||
}
|
||||
textbox.append(p);
|
||||
}
|
||||
statbox.append(textbox);
|
||||
}
|
||||
if(instance.uptime){
|
||||
if (instance.uptime) {
|
||||
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 = _=>{
|
||||
if(instance.online){
|
||||
div.onclick = (_) => {
|
||||
if (instance.online) {
|
||||
window.location.href = "/register.html?instance=" + encodeURI(instance.name);
|
||||
}else{
|
||||
} else {
|
||||
alert(I18n.getTranslation("home.warnOffiline"));
|
||||
}
|
||||
};
|
||||
serverbox.append(div);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,57 +1,58 @@
|
|||
import { Contextmenu } from "./contextmenu.js";
|
||||
import { MarkDown } from "./markdown.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
import {MarkDown} from "./markdown.js";
|
||||
|
||||
class Hover{
|
||||
str:string|MarkDown|(()=>Promise<MarkDown|string>|MarkDown|string);
|
||||
constructor(txt:string|MarkDown|(()=>Promise<MarkDown|string>|MarkDown|string)){
|
||||
this.str=txt;
|
||||
}
|
||||
addEvent(elm:HTMLElement){
|
||||
let timeOut=setTimeout(()=>{},0);
|
||||
let elm2=document.createElement("div");
|
||||
elm.addEventListener("mouseover",()=>{
|
||||
timeOut=setTimeout(async ()=>{
|
||||
elm2=await this.makeHover(elm);
|
||||
},750)
|
||||
});
|
||||
elm.addEventListener("mouseout",()=>{
|
||||
clearTimeout(timeOut);
|
||||
elm2.remove();
|
||||
});
|
||||
new MutationObserver(function (e) {
|
||||
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;
|
||||
const div=document.createElement("div");
|
||||
if(this.str instanceof MarkDown){
|
||||
div.append(this.str.makeHTML())
|
||||
}else if(this.str instanceof Function){
|
||||
const hover=await this.str();
|
||||
if(hover instanceof MarkDown){
|
||||
div.append(hover.makeHTML());
|
||||
}else{
|
||||
div.innerText=hover;
|
||||
}
|
||||
}else{
|
||||
div.innerText=this.str;
|
||||
}
|
||||
const box=elm.getBoundingClientRect();
|
||||
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)
|
||||
document.body.append(div);
|
||||
Contextmenu.keepOnScreen(div);
|
||||
console.log(div,elm);
|
||||
return div;
|
||||
}
|
||||
class Hover {
|
||||
str: string | MarkDown | (() => Promise<MarkDown | string> | MarkDown | string);
|
||||
constructor(txt: string | MarkDown | (() => Promise<MarkDown | string> | MarkDown | string)) {
|
||||
this.str = txt;
|
||||
}
|
||||
addEvent(elm: HTMLElement) {
|
||||
let timeOut = setTimeout(() => {}, 0);
|
||||
let elm2 = document.createElement("div");
|
||||
elm.addEventListener("mouseover", () => {
|
||||
timeOut = setTimeout(async () => {
|
||||
elm2 = await this.makeHover(elm);
|
||||
}, 750);
|
||||
});
|
||||
elm.addEventListener("mouseout", () => {
|
||||
clearTimeout(timeOut);
|
||||
elm2.remove();
|
||||
});
|
||||
new MutationObserver(function (e) {
|
||||
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;
|
||||
const div = document.createElement("div");
|
||||
if (this.str instanceof MarkDown) {
|
||||
div.append(this.str.makeHTML());
|
||||
} else if (this.str instanceof Function) {
|
||||
const hover = await this.str();
|
||||
if (hover instanceof MarkDown) {
|
||||
div.append(hover.makeHTML());
|
||||
} else {
|
||||
div.innerText = hover;
|
||||
}
|
||||
} else {
|
||||
div.innerText = this.str;
|
||||
}
|
||||
const box = elm.getBoundingClientRect();
|
||||
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);
|
||||
document.body.append(div);
|
||||
Contextmenu.keepOnScreen(div);
|
||||
console.log(div, elm);
|
||||
return div;
|
||||
}
|
||||
}
|
||||
export{Hover}
|
||||
export {Hover};
|
||||
|
|
|
@ -1,122 +1,118 @@
|
|||
//@ts-ignore
|
||||
import {langs} from "./translations/langs.js";
|
||||
const langmap=new Map<string,string>();
|
||||
for(const lang of Object.keys(langs) as string[]){
|
||||
langmap.set(lang,langs[lang]);
|
||||
const langmap = new Map<string, string>();
|
||||
for (const lang of Object.keys(langs) as string[]) {
|
||||
langmap.set(lang, langs[lang]);
|
||||
}
|
||||
console.log(langs);
|
||||
type translation={
|
||||
[key:string]:string|translation
|
||||
type translation = {
|
||||
[key: string]: string | translation;
|
||||
};
|
||||
let res:()=>unknown=()=>{};
|
||||
class I18n{
|
||||
static lang:string;
|
||||
static translations:translation[]=[];
|
||||
static done=new Promise<void>((res2,_reject)=>{
|
||||
res=res2;
|
||||
});
|
||||
static async create(lang:string){
|
||||
let res: () => unknown = () => {};
|
||||
class I18n {
|
||||
static lang: string;
|
||||
static translations: translation[] = [];
|
||||
static done = new Promise<void>((res2, _reject) => {
|
||||
res = res2;
|
||||
});
|
||||
static async create(lang: string) {
|
||||
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);
|
||||
}
|
||||
this.lang = lang;
|
||||
this.translations = translations;
|
||||
|
||||
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);
|
||||
}
|
||||
this.lang=lang;
|
||||
this.translations=translations;
|
||||
res();
|
||||
}
|
||||
static getTranslation(msg: string, ...params: string[]): string {
|
||||
let str: string | undefined;
|
||||
const path = msg.split(".");
|
||||
for (const json of this.translations) {
|
||||
let jsont: string | translation = json;
|
||||
for (const thing of path) {
|
||||
if (typeof jsont !== "string" && jsont !== undefined) {
|
||||
jsont = jsont[thing];
|
||||
} else {
|
||||
jsont = json;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res();
|
||||
}
|
||||
static getTranslation(msg:string,...params:string[]):string{
|
||||
let str:string|undefined;
|
||||
const path=msg.split(".");
|
||||
for(const json of this.translations){
|
||||
let jsont:string|translation=json;
|
||||
for(const thing of path){
|
||||
if(typeof jsont !== "string" && jsont!==undefined){
|
||||
jsont=jsont[thing];
|
||||
if (typeof jsont === "string") {
|
||||
str = jsont;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (str) {
|
||||
return this.fillInBlanks(str, params);
|
||||
} else {
|
||||
throw new Error(msg + " not found");
|
||||
}
|
||||
}
|
||||
static fillInBlanks(msg: string, params: string[]): string {
|
||||
//thanks to geotale for the regex
|
||||
msg = msg.replace(/\$\d+/g, (match) => {
|
||||
const number = Number(match.slice(1));
|
||||
if (params[number - 1]) {
|
||||
return params[number - 1];
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
msg = msg.replace(/{{(.+?)}}/g, (str, match: string) => {
|
||||
const [op, strsSplit] = this.fillInBlanks(match, params).split(":");
|
||||
const [first, ...strs] = strsSplit.split("|");
|
||||
switch (op.toUpperCase()) {
|
||||
case "PLURAL": {
|
||||
const numb = Number(first);
|
||||
if (numb === 0) {
|
||||
return strs[strs.length - 1];
|
||||
}
|
||||
return strs[Math.min(strs.length - 1, numb - 1)];
|
||||
}
|
||||
case "GENDER": {
|
||||
if (first === "male") {
|
||||
return strs[0];
|
||||
} else if (first === "female") {
|
||||
return strs[1];
|
||||
} else if (first === "neutral") {
|
||||
if (strs[2]) {
|
||||
return strs[2];
|
||||
} else {
|
||||
return strs[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
});
|
||||
|
||||
}else{
|
||||
jsont=json;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof jsont === "string"){
|
||||
str=jsont;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(str){
|
||||
return this.fillInBlanks(str,params);
|
||||
}else{
|
||||
throw new Error(msg+" not found")
|
||||
}
|
||||
}
|
||||
static fillInBlanks(msg:string,params:string[]):string{
|
||||
//thanks to geotale for the regex
|
||||
msg=msg.replace(/\$\d+/g,(match) => {
|
||||
const number=Number(match.slice(1));
|
||||
if(params[number-1]){
|
||||
return params[number-1];
|
||||
}else{
|
||||
return match;
|
||||
}
|
||||
});
|
||||
msg=msg.replace(/{{(.+?)}}/g,
|
||||
(str, match:string) => {
|
||||
const [op,strsSplit]=this.fillInBlanks(match,params).split(":");
|
||||
const [first,...strs]=strsSplit.split("|");
|
||||
switch(op.toUpperCase()){
|
||||
case "PLURAL":{
|
||||
const numb=Number(first);
|
||||
if(numb===0){
|
||||
return strs[strs.length-1];
|
||||
}
|
||||
return strs[Math.min(strs.length-1,numb-1)];
|
||||
}
|
||||
case "GENDER":{
|
||||
if(first==="male"){
|
||||
return strs[0];
|
||||
}else if(first==="female"){
|
||||
return strs[1];
|
||||
}else if(first==="neutral"){
|
||||
if(strs[2]){
|
||||
return strs[2];
|
||||
}else{
|
||||
return strs[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
);
|
||||
|
||||
return msg;
|
||||
}
|
||||
static options(){
|
||||
return [...langmap.keys()].map(e=>e.replace(".json",""));
|
||||
}
|
||||
static setLanguage(lang:string){
|
||||
if(this.options().indexOf(userLocale)!==-1){
|
||||
localStorage.setItem("lang",lang);
|
||||
I18n.create(lang);
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
static options() {
|
||||
return [...langmap.keys()].map((e) => e.replace(".json", ""));
|
||||
}
|
||||
static setLanguage(lang: string) {
|
||||
if (this.options().indexOf(userLocale) !== -1) {
|
||||
localStorage.setItem("lang", lang);
|
||||
I18n.create(lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(langmap);
|
||||
let userLocale = navigator.language.slice(0,2) || "en";
|
||||
if(I18n.options().indexOf(userLocale)===-1){
|
||||
userLocale="en";
|
||||
let userLocale = navigator.language.slice(0, 2) || "en";
|
||||
if (I18n.options().indexOf(userLocale) === -1) {
|
||||
userLocale = "en";
|
||||
}
|
||||
const storage=localStorage.getItem("lang");
|
||||
if(storage){
|
||||
userLocale=storage;
|
||||
}else{
|
||||
localStorage.setItem("lang",userLocale)
|
||||
const storage = localStorage.getItem("lang");
|
||||
if (storage) {
|
||||
userLocale = storage;
|
||||
} else {
|
||||
localStorage.setItem("lang", userLocale);
|
||||
}
|
||||
I18n.create(userLocale);
|
||||
|
||||
export{I18n,langmap};
|
||||
export {I18n, langmap};
|
||||
|
|
|
@ -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>
|
||||
<div id="loadingdiv"></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">
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
import{ Localuser }from"./localuser.js";
|
||||
import{ Contextmenu }from"./contextmenu.js";
|
||||
import{ mobile }from"./utils/utils.js";
|
||||
import { getBulkUsers, setTheme, Specialuser } from "./utils/utils.js";
|
||||
import{ MarkDown }from"./markdown.js";
|
||||
import{ Message }from"./message.js";
|
||||
import{File}from"./file.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
(async ()=>{
|
||||
await I18n.done
|
||||
import {Localuser} from "./localuser.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
import {mobile} from "./utils/utils.js";
|
||||
import {getBulkUsers, setTheme, Specialuser} from "./utils/utils.js";
|
||||
import {MarkDown} from "./markdown.js";
|
||||
import {Message} from "./message.js";
|
||||
import {File} from "./file.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
(async () => {
|
||||
await I18n.done;
|
||||
const users = getBulkUsers();
|
||||
if(!users.currentuser){
|
||||
if (!users.currentuser) {
|
||||
window.location.href = "/login.html";
|
||||
return;
|
||||
}
|
||||
{
|
||||
const loadingText=document.getElementById("loadingText");
|
||||
const loaddesc=document.getElementById("load-desc");
|
||||
const switchaccounts=document.getElementById("switchaccounts");
|
||||
const filedroptext=document.getElementById("filedroptext");
|
||||
if(loadingText&&loaddesc&&switchaccounts&&filedroptext){
|
||||
loadingText.textContent=I18n.getTranslation("htmlPages.loadingText");
|
||||
loaddesc.textContent=I18n.getTranslation("htmlPages.loaddesc");
|
||||
switchaccounts.textContent=I18n.getTranslation("htmlPages.switchaccounts");
|
||||
filedroptext.textContent=I18n.getTranslation("uploadFilesText");
|
||||
const loadingText = document.getElementById("loadingText");
|
||||
const loaddesc = document.getElementById("load-desc");
|
||||
const switchaccounts = document.getElementById("switchaccounts");
|
||||
const filedroptext = document.getElementById("filedroptext");
|
||||
if (loadingText && loaddesc && switchaccounts && filedroptext) {
|
||||
loadingText.textContent = I18n.getTranslation("htmlPages.loadingText");
|
||||
loaddesc.textContent = I18n.getTranslation("htmlPages.loaddesc");
|
||||
switchaccounts.textContent = I18n.getTranslation("htmlPages.switchaccounts");
|
||||
filedroptext.textContent = I18n.getTranslation("uploadFilesText");
|
||||
}
|
||||
}
|
||||
I18n
|
||||
function showAccountSwitcher(): void{
|
||||
I18n;
|
||||
function showAccountSwitcher(): void {
|
||||
const table = document.createElement("div");
|
||||
table.classList.add("flexttb","accountSwitcher");
|
||||
table.classList.add("flexttb", "accountSwitcher");
|
||||
|
||||
for(const user of Object.values(users.users)){
|
||||
for (const user of Object.values(users.users)) {
|
||||
const specialUser = user as Specialuser;
|
||||
const userInfo = document.createElement("div");
|
||||
userInfo.classList.add("flexltr", "switchtable");
|
||||
|
@ -55,7 +55,7 @@ import { I18n } from "./i18n.js";
|
|||
userInfo.append(userDiv);
|
||||
table.append(userInfo);
|
||||
|
||||
userInfo.addEventListener("click", ()=>{
|
||||
userInfo.addEventListener("click", () => {
|
||||
thisUser.unload();
|
||||
thisUser.swapped = true;
|
||||
const loading = document.getElementById("loading") as HTMLDivElement;
|
||||
|
@ -66,7 +66,7 @@ import { I18n } from "./i18n.js";
|
|||
users.currentuser = specialUser.uid;
|
||||
localStorage.setItem("userinfos", JSON.stringify(users));
|
||||
|
||||
thisUser.initwebsocket().then(()=>{
|
||||
thisUser.initwebsocket().then(() => {
|
||||
thisUser.loaduser();
|
||||
thisUser.init();
|
||||
loading.classList.add("doneloading");
|
||||
|
@ -81,12 +81,12 @@ import { I18n } from "./i18n.js";
|
|||
const switchAccountDiv = document.createElement("div");
|
||||
switchAccountDiv.classList.add("switchtable");
|
||||
switchAccountDiv.textContent = I18n.getTranslation("switchAccounts");
|
||||
switchAccountDiv.addEventListener("click", ()=>{
|
||||
switchAccountDiv.addEventListener("click", () => {
|
||||
window.location.href = "/login.html";
|
||||
});
|
||||
table.append(switchAccountDiv);
|
||||
|
||||
if(Contextmenu.currentmenu){
|
||||
if (Contextmenu.currentmenu) {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
Contextmenu.currentmenu = table;
|
||||
|
@ -94,22 +94,22 @@ 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();
|
||||
});
|
||||
|
||||
let thisUser: Localuser;
|
||||
try{
|
||||
try {
|
||||
console.log(users.users, users.currentuser);
|
||||
thisUser = new Localuser(users.users[users.currentuser]);
|
||||
thisUser.initwebsocket().then(()=>{
|
||||
thisUser.initwebsocket().then(() => {
|
||||
thisUser.loaduser();
|
||||
thisUser.init();
|
||||
const loading = document.getElementById("loading") as HTMLDivElement;
|
||||
|
@ -117,62 +117,65 @@ import { I18n } from "./i18n.js";
|
|||
loading.classList.remove("loading");
|
||||
console.log("done loading");
|
||||
});
|
||||
}catch(e){
|
||||
} 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);
|
||||
}
|
||||
|
||||
const menu = new Contextmenu<void,void>("create rightclick");
|
||||
const menu = new Contextmenu<void, void>("create rightclick");
|
||||
menu.addbutton(
|
||||
I18n.getTranslation("channel.createChannel"),
|
||||
()=>{
|
||||
if(thisUser.lookingguild){
|
||||
() => {
|
||||
if (thisUser.lookingguild) {
|
||||
thisUser.lookingguild.createchannels();
|
||||
}
|
||||
},
|
||||
null,
|
||||
()=>thisUser.isAdmin()
|
||||
() => thisUser.isAdmin(),
|
||||
);
|
||||
|
||||
menu.addbutton(
|
||||
I18n.getTranslation("channel.createCatagory"),
|
||||
()=>{
|
||||
if(thisUser.lookingguild){
|
||||
() => {
|
||||
if (thisUser.lookingguild) {
|
||||
thisUser.lookingguild.createcategory();
|
||||
}
|
||||
},
|
||||
null,
|
||||
()=>thisUser.isAdmin()
|
||||
() => thisUser.isAdmin(),
|
||||
);
|
||||
|
||||
menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement);
|
||||
|
||||
const pasteImageElement = document.getElementById("pasteimage") as HTMLDivElement;
|
||||
let replyingTo: Message | null = null;
|
||||
window.addEventListener("popstate",(e)=>{
|
||||
if(e.state instanceof Object){
|
||||
thisUser.goToChannel(e.state[1],false);
|
||||
window.addEventListener("popstate", (e) => {
|
||||
if (e.state instanceof Object) {
|
||||
thisUser.goToChannel(e.state[1], false);
|
||||
}
|
||||
//console.log(e.state,"state:3")
|
||||
})
|
||||
async function handleEnter(event: KeyboardEvent): Promise<void>{
|
||||
if(thisUser.keyup(event)){return}
|
||||
});
|
||||
async function handleEnter(event: KeyboardEvent): Promise<void> {
|
||||
if (thisUser.keyup(event)) {
|
||||
return;
|
||||
}
|
||||
const channel = thisUser.channelfocus;
|
||||
if(!channel)return;
|
||||
if(markdown.rawString===""&&event.key==="ArrowUp"){
|
||||
if (!channel) return;
|
||||
if (markdown.rawString === "" && event.key === "ArrowUp") {
|
||||
channel.editLast();
|
||||
return;
|
||||
}
|
||||
channel.typingstart();
|
||||
|
||||
if(event.key === "Enter" && !event.shiftKey){
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
replyingTo = thisUser.channelfocus? thisUser.channelfocus.replyingto: null;
|
||||
if(replyingTo?.div){
|
||||
replyingTo = thisUser.channelfocus ? thisUser.channelfocus.replyingto : null;
|
||||
if (replyingTo?.div) {
|
||||
replyingTo.div.classList.remove("replying");
|
||||
}
|
||||
if(thisUser.channelfocus){
|
||||
if (thisUser.channelfocus) {
|
||||
thisUser.channelfocus.replyingto = null;
|
||||
}
|
||||
channel.sendMessage(markdown.rawString, {
|
||||
|
@ -181,10 +184,10 @@ import { I18n } from "./i18n.js";
|
|||
embeds: [], // Add an empty array for the embeds property
|
||||
replyingto: replyingTo,
|
||||
});
|
||||
if(thisUser.channelfocus){
|
||||
if (thisUser.channelfocus) {
|
||||
thisUser.channelfocus.makereplybox();
|
||||
}
|
||||
while(images.length){
|
||||
while (images.length) {
|
||||
images.pop();
|
||||
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);
|
||||
}
|
||||
|
@ -193,15 +196,17 @@ 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)
|
||||
if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
|
||||
typebox.addEventListener("keydown", (event) => {
|
||||
thisUser.keydown(event);
|
||||
if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
|
||||
});
|
||||
markdown.giveBox(typebox);
|
||||
{
|
||||
|
@ -209,30 +214,27 @@ import { I18n } from "./i18n.js";
|
|||
const markdown = new MarkDown("", thisUser);
|
||||
searchBox.markdown = markdown;
|
||||
|
||||
searchBox.addEventListener("keydown", event=>{
|
||||
|
||||
if(event.key === "Enter") {
|
||||
searchBox.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
thisUser.mSearch(markdown.rawString)
|
||||
};
|
||||
thisUser.mSearch(markdown.rawString);
|
||||
}
|
||||
});
|
||||
|
||||
markdown.giveBox(searchBox);
|
||||
markdown.setCustomBox((e)=>{
|
||||
const span=document.createElement("span");
|
||||
span.textContent=e.replace("\n","");
|
||||
markdown.setCustomBox((e) => {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = e.replace("\n", "");
|
||||
return span;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
const images: Blob[] = [];
|
||||
const imagesHtml: HTMLElement[] = [];
|
||||
|
||||
document.addEventListener("paste", async (e: ClipboardEvent)=>{
|
||||
if(!e.clipboardData)return;
|
||||
document.addEventListener("paste", async (e: ClipboardEvent) => {
|
||||
if (!e.clipboardData) return;
|
||||
|
||||
for(const file of Array.from(e.clipboardData.files)){
|
||||
for (const file of Array.from(e.clipboardData.files)) {
|
||||
const fileInstance = File.initFromBlob(file);
|
||||
e.preventDefault();
|
||||
const html = fileInstance.upHTML(images, file);
|
||||
|
@ -244,60 +246,59 @@ import { I18n } from "./i18n.js";
|
|||
|
||||
setTheme();
|
||||
|
||||
function userSettings(): void{
|
||||
function userSettings(): void {
|
||||
thisUser.showusersettings();
|
||||
}
|
||||
|
||||
(document.getElementById("settings") as HTMLImageElement).onclick =
|
||||
userSettings;
|
||||
(document.getElementById("settings") as HTMLImageElement).onclick = userSettings;
|
||||
|
||||
if(mobile){
|
||||
if (mobile) {
|
||||
const channelWrapper = document.getElementById("channelw") as HTMLDivElement;
|
||||
channelWrapper.onclick = ()=>{
|
||||
channelWrapper.onclick = () => {
|
||||
const toggle = document.getElementById("maintoggle") as HTMLInputElement;
|
||||
toggle.checked = true;
|
||||
};
|
||||
const memberListToggle = document.getElementById("memberlisttoggle") as HTMLInputElement;
|
||||
memberListToggle.checked = false;
|
||||
}
|
||||
let dragendtimeout=setTimeout(()=>{})
|
||||
document.addEventListener("dragover",(e)=>{
|
||||
let dragendtimeout = setTimeout(() => {});
|
||||
document.addEventListener("dragover", (e) => {
|
||||
clearTimeout(dragendtimeout);
|
||||
const data = e.dataTransfer;
|
||||
const bg=document.getElementById("gimmefile") as HTMLDivElement;
|
||||
const bg = document.getElementById("gimmefile") as HTMLDivElement;
|
||||
|
||||
if(data){
|
||||
const isfile=data.types.includes("Files")||data.types.includes("application/x-moz-file");
|
||||
if(!isfile){
|
||||
bg.hidden=true;
|
||||
if (data) {
|
||||
const isfile = data.types.includes("Files") || data.types.includes("application/x-moz-file");
|
||||
if (!isfile) {
|
||||
bg.hidden = true;
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
bg.hidden=false;
|
||||
bg.hidden = false;
|
||||
//console.log(data.types,data)
|
||||
}else{
|
||||
bg.hidden=true;
|
||||
} else {
|
||||
bg.hidden = true;
|
||||
}
|
||||
});
|
||||
document.addEventListener("dragleave",(_)=>{
|
||||
dragendtimeout=setTimeout(()=>{
|
||||
const bg=document.getElementById("gimmefile") as HTMLDivElement;
|
||||
bg.hidden=true;
|
||||
},1000)
|
||||
document.addEventListener("dragleave", (_) => {
|
||||
dragendtimeout = setTimeout(() => {
|
||||
const bg = document.getElementById("gimmefile") as HTMLDivElement;
|
||||
bg.hidden = true;
|
||||
}, 1000);
|
||||
});
|
||||
document.addEventListener("dragenter",(e)=>{
|
||||
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;
|
||||
if(data){
|
||||
const isfile=data.types.includes("Files")||data.types.includes("application/x-moz-file");
|
||||
if(isfile){
|
||||
const bg = document.getElementById("gimmefile") as HTMLDivElement;
|
||||
bg.hidden = true;
|
||||
if (data) {
|
||||
const isfile = data.types.includes("Files") || data.types.includes("application/x-moz-file");
|
||||
if (isfile) {
|
||||
e.preventDefault();
|
||||
console.log(data.files);
|
||||
for(const file of Array.from(data.files)){
|
||||
for (const file of Array.from(data.files)) {
|
||||
const fileInstance = File.initFromBlob(file);
|
||||
const html = fileInstance.upHTML(images, file);
|
||||
pasteImageElement.appendChild(html);
|
||||
|
@ -307,14 +308,14 @@ import { I18n } from "./i18n.js";
|
|||
}
|
||||
}
|
||||
});
|
||||
(document.getElementById("upload") as HTMLElement).onclick=()=>{
|
||||
const input=document.createElement("input");
|
||||
input.type="file";
|
||||
(document.getElementById("upload") as HTMLElement).onclick = () => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.click();
|
||||
console.log("clicked")
|
||||
input.onchange=(() => {
|
||||
if(input.files){
|
||||
for(const file of Array.from(input.files)){
|
||||
console.log("clicked");
|
||||
input.onchange = () => {
|
||||
if (input.files) {
|
||||
for (const file of Array.from(input.files)) {
|
||||
const fileInstance = File.initFromBlob(file);
|
||||
const html = fileInstance.upHTML(images, file);
|
||||
pasteImageElement.appendChild(html);
|
||||
|
@ -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>;
|
||||
class InfiniteScroller {
|
||||
readonly getIDFromOffset: (ID: string, offset: number) => Promise<string | undefined>;
|
||||
readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>;
|
||||
readonly destroyFromID: (ID: string) => Promise<boolean>;
|
||||
readonly reachesBottom: () => void;
|
||||
|
@ -19,39 +16,39 @@ offset: number
|
|||
averageheight = 60;
|
||||
watchtime = false;
|
||||
changePromise: Promise<boolean> | undefined;
|
||||
scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number };
|
||||
scollDiv!: {scrollTop: number; scrollHeight: number; clientHeight: number};
|
||||
|
||||
resetVars(){
|
||||
this.scrollTop=0;
|
||||
this.scrollBottom=0;
|
||||
this.averageheight=60;
|
||||
this.watchtime=false;
|
||||
this.needsupdate=true;
|
||||
this.beenloaded=false;
|
||||
this.changePromise=undefined;
|
||||
if(this.timeout){
|
||||
resetVars() {
|
||||
this.scrollTop = 0;
|
||||
this.scrollBottom = 0;
|
||||
this.averageheight = 60;
|
||||
this.watchtime = false;
|
||||
this.needsupdate = true;
|
||||
this.beenloaded = false;
|
||||
this.changePromise = undefined;
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout=null;
|
||||
this.timeout = null;
|
||||
}
|
||||
for(const thing of this.HTMLElements){
|
||||
for (const thing of this.HTMLElements) {
|
||||
this.destroyFromID(thing[1]);
|
||||
}
|
||||
this.HTMLElements=[];
|
||||
this.HTMLElements = [];
|
||||
}
|
||||
constructor(
|
||||
getIDFromOffset: InfiniteScroller["getIDFromOffset"],
|
||||
getHTMLFromID: InfiniteScroller["getHTMLFromID"],
|
||||
destroyFromID: InfiniteScroller["destroyFromID"],
|
||||
reachesBottom: InfiniteScroller["reachesBottom"] = ()=>{}
|
||||
){
|
||||
reachesBottom: InfiniteScroller["reachesBottom"] = () => {},
|
||||
) {
|
||||
this.getIDFromOffset = getIDFromOffset;
|
||||
this.getHTMLFromID = getHTMLFromID;
|
||||
this.destroyFromID = destroyFromID;
|
||||
this.reachesBottom = reachesBottom;
|
||||
}
|
||||
|
||||
async getDiv(initialId: string): Promise<HTMLDivElement>{
|
||||
if(this.div){
|
||||
async getDiv(initialId: string): Promise<HTMLDivElement> {
|
||||
if (this.div) {
|
||||
throw new Error("Div already exists, exiting.");
|
||||
}
|
||||
this.resetVars();
|
||||
|
@ -59,24 +56,24 @@ offset: number
|
|||
scroll.classList.add("scroller");
|
||||
this.div = scroll;
|
||||
|
||||
this.div.addEventListener("scroll", ()=>{
|
||||
this.div.addEventListener("scroll", () => {
|
||||
this.checkscroll();
|
||||
if(this.scrollBottom < 5){
|
||||
if (this.scrollBottom < 5) {
|
||||
this.scrollBottom = 5;
|
||||
}
|
||||
if(this.timeout === null){
|
||||
if (this.timeout === null) {
|
||||
this.timeout = setTimeout(this.updatestuff.bind(this), 300);
|
||||
}
|
||||
this.watchForChange();
|
||||
});
|
||||
|
||||
let oldheight = 0;
|
||||
new ResizeObserver(()=>{
|
||||
new ResizeObserver(() => {
|
||||
this.checkscroll();
|
||||
const func = this.snapBottom();
|
||||
this.updatestuff();
|
||||
const change = oldheight - scroll.offsetHeight;
|
||||
if(change > 0 && this.div){
|
||||
if (change > 0 && this.div) {
|
||||
this.div.scrollTop += change;
|
||||
}
|
||||
oldheight = scroll.offsetHeight;
|
||||
|
@ -88,7 +85,7 @@ offset: number
|
|||
|
||||
await this.firstElement(initialId);
|
||||
this.updatestuff();
|
||||
await this.watchForChange().then(()=>{
|
||||
await this.watchForChange().then(() => {
|
||||
this.updatestuff();
|
||||
this.beenloaded = true;
|
||||
});
|
||||
|
@ -96,76 +93,72 @@ offset: number
|
|||
return scroll;
|
||||
}
|
||||
|
||||
checkscroll(): void{
|
||||
if(this.beenloaded && this.div && !document.body.contains(this.div)){
|
||||
checkscroll(): void {
|
||||
if (this.beenloaded && this.div && !document.body.contains(this.div)) {
|
||||
console.warn("not in document");
|
||||
this.div = null;
|
||||
}
|
||||
}
|
||||
|
||||
async updatestuff(): Promise<void>{
|
||||
async updatestuff(): Promise<void> {
|
||||
this.timeout = null;
|
||||
if(!this.div)return;
|
||||
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){
|
||||
if (this.averageheight < 10) {
|
||||
this.averageheight = 60;
|
||||
}
|
||||
this.scrollTop = this.div.scrollTop;
|
||||
|
||||
if(!this.scrollBottom && !(await this.watchForChange())){
|
||||
if (!this.scrollBottom && !(await this.watchForChange())) {
|
||||
this.reachesBottom();
|
||||
}
|
||||
if(!this.scrollTop){
|
||||
if (!this.scrollTop) {
|
||||
await this.watchForChange();
|
||||
}
|
||||
this.needsupdate = false;
|
||||
}
|
||||
|
||||
async firstElement(id: string): Promise<void>{
|
||||
if(!this.div)return;
|
||||
async firstElement(id: string): Promise<void> {
|
||||
if (!this.div) return;
|
||||
const html = await this.getHTMLFromID(id);
|
||||
this.div.appendChild(html);
|
||||
this.HTMLElements.push([html, id]);
|
||||
}
|
||||
|
||||
async addedBottom(): Promise<void>{
|
||||
async addedBottom(): Promise<void> {
|
||||
await this.updatestuff();
|
||||
const func = this.snapBottom();
|
||||
await this.watchForChange();
|
||||
func();
|
||||
}
|
||||
|
||||
snapBottom(): () => void{
|
||||
snapBottom(): () => void {
|
||||
const scrollBottom = this.scrollBottom;
|
||||
return()=>{
|
||||
if(this.div && scrollBottom < 4){
|
||||
return () => {
|
||||
if (this.div && scrollBottom < 4) {
|
||||
this.div.scrollTop = this.div.scrollHeight;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async watchForTop(
|
||||
already = false,
|
||||
fragment = new DocumentFragment()
|
||||
): Promise<boolean>{
|
||||
if(!this.div)return false;
|
||||
try{
|
||||
private async watchForTop(already = false, fragment = new DocumentFragment()): Promise<boolean> {
|
||||
if (!this.div) return false;
|
||||
try {
|
||||
let again = false;
|
||||
if(this.scrollTop < (already ? this.fillDist : this.minDist)){
|
||||
if (this.scrollTop < (already ? this.fillDist : this.minDist)) {
|
||||
let nextid: string | undefined;
|
||||
const firstelm = this.HTMLElements.at(0);
|
||||
if(firstelm){
|
||||
if (firstelm) {
|
||||
const previd = firstelm[1];
|
||||
nextid = await this.getIDFromOffset(previd, 1);
|
||||
}
|
||||
|
||||
if(nextid){
|
||||
if (nextid) {
|
||||
const html = await this.getHTMLFromID(nextid);
|
||||
|
||||
if(!html){
|
||||
if (!html) {
|
||||
this.destroyFromID(nextid);
|
||||
return false;
|
||||
}
|
||||
|
@ -173,26 +166,24 @@ offset: number
|
|||
fragment.prepend(html);
|
||||
this.HTMLElements.unshift([html, nextid]);
|
||||
this.scrollTop += this.averageheight;
|
||||
|
||||
}
|
||||
}
|
||||
if(this.scrollTop > this.maxDist){
|
||||
if (this.scrollTop > this.maxDist) {
|
||||
const html = this.HTMLElements.shift();
|
||||
if(html){
|
||||
if (html) {
|
||||
again = true;
|
||||
await this.destroyFromID(html[1]);
|
||||
|
||||
this.scrollTop -= this.averageheight;
|
||||
|
||||
}
|
||||
}
|
||||
if(again){
|
||||
if (again) {
|
||||
await this.watchForTop(true, fragment);
|
||||
}
|
||||
return again;
|
||||
}finally{
|
||||
if(!already){
|
||||
if(this.div.scrollTop === 0){
|
||||
} finally {
|
||||
if (!already) {
|
||||
if (this.div.scrollTop === 0) {
|
||||
this.scrollTop = 1;
|
||||
this.div.scrollTop = 10;
|
||||
}
|
||||
|
@ -201,24 +192,21 @@ 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;
|
||||
try{
|
||||
if (!already) func = this.snapBottom();
|
||||
if (!this.div) return false;
|
||||
try {
|
||||
let again = false;
|
||||
const scrollBottom = this.scrollBottom;
|
||||
if(scrollBottom < (already ? this.fillDist : this.minDist)){
|
||||
if (scrollBottom < (already ? this.fillDist : this.minDist)) {
|
||||
let nextid: string | undefined;
|
||||
const lastelm = this.HTMLElements.at(-1);
|
||||
if(lastelm){
|
||||
if (lastelm) {
|
||||
const previd = lastelm[1];
|
||||
nextid = await this.getIDFromOffset(previd, -1);
|
||||
}
|
||||
if(nextid){
|
||||
if (nextid) {
|
||||
again = true;
|
||||
const html = await this.getHTMLFromID(nextid);
|
||||
fragment.appendChild(html);
|
||||
|
@ -226,57 +214,56 @@ offset: number
|
|||
this.scrollBottom += this.averageheight;
|
||||
}
|
||||
}
|
||||
if(scrollBottom > this.maxDist){
|
||||
if (scrollBottom > this.maxDist) {
|
||||
const html = this.HTMLElements.pop();
|
||||
if(html){
|
||||
if (html) {
|
||||
await this.destroyFromID(html[1]);
|
||||
this.scrollBottom -= this.averageheight;
|
||||
again = true;
|
||||
}
|
||||
}
|
||||
if(again){
|
||||
if (again) {
|
||||
await this.watchForBottom(true, fragment);
|
||||
}
|
||||
return again;
|
||||
}finally{
|
||||
if(!already){
|
||||
} finally {
|
||||
if (!already) {
|
||||
this.div.append(fragment);
|
||||
if(func){
|
||||
if (func) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async watchForChange(): Promise<boolean>{
|
||||
if(this.changePromise){
|
||||
async watchForChange(): Promise<boolean> {
|
||||
if (this.changePromise) {
|
||||
this.watchtime = true;
|
||||
return await this.changePromise;
|
||||
}else{
|
||||
} else {
|
||||
this.watchtime = false;
|
||||
}
|
||||
|
||||
this.changePromise = new Promise<boolean>(async res=>{
|
||||
try{
|
||||
if(!this.div){
|
||||
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){
|
||||
if (this.timeout === null && changed) {
|
||||
this.timeout = setTimeout(this.updatestuff.bind(this), 300);
|
||||
}
|
||||
res(Boolean(changed));
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res(false);
|
||||
}finally{
|
||||
setTimeout(()=>{
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.changePromise = undefined;
|
||||
if(this.watchtime){
|
||||
if (this.watchtime) {
|
||||
this.watchForChange();
|
||||
}
|
||||
}, 300);
|
||||
|
@ -285,65 +272,65 @@ offset: number
|
|||
|
||||
return await this.changePromise;
|
||||
}
|
||||
async focus(id: string, flash = true): Promise<void>{
|
||||
async focus(id: string, flash = true): Promise<void> {
|
||||
let element: HTMLElement | undefined;
|
||||
for(const thing of this.HTMLElements){
|
||||
if(thing[1] === id){
|
||||
for (const thing of this.HTMLElements) {
|
||||
if (thing[1] === id) {
|
||||
element = thing[0];
|
||||
}
|
||||
}
|
||||
if(element){
|
||||
if(flash){
|
||||
if (element) {
|
||||
if (flash) {
|
||||
element.scrollIntoView({
|
||||
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");
|
||||
}else{
|
||||
} else {
|
||||
element.scrollIntoView();
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
this.resetVars();
|
||||
//TODO may be a redundant loop, not 100% sure :P
|
||||
for(const thing of this.HTMLElements){
|
||||
for (const thing of this.HTMLElements) {
|
||||
await this.destroyFromID(thing[1]);
|
||||
}
|
||||
this.HTMLElements = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(): Promise<void>{
|
||||
if(this.div){
|
||||
async delete(): Promise<void> {
|
||||
if (this.div) {
|
||||
this.div.remove();
|
||||
this.div = null;
|
||||
}
|
||||
this.resetVars();
|
||||
try{
|
||||
for(const thing of this.HTMLElements){
|
||||
try {
|
||||
for (const thing of this.HTMLElements) {
|
||||
await this.destroyFromID(thing[1]);
|
||||
}
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.HTMLElements = [];
|
||||
if(this.timeout){
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export{ InfiniteScroller };
|
||||
export {InfiniteScroller};
|
||||
|
|
|
@ -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>
|
||||
|
@ -24,4 +33,4 @@
|
|||
</div>
|
||||
<script type="module" src="/invite.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,48 +1,47 @@
|
|||
import { I18n } from "./i18n.js";
|
||||
import{ getapiurls }from"./utils/utils.js";
|
||||
import { getBulkUsers, Specialuser } from "./utils/utils.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {getapiurls} from "./utils/utils.js";
|
||||
import {getBulkUsers, Specialuser} from "./utils/utils.js";
|
||||
|
||||
(async ()=>{
|
||||
(async () => {
|
||||
const users = getBulkUsers();
|
||||
const well = new URLSearchParams(window.location.search).get("instance");
|
||||
const joinable: Specialuser[] = [];
|
||||
|
||||
for(const key in users.users){
|
||||
if(Object.prototype.hasOwnProperty.call(users.users, key)){
|
||||
for (const key in users.users) {
|
||||
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
|
||||
const user: Specialuser = users.users[key];
|
||||
if(well && user.serverurls.wellknown.includes(well)){
|
||||
if (well && user.serverurls.wellknown.includes(well)) {
|
||||
joinable.push(user);
|
||||
}
|
||||
console.log(user);
|
||||
}
|
||||
}
|
||||
|
||||
let urls: { api: string; cdn: string } | undefined;
|
||||
let urls: {api: string; cdn: string} | undefined;
|
||||
|
||||
if(!joinable.length && well){
|
||||
if (!joinable.length && well) {
|
||||
const out = await getapiurls(well);
|
||||
if(out){
|
||||
if (out) {
|
||||
urls = out;
|
||||
for(const key in users.users){
|
||||
if(Object.prototype.hasOwnProperty.call(users.users, key)){
|
||||
for (const key in users.users) {
|
||||
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
|
||||
const user: Specialuser = users.users[key];
|
||||
if(user.serverurls.api.includes(out.api)){
|
||||
if (user.serverurls.api.includes(out.api)) {
|
||||
joinable.push(user);
|
||||
}
|
||||
console.log(user);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
throw new Error(
|
||||
"Someone needs to handle the case where the servers don't exist"
|
||||
);
|
||||
} else {
|
||||
throw new Error("Someone needs to handle the case where the servers don't exist");
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
urls = joinable[0].serverurls;
|
||||
}
|
||||
await I18n.done;
|
||||
if(!joinable.length){
|
||||
document.getElementById("AcceptInvite")!.textContent = I18n.getTranslation("htmlPages.noAccount");
|
||||
if (!joinable.length) {
|
||||
document.getElementById("AcceptInvite")!.textContent =
|
||||
I18n.getTranslation("htmlPages.noAccount");
|
||||
}
|
||||
|
||||
const code = window.location.pathname.split("/")[2];
|
||||
|
@ -51,34 +50,36 @@ 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)
|
||||
if(guildjson.icon){
|
||||
const img = document.createElement("img");
|
||||
img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`;
|
||||
img.classList.add("inviteGuild");
|
||||
document.getElementById("inviteimg")!.append(img);
|
||||
}else{
|
||||
const txt = guildjson.name
|
||||
.replace(/'s /g, " ")
|
||||
.replace(/\w+/g, (word: any[])=>word[0])
|
||||
.replace(/\s/g, "");
|
||||
const div = document.createElement("div");
|
||||
div.textContent = txt;
|
||||
div.classList.add("inviteGuild");
|
||||
document.getElementById("inviteimg")!.append(div);
|
||||
}
|
||||
document.getElementById("invitename")!.textContent = 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`;
|
||||
img.classList.add("inviteGuild");
|
||||
document.getElementById("inviteimg")!.append(img);
|
||||
} else {
|
||||
const txt = guildjson.name
|
||||
.replace(/'s /g, " ")
|
||||
.replace(/\w+/g, (word: any[]) => word[0])
|
||||
.replace(/\s/g, "");
|
||||
const div = document.createElement("div");
|
||||
div.textContent = txt;
|
||||
div.classList.add("inviteGuild");
|
||||
document.getElementById("inviteimg")!.append(div);
|
||||
}
|
||||
});
|
||||
|
||||
function showAccounts(): void{
|
||||
function showAccounts(): void {
|
||||
const table = document.createElement("dialog");
|
||||
for(const user of joinable){
|
||||
for (const user of joinable) {
|
||||
console.log(user.pfpsrc);
|
||||
|
||||
const userinfo = document.createElement("div");
|
||||
|
@ -95,23 +96,21 @@ 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);
|
||||
|
||||
userinfo.append(userDiv);
|
||||
table.append(userinfo);
|
||||
|
||||
userinfo.addEventListener("click", ()=>{
|
||||
userinfo.addEventListener("click", () => {
|
||||
console.log(user);
|
||||
fetch(`${urls!.api}/invites/${code}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: user.token,
|
||||
},
|
||||
}).then(()=>{
|
||||
}).then(() => {
|
||||
users.currentuser = user.uid;
|
||||
localStorage.setItem("userinfos", JSON.stringify(users));
|
||||
window.location.href = "/channels/" + guildinfo.id;
|
||||
|
@ -122,14 +121,14 @@ document.getElementById("inviteimg")!.append(div);
|
|||
const td = document.createElement("div");
|
||||
td.classList.add("switchtable");
|
||||
td.textContent = I18n.getTranslation("invite.loginOrCreateAccount");
|
||||
td.addEventListener("click", ()=>{
|
||||
td.addEventListener("click", () => {
|
||||
const l = new URLSearchParams("?");
|
||||
l.set("goback", window.location.href);
|
||||
l.set("instance", well!);
|
||||
window.location.href = "/login?" + l.toString();
|
||||
});
|
||||
|
||||
if(!joinable.length){
|
||||
if (!joinable.length) {
|
||||
const l = new URLSearchParams("?");
|
||||
l.set("goback", window.location.href);
|
||||
l.set("instance", well!);
|
||||
|
@ -137,12 +136,10 @@ document.getElementById("inviteimg")!.append(div);
|
|||
}
|
||||
|
||||
table.append(td);
|
||||
table.classList.add("flexttb","accountSwitcher");
|
||||
table.classList.add("flexttb", "accountSwitcher");
|
||||
console.log(table);
|
||||
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;
|
||||
|
@ -149,12 +154,12 @@ type memberjson = {
|
|||
id: string;
|
||||
user: userjson | null;
|
||||
guild_id: string;
|
||||
avatar?:string;
|
||||
banner?:string;
|
||||
avatar?: string;
|
||||
banner?: string;
|
||||
guild: {
|
||||
id: string;
|
||||
} | null;
|
||||
presence?:presencejson
|
||||
presence?: presencejson;
|
||||
nick?: string;
|
||||
roles: string[];
|
||||
joined_at: string;
|
||||
|
@ -168,21 +173,20 @@ type emojijson = {
|
|||
name: string;
|
||||
id?: string;
|
||||
animated?: boolean;
|
||||
emoji?:string;
|
||||
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
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
type guildjson = {
|
||||
application_command_counts: { [key: string]: number };
|
||||
application_command_counts: {[key: string]: number};
|
||||
channels: channeljson[];
|
||||
data_mode: string;
|
||||
emojis: emojipjson[];
|
||||
|
@ -404,165 +408,178 @@ type messageCreateJson = {
|
|||
s: number;
|
||||
t: "MESSAGE_CREATE";
|
||||
};
|
||||
type roleCreate={
|
||||
op: 0,
|
||||
t: "GUILD_ROLE_CREATE",
|
||||
d: {
|
||||
guild_id: string,
|
||||
role: rolesjson
|
||||
},
|
||||
s: 6
|
||||
}
|
||||
type wsjson =
|
||||
roleCreate | {
|
||||
op: 0;
|
||||
d: any;
|
||||
s: number;
|
||||
t:
|
||||
| "TYPING_START"
|
||||
| "USER_UPDATE"
|
||||
| "CHANNEL_UPDATE"
|
||||
| "CHANNEL_CREATE"
|
||||
| "CHANNEL_DELETE"
|
||||
| "GUILD_DELETE"
|
||||
| "GUILD_CREATE"
|
||||
| "MESSAGE_REACTION_REMOVE_ALL"
|
||||
| "MESSAGE_REACTION_REMOVE_EMOJI";
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
t: "GUILD_MEMBERS_CHUNK";
|
||||
d: memberChunk;
|
||||
s: number;
|
||||
}
|
||||
| {
|
||||
type roleCreate = {
|
||||
op: 0;
|
||||
t: "GUILD_ROLE_CREATE";
|
||||
d: {
|
||||
id: string;
|
||||
guild_id?: string;
|
||||
channel_id: string;
|
||||
};
|
||||
s: number;
|
||||
t: "MESSAGE_DELETE";
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
d: {
|
||||
guild_id?: string;
|
||||
channel_id: string;
|
||||
} & messagejson;
|
||||
s: number;
|
||||
t: "MESSAGE_UPDATE";
|
||||
}
|
||||
| messageCreateJson
|
||||
| readyjson
|
||||
| {
|
||||
op: 11;
|
||||
s: undefined;
|
||||
d: {};
|
||||
}
|
||||
| {
|
||||
op: 10;
|
||||
s: undefined;
|
||||
d: {
|
||||
heartbeat_interval: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
t: "MESSAGE_REACTION_ADD";
|
||||
d: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
emoji: emojijson;
|
||||
member?: memberjson;
|
||||
};
|
||||
s: number;
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
t: "MESSAGE_REACTION_REMOVE";
|
||||
d: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id: string;
|
||||
emoji: emojijson;
|
||||
role: rolesjson;
|
||||
};
|
||||
s: number;
|
||||
}|{
|
||||
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
|
||||
s: 6;
|
||||
};
|
||||
|
||||
type wsjson =
|
||||
| roleCreate
|
||||
| {
|
||||
op: 0;
|
||||
d: any;
|
||||
s: number;
|
||||
t:
|
||||
| "TYPING_START"
|
||||
| "USER_UPDATE"
|
||||
| "CHANNEL_UPDATE"
|
||||
| "CHANNEL_CREATE"
|
||||
| "CHANNEL_DELETE"
|
||||
| "GUILD_DELETE"
|
||||
| "GUILD_CREATE"
|
||||
| "MESSAGE_REACTION_REMOVE_ALL"
|
||||
| "MESSAGE_REACTION_REMOVE_EMOJI";
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
t: "GUILD_MEMBERS_CHUNK";
|
||||
d: memberChunk;
|
||||
s: number;
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
d: {
|
||||
id: string;
|
||||
guild_id?: string;
|
||||
channel_id: string;
|
||||
};
|
||||
s: number;
|
||||
t: "MESSAGE_DELETE";
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
d: {
|
||||
guild_id?: string;
|
||||
channel_id: string;
|
||||
} & messagejson;
|
||||
s: number;
|
||||
t: "MESSAGE_UPDATE";
|
||||
}
|
||||
| messageCreateJson
|
||||
| readyjson
|
||||
| {
|
||||
op: 11;
|
||||
s: undefined;
|
||||
d: {};
|
||||
}
|
||||
| {
|
||||
op: 10;
|
||||
s: undefined;
|
||||
d: {
|
||||
heartbeat_interval: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
t: "MESSAGE_REACTION_ADD";
|
||||
d: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id?: string;
|
||||
emoji: emojijson;
|
||||
member?: memberjson;
|
||||
};
|
||||
s: number;
|
||||
}
|
||||
| {
|
||||
op: 0;
|
||||
t: "MESSAGE_REACTION_REMOVE";
|
||||
d: {
|
||||
user_id: string;
|
||||
channel_id: string;
|
||||
message_id: string;
|
||||
guild_id: string;
|
||||
emoji: emojijson;
|
||||
};
|
||||
s: number;
|
||||
}
|
||||
| {
|
||||
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;
|
||||
};
|
||||
|
||||
type memberChunk = {
|
||||
guild_id: string;
|
||||
|
@ -573,134 +590,140 @@ type memberChunk = {
|
|||
chunk_count: number;
|
||||
not_found: string[];
|
||||
};
|
||||
type voiceupdate={
|
||||
op: 0,
|
||||
t: "VOICE_STATE_UPDATE",
|
||||
type voiceupdate = {
|
||||
op: 0;
|
||||
t: "VOICE_STATE_UPDATE";
|
||||
d: {
|
||||
guild_id: string,
|
||||
channel_id: string,
|
||||
user_id: string,
|
||||
member: memberjson,
|
||||
session_id: string,
|
||||
token: string,
|
||||
deaf: boolean,
|
||||
mute: boolean,
|
||||
self_deaf: boolean,
|
||||
self_mute: boolean,
|
||||
self_video: boolean,
|
||||
suppress: boolean
|
||||
},
|
||||
s: number
|
||||
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",
|
||||
type voiceserverupdate = {
|
||||
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",
|
||||
type memberlistupdatejson = {
|
||||
op: 0;
|
||||
s: number;
|
||||
t: "GUILD_MEMBER_LIST_UPDATE";
|
||||
d: {
|
||||
ops: [
|
||||
{
|
||||
items:({
|
||||
group:{
|
||||
count:number,
|
||||
id:string
|
||||
}
|
||||
}|{
|
||||
member:memberjson
|
||||
})[]
|
||||
op: "SYNC",
|
||||
range: [
|
||||
number,
|
||||
number
|
||||
]
|
||||
}
|
||||
],
|
||||
online_count: number,
|
||||
member_count: number,
|
||||
id: string,
|
||||
guild_id: string,
|
||||
items: (
|
||||
| {
|
||||
group: {
|
||||
count: number;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
member: memberjson;
|
||||
}
|
||||
)[];
|
||||
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,
|
||||
d: {
|
||||
heartbeat_interval: number
|
||||
}
|
||||
}|{
|
||||
op:6,
|
||||
d:{t: number}
|
||||
}|{
|
||||
op: 2,
|
||||
d: {
|
||||
ssrc: number,
|
||||
"streams": {
|
||||
type: "video",//probally more options, but idk
|
||||
rid: string,
|
||||
quality: number,
|
||||
ssrc: number,
|
||||
rtx_ssrc:number
|
||||
}[],
|
||||
ip: number,
|
||||
port: number,
|
||||
"modes": [],//no clue
|
||||
"experiments": []//no clue
|
||||
}
|
||||
}|sdpback|opRTC12|{
|
||||
op: 5,
|
||||
d: {
|
||||
user_id: string,
|
||||
speaking: 0,
|
||||
ssrc: 940464811
|
||||
}
|
||||
count: number;
|
||||
id: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
type sdpback={
|
||||
op: 4,
|
||||
type webRTCSocket =
|
||||
| {
|
||||
op: 8;
|
||||
d: {
|
||||
heartbeat_interval: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
op: 6;
|
||||
d: {t: number};
|
||||
}
|
||||
| {
|
||||
op: 2;
|
||||
d: {
|
||||
ssrc: number;
|
||||
streams: {
|
||||
type: "video"; //probally more options, but idk
|
||||
rid: string;
|
||||
quality: number;
|
||||
ssrc: number;
|
||||
rtx_ssrc: number;
|
||||
}[];
|
||||
ip: number;
|
||||
port: number;
|
||||
modes: []; //no clue
|
||||
experiments: []; //no clue
|
||||
};
|
||||
}
|
||||
| sdpback
|
||||
| opRTC12
|
||||
| {
|
||||
op: 5;
|
||||
d: {
|
||||
user_id: string;
|
||||
speaking: 0;
|
||||
ssrc: 940464811;
|
||||
};
|
||||
};
|
||||
type sdpback = {
|
||||
op: 4;
|
||||
d: {
|
||||
audioCodec: string,
|
||||
videoCodec: string,
|
||||
media_session_id: string,
|
||||
sdp: string
|
||||
}
|
||||
audioCodec: string;
|
||||
videoCodec: string;
|
||||
media_session_id: string;
|
||||
sdp: string;
|
||||
};
|
||||
};
|
||||
type opRTC12={
|
||||
op: 12,
|
||||
type opRTC12 = {
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
export{
|
||||
type: "fixed";
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
},
|
||||
];
|
||||
};
|
||||
};
|
||||
export {
|
||||
readyjson,
|
||||
dirrectjson,
|
||||
startTypingjson,
|
||||
|
@ -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>
|
||||
|
|
|
@ -1,55 +1,50 @@
|
|||
import { getBulkInfo, Specialuser } from "./utils/utils.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
import { Dialog, FormError } from "./settings.js";
|
||||
import { checkInstance } from "./utils/utils.js";
|
||||
|
||||
|
||||
import {getBulkInfo, Specialuser} from "./utils/utils.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {Dialog, FormError} from "./settings.js";
|
||||
import {checkInstance} from "./utils/utils.js";
|
||||
|
||||
await I18n.done;
|
||||
|
||||
|
||||
|
||||
(async ()=>{
|
||||
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")
|
||||
if(instanceField&&emailField&&pwField&&loginButton&&noAccount){
|
||||
instanceField.textContent=I18n.getTranslation("htmlPages.instanceField");
|
||||
emailField.textContent=I18n.getTranslation("htmlPages.emailField");
|
||||
pwField.textContent=I18n.getTranslation("htmlPages.pwField");
|
||||
loginButton.textContent=I18n.getTranslation("htmlPages.loginButton");
|
||||
noAccount.textContent=I18n.getTranslation("htmlPages.noAccount");
|
||||
(async () => {
|
||||
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");
|
||||
if (instanceField && emailField && pwField && loginButton && noAccount) {
|
||||
instanceField.textContent = I18n.getTranslation("htmlPages.instanceField");
|
||||
emailField.textContent = I18n.getTranslation("htmlPages.emailField");
|
||||
pwField.textContent = I18n.getTranslation("htmlPages.pwField");
|
||||
loginButton.textContent = I18n.getTranslation("htmlPages.loginButton");
|
||||
noAccount.textContent = I18n.getTranslation("htmlPages.noAccount");
|
||||
}
|
||||
})()
|
||||
})();
|
||||
|
||||
|
||||
function trimswitcher(){
|
||||
function trimswitcher() {
|
||||
const json = getBulkInfo();
|
||||
const map = new Map();
|
||||
for(const thing in json.users){
|
||||
for (const thing in json.users) {
|
||||
const user = json.users[thing];
|
||||
let wellknown = user.serverurls.wellknown;
|
||||
if(wellknown.at(-1) !== "/"){
|
||||
if (wellknown.at(-1) !== "/") {
|
||||
wellknown += "/";
|
||||
}
|
||||
wellknown =(user.id||user.email)+"@"+wellknown;
|
||||
if(map.has(wellknown)){
|
||||
wellknown = (user.id || user.email) + "@" + wellknown;
|
||||
if (map.has(wellknown)) {
|
||||
const otheruser = map.get(wellknown);
|
||||
if(otheruser[1].serverurls.wellknown.at(-1) === "/"){
|
||||
if (otheruser[1].serverurls.wellknown.at(-1) === "/") {
|
||||
delete json.users[otheruser[0]];
|
||||
map.set(wellknown, [thing, user]);
|
||||
}else{
|
||||
} else {
|
||||
delete json.users[thing];
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
map.set(wellknown, [thing, user]);
|
||||
}
|
||||
}
|
||||
for(const thing in json.users){
|
||||
if(thing.at(-1) === "/"){
|
||||
for (const thing in json.users) {
|
||||
if (thing.at(-1) === "/") {
|
||||
const user = json.users[thing];
|
||||
delete json.users[thing];
|
||||
json.users[thing.slice(0, -1)] = user;
|
||||
|
@ -59,9 +54,7 @@ function trimswitcher(){
|
|||
console.log(json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function adduser(user: typeof Specialuser.prototype.json){
|
||||
function adduser(user: typeof Specialuser.prototype.json) {
|
||||
user = new Specialuser(user);
|
||||
const info = getBulkInfo();
|
||||
info.users[user.uid] = user;
|
||||
|
@ -73,32 +66,30 @@ const instancein = document.getElementById("instancein") as HTMLInputElement;
|
|||
let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null;
|
||||
// let instanceinfo;
|
||||
|
||||
|
||||
|
||||
if(instancein){
|
||||
if (instancein) {
|
||||
console.log(instancein);
|
||||
instancein.addEventListener("keydown", ()=>{
|
||||
instancein.addEventListener("keydown", () => {
|
||||
const verify = document.getElementById("verify");
|
||||
verify!.textContent = I18n.getTranslation("login.waiting");
|
||||
if(timeout !== null && typeof timeout !== "string"){
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(()=>checkInstance((instancein as HTMLInputElement).value), 1000);
|
||||
verify!.textContent = I18n.getTranslation("login.waiting");
|
||||
if (timeout !== null && typeof timeout !== "string") {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(() => checkInstance((instancein as HTMLInputElement).value), 1000);
|
||||
});
|
||||
if(localStorage.getItem("instanceinfo")){
|
||||
if (localStorage.getItem("instanceinfo")) {
|
||||
const json = JSON.parse(localStorage.getItem("instanceinfo")!);
|
||||
if(json.value){
|
||||
if (json.value) {
|
||||
(instancein as HTMLInputElement).value = json.value;
|
||||
}else{
|
||||
} else {
|
||||
(instancein as HTMLInputElement).value = json.wellknown;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
checkInstance("https://spacebar.chat/");
|
||||
}
|
||||
}
|
||||
|
||||
async function login(username: string, password: string, captcha: string){
|
||||
if(captcha === ""){
|
||||
async function login(username: string, password: string, captcha: string) {
|
||||
if (captcha === "") {
|
||||
captcha = "";
|
||||
}
|
||||
const options = {
|
||||
|
@ -113,25 +104,25 @@ async function login(username: string, password: string, captcha: string){
|
|||
"Content-type": "application/json; charset=UTF-8",
|
||||
},
|
||||
};
|
||||
try{
|
||||
try {
|
||||
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"){
|
||||
if (response.message === "Invalid Form Body") {
|
||||
return response.errors.login._errors[0].message;
|
||||
console.log("test");
|
||||
}
|
||||
//this.serverurls||!this.email||!this.token
|
||||
console.log(response);
|
||||
|
||||
if(response.captcha_sitekey){
|
||||
if (response.captcha_sitekey) {
|
||||
const capt = document.getElementById("h-captcha");
|
||||
if(capt!.children.length){
|
||||
if (capt!.children.length) {
|
||||
eval("hcaptcha.reset()");
|
||||
}else{
|
||||
} else {
|
||||
const capty = document.createElement("div");
|
||||
capty.classList.add("h-captcha");
|
||||
|
||||
|
@ -141,107 +132,100 @@ async function login(username: string, password: string, captcha: string){
|
|||
capt!.append(script);
|
||||
capt!.append(capty);
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
console.log(response);
|
||||
if(response.ticket){
|
||||
const better=new Dialog("");
|
||||
const form=better.options.addForm("",(res:any)=>{
|
||||
if(res.message){
|
||||
throw new FormError(ti,res.message);
|
||||
}else{
|
||||
console.warn(res);
|
||||
if(!res.token)return;
|
||||
adduser({
|
||||
serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string),
|
||||
email: username,
|
||||
token: res.token,
|
||||
}).username = username;
|
||||
const redir = new URLSearchParams(
|
||||
window.location.search
|
||||
).get("goback");
|
||||
if(redir){
|
||||
window.location.href = redir;
|
||||
}else{
|
||||
window.location.href = "/channels/@me";
|
||||
if (response.ticket) {
|
||||
const better = new Dialog("");
|
||||
const form = better.options.addForm(
|
||||
"",
|
||||
(res: any) => {
|
||||
if (res.message) {
|
||||
throw new FormError(ti, res.message);
|
||||
} else {
|
||||
console.warn(res);
|
||||
if (!res.token) return;
|
||||
adduser({
|
||||
serverurls: JSON.parse(localStorage.getItem("instanceinfo") as string),
|
||||
email: username,
|
||||
token: res.token,
|
||||
}).username = username;
|
||||
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",
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
fetchURL: api + "/auth/mfa/totp",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
form.addTitle(I18n.getTranslation("2faCode"));
|
||||
const ti=form.addTextInput("","code");
|
||||
better.show()
|
||||
}else{
|
||||
const ti = form.addTextInput("", "code");
|
||||
better.show();
|
||||
} else {
|
||||
console.warn(response);
|
||||
if(!response.token)return;
|
||||
if (!response.token) return;
|
||||
adduser({
|
||||
serverurls: JSON.parse(localStorage.getItem("instanceinfo")!),
|
||||
email: username,
|
||||
token: response.token,
|
||||
}).username = username;
|
||||
const redir = new URLSearchParams(window.location.search).get(
|
||||
"goback"
|
||||
);
|
||||
if(redir){
|
||||
const redir = new URLSearchParams(window.location.search).get("goback");
|
||||
if (redir) {
|
||||
window.location.href = redir;
|
||||
}else{
|
||||
} else {
|
||||
window.location.href = "/channels/@me";
|
||||
}
|
||||
return"";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
});
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function check(e: SubmitEvent){
|
||||
async function check(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
const target = e.target as HTMLFormElement;
|
||||
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){
|
||||
if (wrongElement) {
|
||||
wrongElement.textContent = h;
|
||||
}
|
||||
console.log(h);
|
||||
}
|
||||
if(document.getElementById("form")){
|
||||
if (document.getElementById("form")) {
|
||||
const form = document.getElementById("form");
|
||||
if(form){
|
||||
form.addEventListener("submit", (e: SubmitEvent)=>check(e));
|
||||
if (form) {
|
||||
form.addEventListener("submit", (e: SubmitEvent) => check(e));
|
||||
}
|
||||
}
|
||||
//this currently does not work, and need to be implemented better at some time.
|
||||
if(!localStorage.getItem("SWMode")){
|
||||
localStorage.setItem("SWMode","true");
|
||||
if (!localStorage.getItem("SWMode")) {
|
||||
localStorage.setItem("SWMode", "true");
|
||||
}
|
||||
|
||||
const switchurl = document.getElementById("switch") as HTMLAreaElement;
|
||||
if(switchurl){
|
||||
if (switchurl) {
|
||||
switchurl.href += window.location.search;
|
||||
const instance = new URLSearchParams(window.location.search).get("instance");
|
||||
console.log(instance);
|
||||
if(instance){
|
||||
if (instance) {
|
||||
instancein.value = instance;
|
||||
checkInstance("");
|
||||
}
|
||||
}
|
||||
trimswitcher();
|
||||
|
||||
export{
|
||||
adduser,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export {adduser};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,125 +1,127 @@
|
|||
import{ User }from"./user.js";
|
||||
import{ Role }from"./role.js";
|
||||
import{ Guild }from"./guild.js";
|
||||
import{ SnowFlake }from"./snowflake.js";
|
||||
import{ memberjson, presencejson }from"./jsontypes.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
import { Dialog, Options, Settings } from "./settings.js";
|
||||
import {User} from "./user.js";
|
||||
import {Role} from "./role.js";
|
||||
import {Guild} from "./guild.js";
|
||||
import {SnowFlake} from "./snowflake.js";
|
||||
import {memberjson, presencejson} from "./jsontypes.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {Dialog, Options, Settings} from "./settings.js";
|
||||
|
||||
class Member extends SnowFlake{
|
||||
class Member extends SnowFlake {
|
||||
static already = {};
|
||||
owner: Guild;
|
||||
user: User;
|
||||
roles: Role[] = [];
|
||||
nick!: string;
|
||||
avatar:void|string=undefined;
|
||||
banner:void|string=undefined;
|
||||
private constructor(memberjson: memberjson, owner: Guild){
|
||||
avatar: void | string = undefined;
|
||||
banner: void | string = undefined;
|
||||
private constructor(memberjson: memberjson, owner: Guild) {
|
||||
super(memberjson.id);
|
||||
this.owner = owner;
|
||||
if(this.localuser.userMap.has(memberjson.id)){
|
||||
if (this.localuser.userMap.has(memberjson.id)) {
|
||||
this.user = this.localuser.userMap.get(memberjson.id) as User;
|
||||
}else if(memberjson.user){
|
||||
} else if (memberjson.user) {
|
||||
this.user = new User(memberjson.user, owner.localuser);
|
||||
}else{
|
||||
} else {
|
||||
throw new Error("Missing user object of this member");
|
||||
}
|
||||
if(this.localuser.userMap.has(this?.id)){
|
||||
if (this.localuser.userMap.has(this?.id)) {
|
||||
this.user = this.localuser.userMap.get(this?.id) as User;
|
||||
}
|
||||
for(const key of Object.keys(memberjson)){
|
||||
if(key === "guild" || key === "owner" || key === "user"){
|
||||
for (const key of Object.keys(memberjson)) {
|
||||
if (key === "guild" || key === "owner" || key === "user") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(key === "roles"){
|
||||
for(const strrole of memberjson.roles){
|
||||
if (key === "roles") {
|
||||
for (const strrole of memberjson.roles) {
|
||||
const role = this.guild.roleids.get(strrole);
|
||||
if(!role)continue;
|
||||
if (!role) continue;
|
||||
this.roles.push(role);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(key === "presence"){
|
||||
if (key === "presence") {
|
||||
this.getPresence(memberjson.presence);
|
||||
continue;
|
||||
}
|
||||
(this as any)[key] = (memberjson as any)[key];
|
||||
}
|
||||
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 (!this.user.bot) {
|
||||
const everyone = this.guild.roleids.get(this.guild.id);
|
||||
if (everyone && this.roles.indexOf(everyone) === -1) {
|
||||
this.roles.push(everyone);
|
||||
}
|
||||
}
|
||||
this.roles.sort((a, b)=>{
|
||||
this.roles.sort((a, b) => {
|
||||
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
||||
});
|
||||
}
|
||||
remove(){
|
||||
remove() {
|
||||
this.user.members.delete(this.guild);
|
||||
this.guild.members.delete(this);
|
||||
}
|
||||
getpfpsrc():string{
|
||||
if(this.hypotheticalpfp&&this.avatar){
|
||||
getpfpsrc(): string {
|
||||
if (this.hypotheticalpfp && this.avatar) {
|
||||
return this.avatar;
|
||||
}
|
||||
if(this.avatar !== undefined&&this.avatar!==null){
|
||||
return`${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${
|
||||
if (this.avatar !== undefined && this.avatar !== null) {
|
||||
return `${this.info.cdn}/guilds/${this.guild.id}/users/${this.id}/avatars${
|
||||
this.avatar
|
||||
}.${this.avatar.startsWith("a_")?"gif":"png"}`;
|
||||
}.${this.avatar.startsWith("a_") ? "gif" : "png"}`;
|
||||
}
|
||||
return this.user.getpfpsrc();
|
||||
}
|
||||
getBannerUrl():string|undefined{
|
||||
if(this.hypotheticalbanner&&this.banner){
|
||||
getBannerUrl(): string | undefined {
|
||||
if (this.hypotheticalbanner && this.banner) {
|
||||
return this.banner;
|
||||
}
|
||||
if(this.banner){
|
||||
if (this.banner) {
|
||||
return `${this.info.cdn}/banners/${this.guild.id}/${
|
||||
this.banner
|
||||
}.${this.banner.startsWith("a_")?"gif":"png"}`;;
|
||||
}else{
|
||||
this.banner
|
||||
}.${this.banner.startsWith("a_") ? "gif" : "png"}`;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
joined_at!:string;
|
||||
premium_since!:string;
|
||||
deaf!:boolean;
|
||||
mute!:boolean;
|
||||
pending!:boolean
|
||||
clone(){
|
||||
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),
|
||||
//TODO presence
|
||||
nick:this.nick,
|
||||
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)
|
||||
joined_at!: string;
|
||||
premium_since!: string;
|
||||
deaf!: boolean;
|
||||
mute!: boolean;
|
||||
pending!: boolean;
|
||||
clone() {
|
||||
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,
|
||||
//TODO presence
|
||||
nick: this.nick,
|
||||
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,
|
||||
);
|
||||
}
|
||||
pronouns?:string;
|
||||
bio?:string;
|
||||
hypotheticalpfp=false;
|
||||
hypotheticalbanner=false;
|
||||
accent_color?:number;
|
||||
get headers(){
|
||||
pronouns?: string;
|
||||
bio?: string;
|
||||
hypotheticalpfp = false;
|
||||
hypotheticalbanner = false;
|
||||
accent_color?: number;
|
||||
get headers() {
|
||||
return this.owner.headers;
|
||||
}
|
||||
|
||||
updatepfp(file: Blob): void{
|
||||
updatepfp(file: Blob): void {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = ()=>{
|
||||
reader.onload = () => {
|
||||
fetch(this.info.api + `/guilds/${this.guild.id}/members/${this.id}/`, {
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
|
@ -129,11 +131,11 @@ class Member extends SnowFlake{
|
|||
});
|
||||
};
|
||||
}
|
||||
updatebanner(file: Blob | null): void{
|
||||
if(file){
|
||||
updatebanner(file: Blob | null): void {
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = ()=>{
|
||||
reader.onload = () => {
|
||||
fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, {
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
|
@ -142,7 +144,7 @@ class Member extends SnowFlake{
|
|||
}),
|
||||
});
|
||||
};
|
||||
}else{
|
||||
} else {
|
||||
fetch(this.info.api + `/guilds/${this.guild.id}/profile/${this.id}`, {
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
|
@ -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===""){
|
||||
|
@ -176,24 +174,26 @@ class Member extends SnowFlake{
|
|||
body: JSON.stringify(json),
|
||||
});
|
||||
}
|
||||
showEditProfile(){
|
||||
const settings=new Settings("");
|
||||
this.editProfile(settings.addButton(I18n.getTranslation("user.editServerProfile"),{ltr:true}));
|
||||
showEditProfile() {
|
||||
const settings = new Settings("");
|
||||
this.editProfile(
|
||||
settings.addButton(I18n.getTranslation("user.editServerProfile"), {ltr: true}),
|
||||
);
|
||||
settings.show();
|
||||
}
|
||||
editProfile(options:Options){
|
||||
if(this.hasPermission("CHANGE_NICKNAME")){
|
||||
editProfile(options: Options) {
|
||||
if (this.hasPermission("CHANGE_NICKNAME")) {
|
||||
const hypotheticalProfile = document.createElement("div");
|
||||
let file: undefined | File | null;
|
||||
let newpronouns: string | undefined;
|
||||
let newbio: string | undefined;
|
||||
let nick:string|undefined;
|
||||
let nick: string | undefined;
|
||||
const hypomember = this.clone();
|
||||
|
||||
let color: string;
|
||||
async function regen(){
|
||||
async function regen() {
|
||||
hypotheticalProfile.textContent = "";
|
||||
const hypoprofile = await hypomember.user.buildprofile(-1, -1,hypomember);
|
||||
const hypoprofile = await hypomember.user.buildprofile(-1, -1, hypomember);
|
||||
|
||||
hypotheticalProfile.appendChild(hypoprofile);
|
||||
}
|
||||
|
@ -202,33 +202,33 @@ class Member extends SnowFlake{
|
|||
const settingsRight = options.addOptions("");
|
||||
settingsRight.addHTMLArea(hypotheticalProfile);
|
||||
|
||||
const nicky=settingsLeft.addTextInput(I18n.getTranslation("member.nick:"),()=>{},{
|
||||
initText:this.nick||""
|
||||
const nicky = settingsLeft.addTextInput(I18n.getTranslation("member.nick:"), () => {}, {
|
||||
initText: this.nick || "",
|
||||
});
|
||||
nicky.watchForChange(_=>{
|
||||
hypomember.nick=_;
|
||||
nick=_;
|
||||
nicky.watchForChange((_) => {
|
||||
hypomember.nick = _;
|
||||
nick = _;
|
||||
regen();
|
||||
})
|
||||
});
|
||||
|
||||
const finput = settingsLeft.addFileInput(
|
||||
I18n.getTranslation("uploadPfp"),
|
||||
_=>{
|
||||
if(file){
|
||||
(_) => {
|
||||
if (file) {
|
||||
this.updatepfp(file);
|
||||
}
|
||||
},
|
||||
{ clear: true }
|
||||
{clear: true},
|
||||
);
|
||||
finput.watchForChange(_=>{
|
||||
if(!_){
|
||||
finput.watchForChange((_) => {
|
||||
if (!_) {
|
||||
file = null;
|
||||
hypomember.avatar = undefined;
|
||||
hypomember.hypotheticalpfp = true;
|
||||
regen();
|
||||
return;
|
||||
}
|
||||
if(_.length){
|
||||
if (_.length) {
|
||||
file = _[0];
|
||||
const blob = URL.createObjectURL(file);
|
||||
hypomember.avatar = blob;
|
||||
|
@ -239,22 +239,22 @@ class Member extends SnowFlake{
|
|||
let bfile: undefined | File | null;
|
||||
const binput = settingsLeft.addFileInput(
|
||||
I18n.getTranslation("uploadBanner"),
|
||||
_=>{
|
||||
if(bfile !== undefined){
|
||||
(_) => {
|
||||
if (bfile !== undefined) {
|
||||
this.updatebanner(bfile);
|
||||
}
|
||||
},
|
||||
{ clear: true }
|
||||
{clear: true},
|
||||
);
|
||||
binput.watchForChange(_=>{
|
||||
if(!_){
|
||||
binput.watchForChange((_) => {
|
||||
if (!_) {
|
||||
bfile = null;
|
||||
hypomember.banner = undefined;
|
||||
hypomember.hypotheticalbanner = true;
|
||||
regen();
|
||||
return;
|
||||
}
|
||||
if(_.length){
|
||||
if (_.length) {
|
||||
bfile = _[0];
|
||||
const blob = URL.createObjectURL(bfile);
|
||||
hypomember.banner = blob;
|
||||
|
@ -265,43 +265,43 @@ class Member extends SnowFlake{
|
|||
let changed = false;
|
||||
const pronounbox = settingsLeft.addTextInput(
|
||||
I18n.getTranslation("pronouns"),
|
||||
_=>{
|
||||
if(newpronouns!==undefined||newbio!==undefined||changed!==undefined){
|
||||
(_) => {
|
||||
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();
|
||||
});
|
||||
return;//Returns early to stop errors
|
||||
if(this.accent_color){
|
||||
return; //Returns early to stop errors
|
||||
if (this.accent_color) {
|
||||
color = "#" + this.accent_color.toString(16);
|
||||
}else{
|
||||
} else {
|
||||
color = "transparent";
|
||||
}
|
||||
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);
|
||||
|
@ -310,116 +310,116 @@ class Member extends SnowFlake{
|
|||
});
|
||||
}
|
||||
}
|
||||
update(memberjson: memberjson){
|
||||
if(memberjson.roles){
|
||||
this.roles=[];
|
||||
update(memberjson: memberjson) {
|
||||
if (memberjson.roles) {
|
||||
this.roles = [];
|
||||
}
|
||||
for(const key of Object.keys(memberjson)){
|
||||
if(key === "guild" || key === "owner" || key === "user"){
|
||||
for (const key of Object.keys(memberjson)) {
|
||||
if (key === "guild" || key === "owner" || key === "user") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(key === "roles"){
|
||||
for(const strrole of memberjson.roles){
|
||||
if (key === "roles") {
|
||||
for (const strrole of memberjson.roles) {
|
||||
const role = this.guild.roleids.get(strrole);
|
||||
if(!role)continue;
|
||||
if (!role) continue;
|
||||
this.roles.push(role);
|
||||
}
|
||||
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 (!this.user.bot) {
|
||||
const everyone = this.guild.roleids.get(this.guild.id);
|
||||
if (everyone && this.roles.indexOf(everyone) === -1) {
|
||||
this.roles.push(everyone);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(key === "presence"){
|
||||
if (key === "presence") {
|
||||
this.getPresence(memberjson.presence);
|
||||
continue;
|
||||
}
|
||||
(this as any)[key] = (memberjson as any)[key];
|
||||
}
|
||||
|
||||
this.roles.sort((a, b)=>{
|
||||
this.roles.sort((a, b) => {
|
||||
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
|
||||
});
|
||||
}
|
||||
get guild(){
|
||||
get guild() {
|
||||
return this.owner;
|
||||
}
|
||||
get localuser(){
|
||||
get localuser() {
|
||||
return this.guild.localuser;
|
||||
}
|
||||
get info(){
|
||||
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){
|
||||
if (owner.localuser.userMap.has(memberjson.id)) {
|
||||
if (memberjson.user) {
|
||||
new User(memberjson.user, owner.localuser);
|
||||
}
|
||||
user = owner.localuser.userMap.get(memberjson.id) as User;
|
||||
}else if(memberjson.user){
|
||||
} else if (memberjson.user) {
|
||||
user = new User(memberjson.user, owner.localuser);
|
||||
}else{
|
||||
} else {
|
||||
throw new Error("missing user object of this member");
|
||||
}
|
||||
if(user.members.has(owner)){
|
||||
if (user.members.has(owner)) {
|
||||
let memb = user.members.get(owner);
|
||||
if(memb === undefined){
|
||||
if (memb === undefined) {
|
||||
memb = new Member(memberjson, owner);
|
||||
user.members.set(owner, memb);
|
||||
owner.members.add(memb);
|
||||
return memb;
|
||||
}else if(memb instanceof Promise){
|
||||
const member=await memb; //I should do something else, though for now this is "good enough";
|
||||
if(member){
|
||||
} else if (memb instanceof Promise) {
|
||||
const member = await memb; //I should do something else, though for now this is "good enough";
|
||||
if (member) {
|
||||
member.update(memberjson);
|
||||
}
|
||||
return member;
|
||||
}else{
|
||||
if(memberjson.presence){
|
||||
} else {
|
||||
if (memberjson.presence) {
|
||||
memb.getPresence(memberjson.presence);
|
||||
}
|
||||
memb.update(memberjson);
|
||||
return memb;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
const memb = new Member(memberjson, owner);
|
||||
user.members.set(owner, memb);
|
||||
owner.members.add(memb);
|
||||
return memb;
|
||||
}
|
||||
}
|
||||
compare(str:string){
|
||||
function similar(str2:string|null|undefined){
|
||||
if(!str2) return 0;
|
||||
const strl=Math.max(str.length,1)
|
||||
if(str2.includes(str)){
|
||||
return strl/str2.length;
|
||||
}else if(str2.toLowerCase().includes(str.toLowerCase())){
|
||||
return strl/str2.length/1.2;
|
||||
compare(str: string) {
|
||||
function similar(str2: string | null | undefined) {
|
||||
if (!str2) return 0;
|
||||
const strl = Math.max(str.length, 1);
|
||||
if (str2.includes(str)) {
|
||||
return strl / str2.length;
|
||||
} else if (str2.toLowerCase().includes(str.toLowerCase())) {
|
||||
return strl / str2.length / 1.2;
|
||||
}
|
||||
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)){
|
||||
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){
|
||||
if (membjson === undefined) {
|
||||
return res(undefined);
|
||||
}else{
|
||||
} else {
|
||||
const member = new Member(membjson, guild);
|
||||
const map = guild.localuser.presences;
|
||||
member.getPresence(map.get(member.id));
|
||||
|
@ -429,62 +429,62 @@ class Member extends SnowFlake{
|
|||
}
|
||||
});
|
||||
user.members.set(guild, promise);
|
||||
const member=await promise;
|
||||
if(member){
|
||||
const member = await promise;
|
||||
if (member) {
|
||||
guild.members.add(member);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
if(maybe instanceof Promise){
|
||||
if (maybe instanceof Promise) {
|
||||
return await maybe;
|
||||
}else{
|
||||
} else {
|
||||
return maybe;
|
||||
}
|
||||
}
|
||||
public getPresence(presence: presencejson | undefined){
|
||||
public getPresence(presence: presencejson | undefined) {
|
||||
this.user.getPresence(presence);
|
||||
}
|
||||
/**
|
||||
* @todo
|
||||
*/
|
||||
highInfo(){
|
||||
* @todo
|
||||
*/
|
||||
highInfo() {
|
||||
fetch(
|
||||
this.info.api +
|
||||
"/users/" +
|
||||
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){
|
||||
for(const thing of this.roles){
|
||||
if(thing.id === ID){
|
||||
hasRole(ID: string) {
|
||||
for (const thing of this.roles) {
|
||||
if (thing.id === ID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
getColor(){
|
||||
for(const thing of this.roles){
|
||||
getColor() {
|
||||
for (const thing of this.roles) {
|
||||
const color = thing.getColor();
|
||||
if(color){
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
return"";
|
||||
return "";
|
||||
}
|
||||
isAdmin(){
|
||||
for(const role of this.roles){
|
||||
if(role.permissions.getPermission("ADMINISTRATOR")){
|
||||
isAdmin() {
|
||||
for (const role of this.roles) {
|
||||
if (role.permissions.getPermission("ADMINISTRATOR")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return this.guild.properties.owner_id === this.user.id;
|
||||
}
|
||||
bind(html: HTMLElement){
|
||||
if(html.tagName === "SPAN"){
|
||||
if(!this){
|
||||
bind(html: HTMLElement) {
|
||||
if (html.tagName === "SPAN") {
|
||||
if (!this) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
|
@ -497,23 +497,23 @@ class Member extends SnowFlake{
|
|||
|
||||
//this.profileclick(html);
|
||||
}
|
||||
profileclick(/* html: HTMLElement */){
|
||||
profileclick(/* html: HTMLElement */) {
|
||||
//to be implemented
|
||||
}
|
||||
get name(){
|
||||
get name() {
|
||||
return this.nick || this.user.username;
|
||||
}
|
||||
kick(){
|
||||
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");
|
||||
});
|
||||
form.addTitle(I18n.getTranslation("member.kick", this.name, this.guild.properties.name));
|
||||
form.addTextInput(I18n.getTranslation("member.reason:"), "reason");
|
||||
menu.show();
|
||||
}
|
||||
kickAPI(reason: string){
|
||||
kickAPI(reason: string) {
|
||||
const headers = structuredClone(this.guild.headers);
|
||||
(headers as any)["x-audit-log-reason"] = reason;
|
||||
fetch(`${this.info.api}/guilds/${this.guild.id}/members/${this.id}`, {
|
||||
|
@ -521,35 +521,35 @@ class Member extends SnowFlake{
|
|||
headers,
|
||||
});
|
||||
}
|
||||
ban(){
|
||||
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");
|
||||
});
|
||||
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)
|
||||
addRole(role: Role) {
|
||||
const roles = this.roles.map((_) => _.id);
|
||||
roles.push(role.id);
|
||||
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
|
||||
method:"PATCH",
|
||||
headers:this.guild.headers,
|
||||
body:JSON.stringify({roles})
|
||||
})
|
||||
fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
|
||||
method: "PATCH",
|
||||
headers: this.guild.headers,
|
||||
body: JSON.stringify({roles}),
|
||||
});
|
||||
}
|
||||
removeRole(role:Role){
|
||||
let roles=this.roles.map(_=>_.id)
|
||||
roles=roles.filter(_=>_!==role.id);
|
||||
fetch(this.info.api+"/guilds/"+this.guild.id+"/members/"+this.id,{
|
||||
method:"PATCH",
|
||||
headers:this.guild.headers,
|
||||
body:JSON.stringify({roles})
|
||||
})
|
||||
removeRole(role: Role) {
|
||||
let roles = this.roles.map((_) => _.id);
|
||||
roles = roles.filter((_) => _ !== role.id);
|
||||
fetch(this.info.api + "/guilds/" + this.guild.id + "/members/" + this.id, {
|
||||
method: "PATCH",
|
||||
headers: this.guild.headers,
|
||||
body: JSON.stringify({roles}),
|
||||
});
|
||||
}
|
||||
banAPI(reason: string){
|
||||
banAPI(reason: string) {
|
||||
const headers = structuredClone(this.guild.headers);
|
||||
(headers as any)["x-audit-log-reason"] = reason;
|
||||
fetch(`${this.info.api}/guilds/${this.guild.id}/bans/${this.id}`, {
|
||||
|
@ -557,16 +557,16 @@ class Member extends SnowFlake{
|
|||
headers,
|
||||
});
|
||||
}
|
||||
hasPermission(name: string): boolean{
|
||||
if(this.isAdmin()){
|
||||
hasPermission(name: string): boolean {
|
||||
if (this.isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
for(const thing of this.roles){
|
||||
if(thing.permissions.getPermission(name)){
|
||||
for (const thing of this.roles) {
|
||||
if (thing.permissions.getPermission(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export{ Member };
|
||||
export {Member};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,157 +1,157 @@
|
|||
import { I18n } from "../i18n.js";
|
||||
import{ getapiurls }from"../utils/utils.js";
|
||||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
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
|
||||
}
|
||||
(async ()=>{
|
||||
import {I18n} from "../i18n.js";
|
||||
import {getapiurls} from "../utils/utils.js";
|
||||
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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
(async () => {
|
||||
const users = getBulkUsers();
|
||||
const params=new URLSearchParams(window.location.search);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const well = params.get("instance");
|
||||
const permstr=params.get("permissions");
|
||||
const permstr = params.get("permissions");
|
||||
const joinable: Specialuser[] = [];
|
||||
|
||||
for(const key in users.users){
|
||||
if(Object.prototype.hasOwnProperty.call(users.users, key)){
|
||||
for (const key in users.users) {
|
||||
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
|
||||
const user: Specialuser = users.users[key];
|
||||
if(well && user.serverurls.wellknown.includes(well)){
|
||||
if (well && user.serverurls.wellknown.includes(well)) {
|
||||
joinable.push(user);
|
||||
}
|
||||
console.log(user);
|
||||
}
|
||||
}
|
||||
|
||||
let urls: { api: string; cdn: string } | undefined;
|
||||
let urls: {api: string; cdn: string} | undefined;
|
||||
|
||||
if(!joinable.length && well){
|
||||
if (!joinable.length && well) {
|
||||
const out = await getapiurls(well);
|
||||
if(out){
|
||||
if (out) {
|
||||
urls = out;
|
||||
for(const key in users.users){
|
||||
if(Object.prototype.hasOwnProperty.call(users.users, key)){
|
||||
for (const key in users.users) {
|
||||
if (Object.prototype.hasOwnProperty.call(users.users, key)) {
|
||||
const user: Specialuser = users.users[key];
|
||||
if(user.serverurls.api.includes(out.api)){
|
||||
if (user.serverurls.api.includes(out.api)) {
|
||||
joinable.push(user);
|
||||
}
|
||||
console.log(user);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
throw new Error(
|
||||
"Someone needs to handle the case where the servers don't exist"
|
||||
);
|
||||
} else {
|
||||
throw new Error("Someone needs to handle the case where the servers don't exist");
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
urls = joinable[0].serverurls;
|
||||
}
|
||||
|
||||
if(!joinable.length){
|
||||
document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot";
|
||||
if (!joinable.length) {
|
||||
document.getElementById("AcceptInvite")!.textContent = "Create an account to invite the bot";
|
||||
}
|
||||
await I18n.done;
|
||||
function showGuilds(user:Specialuser){
|
||||
if(!urls) return;
|
||||
fetch(urls.api+"/oauth2/authorize/"+window.location.search,{
|
||||
headers:{
|
||||
Authorization:user.token
|
||||
}
|
||||
}).then(_=>_.json()).then((json:botjsonfetch)=>{
|
||||
const guilds:botjsonfetch["guilds"]=[];
|
||||
for(const guild of json.guilds){
|
||||
const permisions=new Permissions(guild.permissions)
|
||||
if(permisions.hasPermission("MANAGE_GUILD")){
|
||||
guilds.push(guild);
|
||||
}
|
||||
}
|
||||
const dialog=document.createElement("dialog");
|
||||
dialog.classList.add("flexttb","accountSwitcher");
|
||||
const h1=document.createElement("h1");
|
||||
dialog.append(h1);
|
||||
h1.textContent="Invite to server:";
|
||||
const select=document.createElement("select");
|
||||
const selectSpan=document.createElement("span");
|
||||
selectSpan.classList.add("selectspan");
|
||||
const selectArrow = document.createElement("span");
|
||||
selectArrow.classList.add("svgicon","svg-category","selectarrow");
|
||||
for(const guild of guilds){
|
||||
const option=document.createElement("option");
|
||||
option.textContent=guild.name;
|
||||
option.value=guild.id;
|
||||
select.append(option);
|
||||
}
|
||||
selectSpan.append(select);
|
||||
selectSpan.append(selectArrow);
|
||||
dialog.append(selectSpan);
|
||||
const button=document.createElement("button");
|
||||
button.textContent="Invite";
|
||||
dialog.append(button);
|
||||
button.onclick=()=>{
|
||||
const id=select.value;
|
||||
const params2=new URLSearchParams("");
|
||||
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
|
||||
}),
|
||||
headers:{
|
||||
"Content-type": "application/json; charset=UTF-8",
|
||||
Authorization:user.token,
|
||||
}
|
||||
}).then(req=>{
|
||||
if(req.ok){
|
||||
alert("Bot added successfully");
|
||||
}
|
||||
})
|
||||
}
|
||||
document.body.append(dialog);
|
||||
function showGuilds(user: Specialuser) {
|
||||
if (!urls) return;
|
||||
fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
|
||||
headers: {
|
||||
Authorization: user.token,
|
||||
},
|
||||
})
|
||||
.then((_) => _.json())
|
||||
.then((json: botjsonfetch) => {
|
||||
const guilds: botjsonfetch["guilds"] = [];
|
||||
for (const guild of json.guilds) {
|
||||
const permisions = new Permissions(guild.permissions);
|
||||
if (permisions.hasPermission("MANAGE_GUILD")) {
|
||||
guilds.push(guild);
|
||||
}
|
||||
}
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.classList.add("flexttb", "accountSwitcher");
|
||||
const h1 = document.createElement("h1");
|
||||
dialog.append(h1);
|
||||
h1.textContent = "Invite to server:";
|
||||
const select = document.createElement("select");
|
||||
const selectSpan = document.createElement("span");
|
||||
selectSpan.classList.add("selectspan");
|
||||
const selectArrow = document.createElement("span");
|
||||
selectArrow.classList.add("svgicon", "svg-category", "selectarrow");
|
||||
for (const guild of guilds) {
|
||||
const option = document.createElement("option");
|
||||
option.textContent = guild.name;
|
||||
option.value = guild.id;
|
||||
select.append(option);
|
||||
}
|
||||
selectSpan.append(select);
|
||||
selectSpan.append(selectArrow);
|
||||
dialog.append(selectSpan);
|
||||
const button = document.createElement("button");
|
||||
button.textContent = "Invite";
|
||||
dialog.append(button);
|
||||
button.onclick = () => {
|
||||
const id = select.value;
|
||||
const params2 = new URLSearchParams("");
|
||||
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,
|
||||
}),
|
||||
headers: {
|
||||
"Content-type": "application/json; charset=UTF-8",
|
||||
Authorization: user.token,
|
||||
},
|
||||
}).then((req) => {
|
||||
if (req.ok) {
|
||||
alert("Bot added successfully");
|
||||
}
|
||||
});
|
||||
};
|
||||
document.body.append(dialog);
|
||||
});
|
||||
}
|
||||
function showAccounts(): void{
|
||||
function showAccounts(): void {
|
||||
const table = document.createElement("dialog");
|
||||
for(const user of joinable){
|
||||
for (const user of joinable) {
|
||||
console.log(user.pfpsrc);
|
||||
|
||||
const userinfo = document.createElement("div");
|
||||
|
@ -168,16 +168,14 @@ 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);
|
||||
|
||||
userinfo.append(userDiv);
|
||||
table.append(userinfo);
|
||||
|
||||
userinfo.addEventListener("click", ()=>{
|
||||
userinfo.addEventListener("click", () => {
|
||||
table.remove();
|
||||
showGuilds(user);
|
||||
});
|
||||
|
@ -186,14 +184,14 @@ type botjsonfetch={
|
|||
const td = document.createElement("div");
|
||||
td.classList.add("switchtable");
|
||||
td.textContent = "Login or create an account ⇌";
|
||||
td.addEventListener("click", ()=>{
|
||||
td.addEventListener("click", () => {
|
||||
const l = new URLSearchParams("?");
|
||||
l.set("goback", window.location.href);
|
||||
l.set("instance", well!);
|
||||
window.location.href = "/login?" + l.toString();
|
||||
});
|
||||
|
||||
if(!joinable.length){
|
||||
if (!joinable.length) {
|
||||
const l = new URLSearchParams("?");
|
||||
l.set("goback", window.location.href);
|
||||
l.set("instance", well!);
|
||||
|
@ -201,54 +199,56 @@ type botjsonfetch={
|
|||
}
|
||||
|
||||
table.append(td);
|
||||
table.classList.add("flexttb","accountSwitcher");
|
||||
table.classList.add("flexttb", "accountSwitcher");
|
||||
console.log(table);
|
||||
document.body.append(table);
|
||||
}
|
||||
const user=joinable[0];
|
||||
if(!user){
|
||||
const user = joinable[0];
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
fetch(urls.api+"/oauth2/authorize/"+window.location.search,{
|
||||
headers:{
|
||||
Authorization:user.token
|
||||
}
|
||||
}).then(_=>_.json()).then((json:botjsonfetch)=>{
|
||||
const title=document.getElementById("invitename");
|
||||
if(title){
|
||||
title.textContent=`Invite ${json.bot.username} to your servers`
|
||||
}
|
||||
const desc=document.getElementById("invitedescription");
|
||||
if(desc){
|
||||
desc.textContent=json.application.description;
|
||||
}
|
||||
const pfp=document.getElementById("inviteimg") as HTMLImageElement;
|
||||
if(json.bot.avatar !== null){
|
||||
pfp.src=`${urls.cdn}/avatars/${json.bot.id}/${json.bot.avatar}.png`;
|
||||
}else{
|
||||
const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
|
||||
pfp.src=`${urls.cdn}/embed/avatars/${int}.png`;
|
||||
}
|
||||
const perms=document.getElementById("permissions") as HTMLDivElement;
|
||||
fetch(urls.api + "/oauth2/authorize/" + window.location.search, {
|
||||
headers: {
|
||||
Authorization: user.token,
|
||||
},
|
||||
})
|
||||
.then((_) => _.json())
|
||||
.then((json: botjsonfetch) => {
|
||||
const title = document.getElementById("invitename");
|
||||
if (title) {
|
||||
title.textContent = `Invite ${json.bot.username} to your servers`;
|
||||
}
|
||||
const desc = document.getElementById("invitedescription");
|
||||
if (desc) {
|
||||
desc.textContent = json.application.description;
|
||||
}
|
||||
const pfp = document.getElementById("inviteimg") as HTMLImageElement;
|
||||
if (json.bot.avatar !== null) {
|
||||
pfp.src = `${urls.cdn}/avatars/${json.bot.id}/${json.bot.avatar}.png`;
|
||||
} else {
|
||||
const int = Number((BigInt(json.bot.id) >> 22n) % 6n);
|
||||
pfp.src = `${urls.cdn}/embed/avatars/${int}.png`;
|
||||
}
|
||||
const perms = document.getElementById("permissions") as HTMLDivElement;
|
||||
|
||||
if(perms&&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");
|
||||
const h2=document.createElement("h2");
|
||||
h2.textContent=perm.readableName;
|
||||
div.append(h2,perm.description);
|
||||
div.classList.add("flexttb");
|
||||
perms.append(div);
|
||||
if (perms && 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");
|
||||
const h2 = document.createElement("h2");
|
||||
h2.textContent = perm.readableName;
|
||||
div.append(h2, perm.description);
|
||||
div.classList.add("flexttb");
|
||||
perms.append(div);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const AcceptInvite=document.getElementById("AcceptInvite");
|
||||
if(AcceptInvite){
|
||||
});
|
||||
const AcceptInvite = document.getElementById("AcceptInvite");
|
||||
if (AcceptInvite) {
|
||||
AcceptInvite.addEventListener("click", showAccounts);
|
||||
AcceptInvite.textContent=I18n.getTranslation("htmlPages.addBot")
|
||||
AcceptInvite.textContent = I18n.getTranslation("htmlPages.addBot");
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -1,21 +1,30 @@
|
|||
<!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>
|
||||
<div id="invitebody">
|
||||
<img id="inviteimg" class="pfp"/>
|
||||
<img id="inviteimg" class="pfp" />
|
||||
<h1 id="invitename">Bot Name</h1>
|
||||
<p id="invitedescription">Add Bot</p>
|
||||
<div id="permissions"><h1>This will allow the bot to:</h1></div>
|
||||
|
@ -24,4 +33,4 @@
|
|||
</div>
|
||||
<script type="module" src="/oauth2/auth.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
import { I18n } from "./i18n.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
|
||||
class Permissions{
|
||||
class Permissions {
|
||||
allow: bigint;
|
||||
deny: bigint;
|
||||
readonly hasDeny: boolean;
|
||||
constructor(allow: string, deny: string = ""){
|
||||
constructor(allow: string, deny: string = "") {
|
||||
this.hasDeny = Boolean(deny);
|
||||
try{
|
||||
try {
|
||||
this.allow = BigInt(allow);
|
||||
this.deny = BigInt(deny);
|
||||
}catch{
|
||||
} catch {
|
||||
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.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
getPermissionbit(b: number, big: bigint): boolean{
|
||||
getPermissionbit(b: number, big: bigint): boolean {
|
||||
return Boolean((big >> BigInt(b)) & 1n);
|
||||
}
|
||||
setPermissionbit(b: number, state: boolean, big: bigint): bigint{
|
||||
setPermissionbit(b: number, state: boolean, big: bigint): bigint {
|
||||
const bit = 1n << BigInt(b);
|
||||
return(big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
|
||||
return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3
|
||||
}
|
||||
//private static info: { name: string; readableName: string; description: string }[];
|
||||
static *info():Generator<{ name: string; readableName: string; description: string }>{
|
||||
for(const thing of this.permisions){
|
||||
static *info(): Generator<{name: string; readableName: string; description: string}> {
|
||||
for (const thing of this.permisions) {
|
||||
yield {
|
||||
name:thing,
|
||||
readableName:I18n.getTranslation("permissions.readableNames."+thing),
|
||||
description:I18n.getTranslation("permissions.descriptions."+thing),
|
||||
}
|
||||
name: thing,
|
||||
readableName: I18n.getTranslation("permissions.readableNames." + thing),
|
||||
description: I18n.getTranslation("permissions.descriptions." + thing),
|
||||
};
|
||||
}
|
||||
}
|
||||
static permisions=[
|
||||
static permisions = [
|
||||
"CREATE_INSTANT_INVITE",
|
||||
"KICK_MEMBERS",
|
||||
"BAN_MEMBERS",
|
||||
|
@ -83,57 +83,50 @@ 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)){
|
||||
console.error(name +" is not found in map",Permissions.permisions);
|
||||
getPermission(name: string): number {
|
||||
if (undefined === Permissions.permisions.indexOf(name)) {
|
||||
console.error(name + " is not found in map", Permissions.permisions);
|
||||
}
|
||||
if(this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)){
|
||||
if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) {
|
||||
return 1;
|
||||
}else if(
|
||||
this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)
|
||||
){
|
||||
return-1;
|
||||
}else{
|
||||
} else if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.deny)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
hasPermission(name: string,adminOverride=true): boolean{
|
||||
if(this.deny){
|
||||
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(name !== "ADMINISTRATOR"&&adminOverride)return this.hasPermission("ADMINISTRATOR");
|
||||
if (this.getPermissionbit(Permissions.permisions.indexOf(name), this.allow)) return true;
|
||||
if (name !== "ADMINISTRATOR" && adminOverride) return this.hasPermission("ADMINISTRATOR");
|
||||
return false;
|
||||
}
|
||||
setPermission(name: string, setto: number): void{
|
||||
setPermission(name: string, setto: number): void {
|
||||
const bit = Permissions.permisions.indexOf(name);
|
||||
if(bit===undefined){
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
if(setto === 0){
|
||||
if (setto === 0) {
|
||||
this.deny = this.setPermissionbit(bit, false, this.deny);
|
||||
this.allow = this.setPermissionbit(bit, false, this.allow);
|
||||
}else if(setto === 1){
|
||||
} else if (setto === 1) {
|
||||
this.deny = this.setPermissionbit(bit, false, this.deny);
|
||||
this.allow = this.setPermissionbit(bit, true, this.allow);
|
||||
}else if(setto === -1){
|
||||
} else if (setto === -1) {
|
||||
this.deny = this.setPermissionbit(bit, true, this.deny);
|
||||
this.allow = this.setPermissionbit(bit, false, this.allow);
|
||||
}else{
|
||||
} else {
|
||||
console.error("invalid number entered:" + setto);
|
||||
}
|
||||
}
|
||||
}
|
||||
export{ Permissions };
|
||||
export {Permissions};
|
||||
|
|
|
@ -1,59 +1,80 @@
|
|||
<!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">
|
||||
<h1>Create an account</h1>
|
||||
<form id="register" submit="registertry(e)">
|
||||
<div>
|
||||
<label for="instance" id="instanceField"><b>Instance:</b></label>
|
||||
<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>
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
import { I18n } from "./i18n.js";
|
||||
import{ checkInstance }from"./utils/utils.js";
|
||||
import {adduser} from"./login.js";
|
||||
import { MarkDown } from "./markdown.js";
|
||||
await I18n.done
|
||||
import {I18n} from "./i18n.js";
|
||||
import {checkInstance} from "./utils/utils.js";
|
||||
import {adduser} from "./login.js";
|
||||
import {MarkDown} from "./markdown.js";
|
||||
await I18n.done;
|
||||
const registerElement = document.getElementById("register");
|
||||
if(registerElement){
|
||||
if (registerElement) {
|
||||
registerElement.addEventListener("submit", registertry);
|
||||
}
|
||||
(async ()=>{
|
||||
(async () => {
|
||||
await I18n.done;
|
||||
const userField=document.getElementById("userField");
|
||||
const pw2Field=document.getElementById("pw2Field");
|
||||
const dobField=document.getElementById("dobField");
|
||||
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")
|
||||
const userField = document.getElementById("userField");
|
||||
const pw2Field = document.getElementById("pw2Field");
|
||||
const dobField = document.getElementById("dobField");
|
||||
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");
|
||||
}
|
||||
})()
|
||||
async function registertry(e: Event){
|
||||
})();
|
||||
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;
|
||||
|
@ -34,15 +33,17 @@ async function registertry(e: Event){
|
|||
const consent = (elements[6] as HTMLInputElement).checked;
|
||||
const captchaKey = (elements[7] as HTMLInputElement)?.value;
|
||||
|
||||
if(password !== confirmPassword){
|
||||
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation("localuser.PasswordsNoMatch");
|
||||
if (password !== confirmPassword) {
|
||||
(document.getElementById("wrong") as HTMLElement).textContent = I18n.getTranslation(
|
||||
"localuser.PasswordsNoMatch",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
|
||||
const apiurl = new URL(instanceInfo.api);
|
||||
|
||||
try{
|
||||
try {
|
||||
const response = await fetch(apiurl + "/auth/register", {
|
||||
body: JSON.stringify({
|
||||
date_of_birth: dateofbirth,
|
||||
|
@ -60,9 +61,9 @@ async function registertry(e: Event){
|
|||
|
||||
const data = await response.json();
|
||||
|
||||
if(data.captcha_sitekey){
|
||||
if (data.captcha_sitekey) {
|
||||
const capt = document.getElementById("h-captcha");
|
||||
if(capt && !capt.children.length){
|
||||
if (capt && !capt.children.length) {
|
||||
const capty = document.createElement("div");
|
||||
capty.classList.add("h-captcha");
|
||||
capty.setAttribute("data-sitekey", data.captcha_sitekey);
|
||||
|
@ -70,15 +71,15 @@ async function registertry(e: Event){
|
|||
script.src = "https://js.hcaptcha.com/1/api.js";
|
||||
capt.append(script);
|
||||
capt.append(capty);
|
||||
}else{
|
||||
} else {
|
||||
eval("hcaptcha.reset()");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!data.token){
|
||||
if (!data.token) {
|
||||
handleErrors(data.errors, elements);
|
||||
}else{
|
||||
} else {
|
||||
adduser({
|
||||
serverurls: instanceInfo,
|
||||
email,
|
||||
|
@ -88,74 +89,72 @@ async function registertry(e: Event){
|
|||
const redir = new URLSearchParams(window.location.search).get("goback");
|
||||
window.location.href = redir ? redir : "/channels/@me";
|
||||
}
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error("Registration failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleErrors(errors: any, elements: HTMLFormControlsCollection){
|
||||
if(errors.consent){
|
||||
function handleErrors(errors: any, elements: HTMLFormControlsCollection) {
|
||||
if (errors.consent) {
|
||||
showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
|
||||
}else if(errors.password){
|
||||
} else if (errors.password) {
|
||||
showError(
|
||||
elements[3] as HTMLElement,
|
||||
I18n.getTranslation("register.passwordError",errors.password._errors[0].message)
|
||||
elements[3] as HTMLElement,
|
||||
I18n.getTranslation("register.passwordError", errors.password._errors[0].message),
|
||||
);
|
||||
}else if(errors.username){
|
||||
} else if (errors.username) {
|
||||
showError(
|
||||
elements[2] as HTMLElement,
|
||||
I18n.getTranslation("register.usernameError",errors.username._errors[0].message)
|
||||
elements[2] as HTMLElement,
|
||||
I18n.getTranslation("register.usernameError", errors.username._errors[0].message),
|
||||
);
|
||||
}else if(errors.email){
|
||||
} else if (errors.email) {
|
||||
showError(
|
||||
elements[1] as HTMLElement,
|
||||
I18n.getTranslation("register.emailError",errors.email._errors[0].message)
|
||||
elements[1] as HTMLElement,
|
||||
I18n.getTranslation("register.emailError", errors.email._errors[0].message),
|
||||
);
|
||||
}else if(errors.date_of_birth){
|
||||
} else if (errors.date_of_birth) {
|
||||
showError(
|
||||
elements[5] as HTMLElement,
|
||||
I18n.getTranslation("register.DOBError",errors.date_of_birth._errors[0].message)
|
||||
elements[5] as HTMLElement,
|
||||
I18n.getTranslation("register.DOBError", errors.date_of_birth._errors[0].message),
|
||||
);
|
||||
}else{
|
||||
} else {
|
||||
(document.getElementById("wrong") as HTMLElement).textContent =
|
||||
errors[Object.keys(errors)[0]]._errors[0].message;
|
||||
errors[Object.keys(errors)[0]]._errors[0].message;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(element: HTMLElement, message: string){
|
||||
function showError(element: HTMLElement, message: string) {
|
||||
const parent = element.parentElement!;
|
||||
let errorElement = parent.getElementsByClassName(
|
||||
"suberror"
|
||||
)[0] as HTMLElement;
|
||||
if(!errorElement){
|
||||
let errorElement = parent.getElementsByClassName("suberror")[0] as HTMLElement;
|
||||
if (!errorElement) {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("suberror", "suberrora");
|
||||
parent.append(div);
|
||||
errorElement = div;
|
||||
}else{
|
||||
} else {
|
||||
errorElement.classList.remove("suberror");
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => {
|
||||
errorElement.classList.add("suberror");
|
||||
}, 100);
|
||||
}
|
||||
errorElement.textContent = message;
|
||||
}
|
||||
|
||||
async function tosLogic(){
|
||||
async function tosLogic() {
|
||||
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
|
||||
const apiurl = new URL(instanceInfo.api);
|
||||
const urlstr=apiurl.toString();
|
||||
const urlstr = apiurl.toString();
|
||||
const response = await fetch(urlstr + (urlstr.endsWith("/") ? "" : "/") + "ping");
|
||||
const data = await response.json();
|
||||
const tosPage = data.instance.tosPage;
|
||||
|
||||
if(tosPage){
|
||||
const box=document.getElementById("TOSbox");
|
||||
if(!box) return;
|
||||
box.innerHTML ="";
|
||||
box.append(new MarkDown(I18n.getTranslation("register.agreeTOS",tosPage)).makeHTML());
|
||||
}else{
|
||||
document.getElementById("TOSbox")!.textContent =I18n.getTranslation("register.noTOS");
|
||||
if (tosPage) {
|
||||
const box = document.getElementById("TOSbox");
|
||||
if (!box) return;
|
||||
box.innerHTML = "";
|
||||
box.append(new MarkDown(I18n.getTranslation("register.agreeTOS", tosPage)).makeHTML());
|
||||
} else {
|
||||
document.getElementById("TOSbox")!.textContent = I18n.getTranslation("register.noTOS");
|
||||
}
|
||||
console.log(tosPage);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import{ Permissions }from"./permissions.js";
|
||||
import{ Localuser }from"./localuser.js";
|
||||
import{ Guild }from"./guild.js";
|
||||
import{ SnowFlake }from"./snowflake.js";
|
||||
import{ rolesjson }from"./jsontypes.js";
|
||||
import{ Search }from"./search.js";
|
||||
class Role extends SnowFlake{
|
||||
import {Permissions} from "./permissions.js";
|
||||
import {Localuser} from "./localuser.js";
|
||||
import {Guild} from "./guild.js";
|
||||
import {SnowFlake} from "./snowflake.js";
|
||||
import {rolesjson} from "./jsontypes.js";
|
||||
import {Search} from "./search.js";
|
||||
class Role extends SnowFlake {
|
||||
permissions: Permissions;
|
||||
owner: Guild;
|
||||
color!: number;
|
||||
|
@ -14,14 +14,14 @@ class Role extends SnowFlake{
|
|||
icon!: string;
|
||||
mentionable!: boolean;
|
||||
unicode_emoji!: string;
|
||||
position!:number;
|
||||
position!: number;
|
||||
headers: Guild["headers"];
|
||||
constructor(json: rolesjson, owner: Guild){
|
||||
constructor(json: rolesjson, owner: Guild) {
|
||||
super(json.id);
|
||||
this.headers = owner.headers;
|
||||
this.info = owner.info;
|
||||
for(const thing of Object.keys(json)){
|
||||
if(thing === "id"){
|
||||
for (const thing of Object.keys(json)) {
|
||||
if (thing === "id") {
|
||||
continue;
|
||||
}
|
||||
(this as any)[thing] = (json as any)[thing];
|
||||
|
@ -29,39 +29,39 @@ class Role extends SnowFlake{
|
|||
this.permissions = new Permissions(json.permissions);
|
||||
this.owner = owner;
|
||||
}
|
||||
newJson(json: rolesjson){
|
||||
for(const thing of Object.keys(json)){
|
||||
if(thing === "id"||thing==="permissions"){
|
||||
newJson(json: rolesjson) {
|
||||
for (const thing of Object.keys(json)) {
|
||||
if (thing === "id" || thing === "permissions") {
|
||||
continue;
|
||||
}
|
||||
(this as any)[thing] = (json as any)[thing];
|
||||
}
|
||||
this.permissions.allow=BigInt(json.permissions);
|
||||
this.permissions.allow = BigInt(json.permissions);
|
||||
}
|
||||
get guild(): Guild{
|
||||
get guild(): Guild {
|
||||
return this.owner;
|
||||
}
|
||||
get localuser(): Localuser{
|
||||
get localuser(): Localuser {
|
||||
return this.guild.localuser;
|
||||
}
|
||||
getColor(): string | null{
|
||||
if(this.color === 0){
|
||||
getColor(): string | null {
|
||||
if (this.color === 0) {
|
||||
return null;
|
||||
}
|
||||
return`#${this.color.toString(16)}`;
|
||||
return `#${this.color.toString(16)}`;
|
||||
}
|
||||
canManage(){
|
||||
if(this.guild.member.hasPermission("MANAGE_ROLES")){
|
||||
let max=-Infinity;
|
||||
this.guild.member.roles.forEach(r=>max=Math.max(max,r.position))
|
||||
return this.position<=max||this.guild.properties.owner_id===this.guild.member.id;
|
||||
canManage() {
|
||||
if (this.guild.member.hasPermission("MANAGE_ROLES")) {
|
||||
let max = -Infinity;
|
||||
this.guild.member.roles.forEach((r) => (max = Math.max(max, r.position)));
|
||||
return this.position <= max || this.guild.properties.owner_id === this.guild.member.id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export{ Role };
|
||||
import{ Options }from"./settings.js";
|
||||
class PermissionToggle implements OptionsElement<number>{
|
||||
export {Role};
|
||||
import {Options} from "./settings.js";
|
||||
class PermissionToggle implements OptionsElement<number> {
|
||||
readonly rolejson: {
|
||||
name: string;
|
||||
readableName: string;
|
||||
|
@ -70,17 +70,13 @@ 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;
|
||||
}
|
||||
watchForChange(){}
|
||||
generateHTML(): HTMLElement{
|
||||
watchForChange() {}
|
||||
generateHTML(): HTMLElement {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("setting");
|
||||
const name = document.createElement("span");
|
||||
|
@ -94,7 +90,7 @@ class PermissionToggle implements OptionsElement<number>{
|
|||
div.appendChild(p);
|
||||
return div;
|
||||
}
|
||||
generateCheckbox(): HTMLElement{
|
||||
generateCheckbox(): HTMLElement {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("tritoggle");
|
||||
const state = this.permissions.getPermission(this.rolejson.name);
|
||||
|
@ -103,10 +99,10 @@ class PermissionToggle implements OptionsElement<number>{
|
|||
on.type = "radio";
|
||||
on.name = this.rolejson.name;
|
||||
div.append(on);
|
||||
if(state === 1){
|
||||
if (state === 1) {
|
||||
on.checked = true;
|
||||
}
|
||||
on.onclick = _=>{
|
||||
on.onclick = (_) => {
|
||||
this.permissions.setPermission(this.rolejson.name, 1);
|
||||
this.owner.changed();
|
||||
};
|
||||
|
@ -115,283 +111,293 @@ class PermissionToggle implements OptionsElement<number>{
|
|||
no.type = "radio";
|
||||
no.name = this.rolejson.name;
|
||||
div.append(no);
|
||||
if(state === 0){
|
||||
if (state === 0) {
|
||||
no.checked = true;
|
||||
}
|
||||
no.onclick = _=>{
|
||||
no.onclick = (_) => {
|
||||
this.permissions.setPermission(this.rolejson.name, 0);
|
||||
this.owner.changed();
|
||||
};
|
||||
if(this.permissions.hasDeny){
|
||||
if (this.permissions.hasDeny) {
|
||||
const off = document.createElement("input");
|
||||
off.type = "radio";
|
||||
off.name = this.rolejson.name;
|
||||
div.append(off);
|
||||
if(state === -1){
|
||||
if (state === -1) {
|
||||
off.checked = true;
|
||||
}
|
||||
off.onclick = _=>{
|
||||
off.onclick = (_) => {
|
||||
this.permissions.setPermission(this.rolejson.name, -1);
|
||||
this.owner.changed();
|
||||
};
|
||||
}
|
||||
return div;
|
||||
}
|
||||
submit(){}
|
||||
submit() {}
|
||||
}
|
||||
import{ OptionsElement, Buttons }from"./settings.js";
|
||||
import { Contextmenu } from "./contextmenu.js";
|
||||
import { Channel } from "./channel.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
class RoleList extends Buttons{
|
||||
import {OptionsElement, Buttons} from "./settings.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
import {Channel} from "./channel.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
class RoleList extends Buttons {
|
||||
permissions: [Role, Permissions][];
|
||||
permission: Permissions;
|
||||
readonly guild: Guild;
|
||||
readonly channel: false|Channel;
|
||||
readonly channel: false | Channel;
|
||||
declare buttons: [string, string][];
|
||||
readonly options: Options;
|
||||
onchange: Function;
|
||||
curid?: string;
|
||||
get info(){
|
||||
get info() {
|
||||
return this.guild.info;
|
||||
}
|
||||
get headers(){
|
||||
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;
|
||||
this.channel = channel;
|
||||
this.onchange = onchange;
|
||||
const options = new Options("", this);
|
||||
if(channel){
|
||||
if (channel) {
|
||||
this.permission = new Permissions("0", "0");
|
||||
}else{
|
||||
} else {
|
||||
this.permission = new Permissions("0");
|
||||
}
|
||||
this.makeguildmenus(options);
|
||||
for(const thing of Permissions.info()){
|
||||
options.options.push(
|
||||
new PermissionToggle(thing, this.permission, options)
|
||||
);
|
||||
for (const thing of Permissions.info()) {
|
||||
options.options.push(new PermissionToggle(thing, this.permission, options));
|
||||
}
|
||||
for(const i of permissions){
|
||||
for (const i of permissions) {
|
||||
this.buttons.push([i[0].name, i[0].id]);
|
||||
}
|
||||
this.options = options;
|
||||
guild.roleUpdate=this.groleUpdate.bind(this);
|
||||
if(channel){
|
||||
channel.croleUpdate=this.croleUpdate.bind(this);
|
||||
guild.roleUpdate = this.groleUpdate.bind(this);
|
||||
if (channel) {
|
||||
channel.croleUpdate = this.croleUpdate.bind(this);
|
||||
}
|
||||
}
|
||||
private groleUpdate(role:Role,added:1|0|-1){
|
||||
if(!this.channel){
|
||||
if(added===1){
|
||||
this.permissions.push([role,role.permissions]);
|
||||
private groleUpdate(role: Role, added: 1 | 0 | -1) {
|
||||
if (!this.channel) {
|
||||
if (added === 1) {
|
||||
this.permissions.push([role, role.permissions]);
|
||||
}
|
||||
}
|
||||
if(added===-1){
|
||||
this.permissions=this.permissions.filter(r=>r[0]!==role);
|
||||
if (added === -1) {
|
||||
this.permissions = this.permissions.filter((r) => r[0] !== role);
|
||||
}
|
||||
this.redoButtons();
|
||||
}
|
||||
private croleUpdate(role:Role,perm:Permissions,added:boolean){
|
||||
if(added){
|
||||
this.permissions.push([role,perm])
|
||||
}else{
|
||||
this.permissions=this.permissions.filter(r=>r[0]!==role);
|
||||
private croleUpdate(role: Role, perm: Permissions, added: boolean) {
|
||||
if (added) {
|
||||
this.permissions.push([role, perm]);
|
||||
} else {
|
||||
this.permissions = this.permissions.filter((r) => r[0] !== role);
|
||||
}
|
||||
this.redoButtons();
|
||||
}
|
||||
makeguildmenus(option:Options){
|
||||
option.addButtonInput("",I18n.getTranslation("role.displaySettings"),()=>{
|
||||
const role=this.guild.roleids.get(this.curid as string);
|
||||
if(!role) return;
|
||||
const form=option.addSubForm(I18n.getTranslation("role.displaySettings"),()=>{},{
|
||||
fetchURL:this.info.api+"/guilds/"+this.guild.id+"/roles/"+this.curid,
|
||||
method:"PATCH",
|
||||
headers:this.headers,
|
||||
traditionalSubmit:true
|
||||
makeguildmenus(option: Options) {
|
||||
option.addButtonInput("", I18n.getTranslation("role.displaySettings"), () => {
|
||||
const role = this.guild.roleids.get(this.curid as string);
|
||||
if (!role) return;
|
||||
const form = option.addSubForm(I18n.getTranslation("role.displaySettings"), () => {}, {
|
||||
fetchURL: this.info.api + "/guilds/" + this.guild.id + "/roles/" + this.curid,
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
traditionalSubmit: true,
|
||||
});
|
||||
form.addTextInput(I18n.getTranslation("role.name"),"name",{
|
||||
initText:role.name
|
||||
form.addTextInput(I18n.getTranslation("role.name"), "name", {
|
||||
initText: role.name,
|
||||
});
|
||||
form.addCheckboxInput(I18n.getTranslation("role.hoisted"),"hoist",{
|
||||
initState:role.hoist
|
||||
form.addCheckboxInput(I18n.getTranslation("role.hoisted"), "hoist", {
|
||||
initState: role.hoist,
|
||||
});
|
||||
form.addCheckboxInput(I18n.getTranslation("role.mentionable"),"mentionable",{
|
||||
initState:role.mentionable
|
||||
form.addCheckboxInput(I18n.getTranslation("role.mentionable"), "mentionable", {
|
||||
initState: role.mentionable,
|
||||
});
|
||||
const color="#"+role.color.toString(16).padStart(6,"0");
|
||||
form.addColorInput(I18n.getTranslation("role.color"),"color",{
|
||||
initColor:color
|
||||
const color = "#" + role.color.toString(16).padStart(6, "0");
|
||||
form.addColorInput(I18n.getTranslation("role.color"), "color", {
|
||||
initColor: color,
|
||||
});
|
||||
form.addPreprocessor((obj:any)=>{
|
||||
obj.color=Number("0x"+obj.color.substring(1));
|
||||
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){
|
||||
if(!this.channel) return;
|
||||
console.log(role);
|
||||
fetch(this.info.api+"/channels/"+this.channel.id+"/permissions/"+role.id,{
|
||||
method:"DELETE",
|
||||
headers:this.headers
|
||||
})
|
||||
},null);
|
||||
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) {
|
||||
if (!this.channel) return;
|
||||
console.log(role);
|
||||
fetch(this.info.api + "/channels/" + this.channel.id + "/permissions/" + role.id, {
|
||||
method: "DELETE",
|
||||
headers: this.headers,
|
||||
});
|
||||
},
|
||||
null,
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
private static GuildRoleMenu(){
|
||||
const menu=new Contextmenu<RoleList,Role>("role settings");
|
||||
menu.addbutton(()=>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);
|
||||
private static GuildRoleMenu() {
|
||||
const menu = new Contextmenu<RoleList, Role>("role settings");
|
||||
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,
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
redoButtons(){
|
||||
this.buttons=[];
|
||||
this.permissions.sort(([a],[b])=>b.position-a.position);
|
||||
for(const i of this.permissions){
|
||||
redoButtons() {
|
||||
this.buttons = [];
|
||||
this.permissions.sort(([a], [b]) => b.position - a.position);
|
||||
for (const i of this.permissions) {
|
||||
this.buttons.push([i[0].name, i[0].id]);
|
||||
}
|
||||
console.log("in here :P")
|
||||
if(!this.buttonList)return;
|
||||
console.log("in here :P");
|
||||
const elms=Array.from(this.buttonList.children);
|
||||
const div=elms[0] as HTMLDivElement;
|
||||
const div2=elms[1] as HTMLDivElement;
|
||||
if (!this.buttonList) return;
|
||||
console.log("in here :P");
|
||||
const elms = Array.from(this.buttonList.children);
|
||||
const div = elms[0] as HTMLDivElement;
|
||||
const div2 = elms[1] as HTMLDivElement;
|
||||
console.log(div);
|
||||
div.innerHTML="";
|
||||
div.append(this.buttonListGen(div2));//not actually sure why the html is needed
|
||||
div.innerHTML = "";
|
||||
div.append(this.buttonListGen(div2)); //not actually sure why the html is needed
|
||||
}
|
||||
buttonMap=new WeakMap<HTMLButtonElement,Role>();
|
||||
dragged?:HTMLButtonElement;
|
||||
buttonDragEvents(button:HTMLButtonElement,role:Role){
|
||||
this.buttonMap.set(button,role);
|
||||
button.addEventListener("dragstart", e=>{
|
||||
buttonMap = new WeakMap<HTMLButtonElement, Role>();
|
||||
dragged?: HTMLButtonElement;
|
||||
buttonDragEvents(button: HTMLButtonElement, role: Role) {
|
||||
this.buttonMap.set(button, role);
|
||||
button.addEventListener("dragstart", (e) => {
|
||||
this.dragged = button;
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
button.addEventListener("dragend", ()=>{
|
||||
button.addEventListener("dragend", () => {
|
||||
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", _=>{
|
||||
const role2=this.buttonMap.get(this.dragged as HTMLButtonElement);
|
||||
if(!role2) return;
|
||||
const index2=this.guild.roles.indexOf(role2);
|
||||
this.guild.roles.splice(index2,1);
|
||||
const index=this.guild.roles.indexOf(role);
|
||||
this.guild.roles.splice(index+1,0,role2);
|
||||
button.addEventListener("drop", (_) => {
|
||||
const role2 = this.buttonMap.get(this.dragged as HTMLButtonElement);
|
||||
if (!role2) return;
|
||||
const index2 = this.guild.roles.indexOf(role2);
|
||||
this.guild.roles.splice(index2, 1);
|
||||
const index = this.guild.roles.indexOf(role);
|
||||
this.guild.roles.splice(index + 1, 0, role2);
|
||||
this.guild.recalcRoles();
|
||||
console.log(role);
|
||||
});
|
||||
}
|
||||
buttonListGen(html:HTMLElement){
|
||||
const buttonTable=document.createElement("div");
|
||||
buttonListGen(html: HTMLElement) {
|
||||
const buttonTable = document.createElement("div");
|
||||
buttonTable.classList.add("flexttb");
|
||||
|
||||
const roleRow=document.createElement("div");
|
||||
roleRow.classList.add("flexltr","rolesheader");
|
||||
const roleRow = document.createElement("div");
|
||||
roleRow.classList.add("flexltr", "rolesheader");
|
||||
roleRow.append("Roles");
|
||||
const add=document.createElement("span");
|
||||
add.classList.add("svg-plus","svgicon","addrole");
|
||||
add.onclick=async (e)=>{
|
||||
const box=add.getBoundingClientRect();
|
||||
const add = document.createElement("span");
|
||||
add.classList.add("svg-plus", "svgicon", "addrole");
|
||||
add.onclick = async (e) => {
|
||||
const box = add.getBoundingClientRect();
|
||||
e.stopPropagation();
|
||||
if(this.channel){
|
||||
const roles:[Role,string[]][]=[];
|
||||
for(const role of this.guild.roles){
|
||||
if(this.permissions.find(r=>r[0]==role)){
|
||||
if (this.channel) {
|
||||
const roles: [Role, string[]][] = [];
|
||||
for (const role of this.guild.roles) {
|
||||
if (this.permissions.find((r) => r[0] == role)) {
|
||||
continue;
|
||||
}
|
||||
roles.push([role,[role.name]]);
|
||||
roles.push([role, [role.name]]);
|
||||
}
|
||||
const search=new Search(roles);
|
||||
const search = new Search(roles);
|
||||
|
||||
const found=await search.find(box.left,box.top);
|
||||
const found = await search.find(box.left, box.top);
|
||||
|
||||
|
||||
if(!found) return;
|
||||
if (!found) return;
|
||||
console.log(found);
|
||||
this.onchange(found.id,new Permissions("0","0"));
|
||||
}else{
|
||||
const div=document.createElement("div");
|
||||
const bar=document.createElement("input");
|
||||
div.classList.add("fixedsearch","OptionList");
|
||||
bar.type="text";
|
||||
div.style.left=(box.left^0)+"px";
|
||||
div.style.top=(box.top^0)+"px";
|
||||
div.append(bar)
|
||||
this.onchange(found.id, new Permissions("0", "0"));
|
||||
} else {
|
||||
const div = document.createElement("div");
|
||||
const bar = document.createElement("input");
|
||||
div.classList.add("fixedsearch", "OptionList");
|
||||
bar.type = "text";
|
||||
div.style.left = (box.left ^ 0) + "px";
|
||||
div.style.top = (box.top ^ 0) + "px";
|
||||
div.append(bar);
|
||||
document.body.append(div);
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
if (Contextmenu.currentmenu != "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
Contextmenu.currentmenu=div;
|
||||
Contextmenu.currentmenu = div;
|
||||
Contextmenu.keepOnScreen(div);
|
||||
bar.onchange=()=>{
|
||||
bar.onchange = () => {
|
||||
div.remove();
|
||||
console.log(bar.value)
|
||||
if(bar.value==="") return;
|
||||
fetch(this.info.api+`/guilds/${this.guild.id}/roles`,{
|
||||
method:"POST",
|
||||
headers:this.headers,
|
||||
body:JSON.stringify({
|
||||
color:0,
|
||||
name:bar.value,
|
||||
permissions:""
|
||||
})
|
||||
})
|
||||
}
|
||||
console.log(bar.value);
|
||||
if (bar.value === "") return;
|
||||
fetch(this.info.api + `/guilds/${this.guild.id}/roles`, {
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify({
|
||||
color: 0,
|
||||
name: bar.value,
|
||||
permissions: "",
|
||||
}),
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
roleRow.append(add);
|
||||
|
||||
buttonTable.append(roleRow);
|
||||
for(const thing of this.buttons){
|
||||
for (const thing of this.buttons) {
|
||||
const button = document.createElement("button");
|
||||
button.classList.add("SettingsButton");
|
||||
button.textContent = thing[0];
|
||||
const role=this.guild.roleids.get(thing[1]);
|
||||
if(role){
|
||||
if(!this.channel){
|
||||
if(role.canManage()){
|
||||
this.buttonDragEvents(button,role);
|
||||
button.draggable=true;
|
||||
RoleList.guildrolemenu.bindContextmenu(button,this,role)
|
||||
const role = this.guild.roleids.get(thing[1]);
|
||||
if (role) {
|
||||
if (!this.channel) {
|
||||
if (role.canManage()) {
|
||||
this.buttonDragEvents(button, role);
|
||||
button.draggable = true;
|
||||
RoleList.guildrolemenu.bindContextmenu(button, this, role);
|
||||
}
|
||||
}else{
|
||||
if(role.canManage()){
|
||||
RoleList.channelrolemenu.bindContextmenu(button,this,role)
|
||||
} else {
|
||||
if (role.canManage()) {
|
||||
RoleList.channelrolemenu.bindContextmenu(button, this, role);
|
||||
}
|
||||
}
|
||||
}
|
||||
button.onclick = _=>{
|
||||
button.onclick = (_) => {
|
||||
this.generateHTMLArea(thing[1], html);
|
||||
if(this.warndiv){
|
||||
if (this.warndiv) {
|
||||
this.warndiv.remove();
|
||||
}
|
||||
};
|
||||
|
@ -400,29 +406,29 @@ class RoleList extends Buttons{
|
|||
return buttonTable;
|
||||
}
|
||||
|
||||
generateButtons(html:HTMLElement):HTMLDivElement{
|
||||
generateButtons(html: HTMLElement): HTMLDivElement {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("settingbuttons");
|
||||
div.append(this.buttonListGen(html));
|
||||
return div;
|
||||
}
|
||||
handleString(str: string): HTMLElement{
|
||||
handleString(str: string): HTMLElement {
|
||||
this.curid = str;
|
||||
const arr = this.permissions.find(_=>_[0].id === str);
|
||||
if(arr){
|
||||
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);
|
||||
if(role){
|
||||
const role = this.permissions.find((e) => e[0].id === str);
|
||||
if (role) {
|
||||
this.options.name = role[0].name;
|
||||
this.options.haschanged = false;
|
||||
}
|
||||
}
|
||||
return this.options.generateHTML();
|
||||
}
|
||||
save(){
|
||||
save() {
|
||||
this.onchange(this.curid, this.permission);
|
||||
}
|
||||
}
|
||||
export{ RoleList, PermissionToggle };
|
||||
export {RoleList, PermissionToggle};
|
||||
|
|
|
@ -1,72 +1,70 @@
|
|||
import { Contextmenu } from "./contextmenu.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
|
||||
class Search<E>{
|
||||
options:Map<string,E>;
|
||||
readonly keys:string[];
|
||||
constructor(options:[E,string[]][]){
|
||||
const map=options.flatMap(e=>{
|
||||
const val=e[1].map(f=>[f,e[0]]);
|
||||
return val as [string,E][];
|
||||
})
|
||||
this.options=new Map(map);
|
||||
this.keys=[...this.options.keys()];
|
||||
}
|
||||
generateList(str:string,max:number,res:(e:E)=>void){
|
||||
str=str.toLowerCase();
|
||||
const options=this.keys.filter(e=>{
|
||||
return e.toLowerCase().includes(str)
|
||||
});
|
||||
const div=document.createElement("div");
|
||||
div.classList.add("OptionList","flexttb");
|
||||
for(const option of options.slice(0, max)){
|
||||
const hoption=document.createElement("span");
|
||||
hoption.textContent=option;
|
||||
hoption.onclick=()=>{
|
||||
if(!this.options.has(option)) return;
|
||||
res(this.options.get(option) as E)
|
||||
}
|
||||
div.append(hoption);
|
||||
}
|
||||
return div;
|
||||
}
|
||||
async find(x:number,y:number,max=4):Promise<E|undefined>{
|
||||
return new Promise<E|undefined>((res)=>{
|
||||
class Search<E> {
|
||||
options: Map<string, E>;
|
||||
readonly keys: string[];
|
||||
constructor(options: [E, string[]][]) {
|
||||
const map = options.flatMap((e) => {
|
||||
const val = e[1].map((f) => [f, e[0]]);
|
||||
return val as [string, E][];
|
||||
});
|
||||
this.options = new Map(map);
|
||||
this.keys = [...this.options.keys()];
|
||||
}
|
||||
generateList(str: string, max: number, res: (e: E) => void) {
|
||||
str = str.toLowerCase();
|
||||
const options = this.keys.filter((e) => {
|
||||
return e.toLowerCase().includes(str);
|
||||
});
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("OptionList", "flexttb");
|
||||
for (const option of options.slice(0, max)) {
|
||||
const hoption = document.createElement("span");
|
||||
hoption.textContent = option;
|
||||
hoption.onclick = () => {
|
||||
if (!this.options.has(option)) return;
|
||||
res(this.options.get(option) as E);
|
||||
};
|
||||
div.append(hoption);
|
||||
}
|
||||
return div;
|
||||
}
|
||||
async find(x: number, y: number, max = 4): Promise<E | undefined> {
|
||||
return new Promise<E | undefined>((res) => {
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("fixedsearch");
|
||||
console.log((x ^ 0) + "", (y ^ 0) + "");
|
||||
container.style.left = (x ^ 0) + "px";
|
||||
container.style.top = (y ^ 0) + "px";
|
||||
const remove = container.remove;
|
||||
container.remove = () => {
|
||||
remove.call(container);
|
||||
res(undefined);
|
||||
};
|
||||
|
||||
const container=document.createElement("div");
|
||||
container.classList.add("fixedsearch");
|
||||
console.log((x^0)+"",(y^0)+"");
|
||||
container.style.left=(x^0)+"px";
|
||||
container.style.top=(y^0)+"px";
|
||||
const remove=container.remove;
|
||||
container.remove=()=>{
|
||||
remove.call(container);
|
||||
res(undefined);
|
||||
}
|
||||
|
||||
function resolve(e:E){
|
||||
res(e);
|
||||
container.remove();
|
||||
}
|
||||
const bar=document.createElement("input");
|
||||
const options=document.createElement("div");
|
||||
const keydown=()=>{
|
||||
const html=this.generateList(bar.value,max,resolve);
|
||||
options.innerHTML="";
|
||||
options.append(html);
|
||||
}
|
||||
bar.oninput=keydown;
|
||||
keydown();
|
||||
bar.type="text";
|
||||
container.append(bar);
|
||||
container.append(options);
|
||||
document.body.append(container);
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
Contextmenu.currentmenu=container;
|
||||
Contextmenu.keepOnScreen(container);
|
||||
|
||||
})
|
||||
}
|
||||
function resolve(e: E) {
|
||||
res(e);
|
||||
container.remove();
|
||||
}
|
||||
const bar = document.createElement("input");
|
||||
const options = document.createElement("div");
|
||||
const keydown = () => {
|
||||
const html = this.generateList(bar.value, max, resolve);
|
||||
options.innerHTML = "";
|
||||
options.append(html);
|
||||
};
|
||||
bar.oninput = keydown;
|
||||
keydown();
|
||||
bar.type = "text";
|
||||
container.append(bar);
|
||||
container.append(options);
|
||||
document.body.append(container);
|
||||
if (Contextmenu.currentmenu != "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
Contextmenu.currentmenu = container;
|
||||
Contextmenu.keepOnScreen(container);
|
||||
});
|
||||
}
|
||||
}
|
||||
export {Search};
|
||||
|
|
|
@ -1,42 +1,45 @@
|
|||
function deleteoldcache(){
|
||||
function deleteoldcache() {
|
||||
caches.delete("cache");
|
||||
console.log("this ran :P");
|
||||
}
|
||||
|
||||
async function putInCache(request: URL | RequestInfo, response: Response){
|
||||
async function putInCache(request: URL | RequestInfo, response: Response) {
|
||||
console.log(request, response);
|
||||
const cache = await caches.open("cache");
|
||||
console.log("Grabbed");
|
||||
try{
|
||||
try {
|
||||
console.log(await cache.put(request, response));
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
let lastcache: string;
|
||||
self.addEventListener("activate", async ()=>{
|
||||
self.addEventListener("activate", async () => {
|
||||
console.log("Service Worker activated");
|
||||
checkCache();
|
||||
});
|
||||
|
||||
async function checkCache(){
|
||||
if(checkedrecently){
|
||||
async function checkCache() {
|
||||
if (checkedrecently) {
|
||||
return;
|
||||
}
|
||||
const promise = await caches.match("/getupdates");
|
||||
if(promise){
|
||||
if (promise) {
|
||||
lastcache = await promise.text();
|
||||
}
|
||||
console.log(lastcache);
|
||||
fetch("/getupdates").then(async data=>{
|
||||
setTimeout((_: any)=>{
|
||||
checkedrecently = false;
|
||||
}, 1000 * 60 * 30);
|
||||
if(!data.ok) return;
|
||||
fetch("/getupdates").then(async (data) => {
|
||||
setTimeout(
|
||||
(_: any) => {
|
||||
checkedrecently = false;
|
||||
},
|
||||
1000 * 60 * 30,
|
||||
);
|
||||
if (!data.ok) return;
|
||||
const text = await data.clone().text();
|
||||
console.log(text, lastcache);
|
||||
if(lastcache !== text){
|
||||
if (lastcache !== text) {
|
||||
deleteoldcache();
|
||||
putInCache("/getupdates", data);
|
||||
}
|
||||
|
@ -45,98 +48,100 @@ async function checkCache(){
|
|||
}
|
||||
var checkedrecently = false;
|
||||
|
||||
function samedomain(url: string | URL){
|
||||
function samedomain(url: string | URL) {
|
||||
return new URL(url).origin === self.origin;
|
||||
}
|
||||
|
||||
const htmlFiles=new Set(["/index","/login","/home","/register","/oauth2/auth"]);
|
||||
const htmlFiles = new Set(["/index", "/login", "/home", "/register", "/oauth2/auth"]);
|
||||
|
||||
|
||||
function isHtml(url:string):string|void{
|
||||
const path=new URL(url).pathname;
|
||||
if(htmlFiles.has(path)||htmlFiles.has(path+".html")){
|
||||
return path+path.endsWith(".html")?"":".html";
|
||||
function isHtml(url: string): string | void {
|
||||
const path = new URL(url).pathname;
|
||||
if (htmlFiles.has(path) || htmlFiles.has(path + ".html")) {
|
||||
return path + path.endsWith(".html") ? "" : ".html";
|
||||
}
|
||||
}
|
||||
let enabled="false";
|
||||
let offline=false;
|
||||
let enabled = "false";
|
||||
let offline = false;
|
||||
|
||||
function toPath(url:string):string{
|
||||
const Url= new URL(url);
|
||||
let html=isHtml(url);
|
||||
if(!html){
|
||||
const path=Url.pathname;
|
||||
if(path.startsWith("/channels")){
|
||||
html="./index.html"
|
||||
}else if(path.startsWith("/invite/")||path==="/invite"){
|
||||
html="./invite.html"
|
||||
function toPath(url: string): string {
|
||||
const Url = new URL(url);
|
||||
let html = isHtml(url);
|
||||
if (!html) {
|
||||
const path = Url.pathname;
|
||||
if (path.startsWith("/channels")) {
|
||||
html = "./index.html";
|
||||
} else if (path.startsWith("/invite/") || path === "/invite") {
|
||||
html = "./invite.html";
|
||||
}
|
||||
}
|
||||
return html||Url.pathname;
|
||||
return html || Url.pathname;
|
||||
}
|
||||
let fails=0;
|
||||
async function getfile(event: FetchEvent):Promise<Response>{
|
||||
let fails = 0;
|
||||
async function getfile(event: FetchEvent): Promise<Response> {
|
||||
checkCache();
|
||||
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){
|
||||
putInCache(toPath(event.request.url),responce.clone());
|
||||
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) {
|
||||
putInCache(toPath(event.request.url), responce.clone());
|
||||
}
|
||||
if(!responce.ok){
|
||||
if (!responce.ok) {
|
||||
fails++;
|
||||
if(fails>5){
|
||||
offline=true;
|
||||
if (fails > 5) {
|
||||
offline = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return responce;
|
||||
}
|
||||
|
||||
let path=toPath(event.request.url);
|
||||
if(path === "/instances.json"){
|
||||
let path = toPath(event.request.url);
|
||||
if (path === "/instances.json") {
|
||||
return await fetch(path);
|
||||
}
|
||||
console.log("Getting path: "+path);
|
||||
console.log("Getting path: " + path);
|
||||
const responseFromCache = await caches.match(path);
|
||||
if(responseFromCache){
|
||||
if (responseFromCache) {
|
||||
console.log("cache hit");
|
||||
return responseFromCache;
|
||||
}
|
||||
try{
|
||||
try {
|
||||
const responseFromNetwork = await fetch(path);
|
||||
if(responseFromNetwork.ok){
|
||||
if (responseFromNetwork.ok) {
|
||||
await putInCache(path, responseFromNetwork.clone());
|
||||
}
|
||||
return responseFromNetwork;
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return new Response(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.addEventListener("fetch", (e)=>{
|
||||
const event=e as FetchEvent;
|
||||
try{
|
||||
self.addEventListener("fetch", (e) => {
|
||||
const event = e as FetchEvent;
|
||||
try {
|
||||
event.respondWith(getfile(event));
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("message", (message)=>{
|
||||
const data=message.data;
|
||||
switch(data.code){
|
||||
self.addEventListener("message", (message) => {
|
||||
const data = message.data;
|
||||
switch (data.code) {
|
||||
case "setMode":
|
||||
enabled=data.data;
|
||||
enabled = data.data;
|
||||
break;
|
||||
case "CheckUpdate":
|
||||
checkedrecently=false;
|
||||
checkedrecently = false;
|
||||
checkCache();
|
||||
break;
|
||||
case "ForceClear":
|
||||
deleteoldcache();
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,17 @@
|
|||
abstract class SnowFlake{
|
||||
abstract class SnowFlake {
|
||||
public readonly id: string;
|
||||
constructor(id: string){
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
getUnixTime(): number{
|
||||
getUnixTime(): number {
|
||||
return SnowFlake.stringToUnixTime(this.id);
|
||||
}
|
||||
static stringToUnixTime(str: string){
|
||||
try{
|
||||
static stringToUnixTime(str: string) {
|
||||
try {
|
||||
return Number((BigInt(str) >> 22n) + 1420070400000n);
|
||||
}catch{
|
||||
} catch {
|
||||
throw new Error(`The ID is corrupted, it's ${str} when it should be some number.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
export{ SnowFlake };
|
||||
export {SnowFlake};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -73,7 +73,7 @@
|
|||
|
||||
--secondary-bg: #9397bd;
|
||||
--secondary-hover: #9ea5cc;
|
||||
|
||||
|
||||
--servers-bg: #7a7aaa;
|
||||
--channels-bg: #babdd2;
|
||||
--channel-selected: #9c9fbf;
|
||||
|
@ -93,7 +93,7 @@
|
|||
|
||||
.Dark-Accent-theme {
|
||||
color-scheme: dark;
|
||||
|
||||
|
||||
--primary-bg: color-mix(in srgb, #3f3f3f 65%, var(--accent-color));
|
||||
--primary-hover: color-mix(in srgb, #373737 68%, var(--accent-color));
|
||||
--primary-text: #ebebeb;
|
||||
|
@ -112,7 +112,7 @@
|
|||
|
||||
--spoiler: color-mix(in srgb, #101010 72%, var(--accent-color));
|
||||
--link: color-mix(in srgb, #99ccff 75%, var(--accent-color));
|
||||
|
||||
|
||||
--black: color-mix(in srgb, #000000 90%, var(--accent-color));
|
||||
--icon: color-mix(in srgb, #ffffff, var(--accent-color));
|
||||
--dock-bg: color-mix(in srgb, #171717 68%, var(--accent-color));
|
||||
|
@ -173,4 +173,4 @@ body {
|
|||
--settings-panel-hover: color-mix(in srgb, var(--settings-panel-selected), transparent);
|
||||
--loading-bg: var(--secondary-bg);
|
||||
--loading-text: var(--secondary-text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import{ Member }from"./member.js";
|
||||
import{ MarkDown }from"./markdown.js";
|
||||
import{ Contextmenu }from"./contextmenu.js";
|
||||
import{ Localuser }from"./localuser.js";
|
||||
import{ Guild }from"./guild.js";
|
||||
import{ SnowFlake }from"./snowflake.js";
|
||||
import{ presencejson, userjson }from"./jsontypes.js";
|
||||
import { Role } from "./role.js";
|
||||
import { Search } from "./search.js";
|
||||
import { I18n } from "./i18n.js";
|
||||
import { Direct } from "./direct.js";
|
||||
import {Member} from "./member.js";
|
||||
import {MarkDown} from "./markdown.js";
|
||||
import {Contextmenu} from "./contextmenu.js";
|
||||
import {Localuser} from "./localuser.js";
|
||||
import {Guild} from "./guild.js";
|
||||
import {SnowFlake} from "./snowflake.js";
|
||||
import {presencejson, userjson} from "./jsontypes.js";
|
||||
import {Role} from "./role.js";
|
||||
import {Search} from "./search.js";
|
||||
import {I18n} from "./i18n.js";
|
||||
import {Direct} from "./direct.js";
|
||||
|
||||
class User extends SnowFlake{
|
||||
class User extends SnowFlake {
|
||||
owner: Localuser;
|
||||
hypotheticalpfp!: boolean;
|
||||
avatar!: string | null;
|
||||
|
@ -29,33 +29,32 @@ 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;
|
||||
|
||||
constructor(userjson: userjson, owner: Localuser, dontclone = false){
|
||||
constructor(userjson: userjson, owner: Localuser, dontclone = false) {
|
||||
super(userjson.id);
|
||||
this.owner = owner;
|
||||
if(localStorage.getItem("logbad")&&owner.user&&owner.user.id!==userjson.id){
|
||||
this.checkfortmi(userjson)
|
||||
if (localStorage.getItem("logbad") && owner.user && owner.user.id !== userjson.id) {
|
||||
this.checkfortmi(userjson);
|
||||
}
|
||||
if(!owner){
|
||||
if (!owner) {
|
||||
console.error("missing localuser");
|
||||
}
|
||||
if(dontclone){
|
||||
for(const key of Object.keys(userjson)){
|
||||
if(key === "bio"){
|
||||
if (dontclone) {
|
||||
for (const key of Object.keys(userjson)) {
|
||||
if (key === "bio") {
|
||||
this.bio = new MarkDown(userjson[key], this.localuser);
|
||||
continue;
|
||||
}
|
||||
if(key === "id"){
|
||||
if (key === "id") {
|
||||
continue;
|
||||
}
|
||||
(this as any)[key] = (userjson as any)[key];
|
||||
}
|
||||
this.hypotheticalpfp = false;
|
||||
}else{
|
||||
} else {
|
||||
return User.checkuser(userjson, owner);
|
||||
}
|
||||
}
|
||||
|
@ -64,84 +63,90 @@ 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")
|
||||
checkfortmi(json: any) {
|
||||
if (json.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"]);
|
||||
for(const thing of bad){
|
||||
if(json.hasOwnProperty(thing)){
|
||||
console.error(thing+" should not be exposed to the client");
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
tojson():userjson{
|
||||
tojson(): userjson {
|
||||
return {
|
||||
username: this.username,
|
||||
id: this.id,
|
||||
public_flags: this.public_flags,
|
||||
discriminator: this.discriminator,
|
||||
avatar: this.avatar,
|
||||
accent_color: this.accent_color,
|
||||
banner: this.banner,
|
||||
bio: this.bio.rawString,
|
||||
premium_since: this.premium_since,
|
||||
premium_type: this.premium_type,
|
||||
bot: this.bot,
|
||||
theme_colors: this.theme_colors,
|
||||
pronouns: this.pronouns,
|
||||
badge_ids: this.badge_ids,
|
||||
}
|
||||
username: this.username,
|
||||
id: this.id,
|
||||
public_flags: this.public_flags,
|
||||
discriminator: this.discriminator,
|
||||
avatar: this.avatar,
|
||||
accent_color: this.accent_color,
|
||||
banner: this.banner,
|
||||
bio: this.bio.rawString,
|
||||
premium_since: this.premium_since,
|
||||
premium_type: this.premium_type,
|
||||
bot: this.bot,
|
||||
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
|
||||
);
|
||||
clone(): User {
|
||||
const json = this.tojson();
|
||||
json.id += "#clone";
|
||||
return new User(json, this.owner);
|
||||
}
|
||||
|
||||
public getPresence(presence: presencejson | undefined): void{
|
||||
if(presence){
|
||||
public getPresence(presence: presencejson | undefined): void {
|
||||
if (presence) {
|
||||
this.setstatus(presence.status);
|
||||
}else{
|
||||
} else {
|
||||
this.setstatus("offline");
|
||||
}
|
||||
}
|
||||
get online(){
|
||||
return (this.status)&&(this.status!="offline");
|
||||
get online() {
|
||||
return this.status && this.status != "offline";
|
||||
}
|
||||
setstatus(status: string): void{
|
||||
setstatus(status: string): void {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
getStatus(): string{
|
||||
getStatus(): string {
|
||||
return this.status || "offline";
|
||||
}
|
||||
|
||||
static contextmenu = new Contextmenu<User, Member | undefined>("User Menu");
|
||||
async opendm(){
|
||||
for(const dm of (this.localuser.guildids.get("@me") as Direct).channels){
|
||||
if(dm.user.id===this.id){
|
||||
async opendm() {
|
||||
for (const dm of (this.localuser.guildids.get("@me") as Direct).channels) {
|
||||
if (dm.user.id === this.id) {
|
||||
this.localuser.goToChannel(dm.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await fetch(this.info.api + "/users/@me/channels", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ recipients: [this.id] }),
|
||||
body: JSON.stringify({recipients: [this.id]}),
|
||||
headers: this.localuser.headers,
|
||||
})
|
||||
.then(res=>res.json())
|
||||
.then(json=>{
|
||||
this.localuser.goToChannel(json.id);
|
||||
});
|
||||
.then((res) => res.json())
|
||||
.then((json) => {
|
||||
this.localuser.goToChannel(json.id);
|
||||
});
|
||||
return;
|
||||
}
|
||||
async changeRelationship(type:0|1|2|3|4|5){
|
||||
if(type!==0){
|
||||
async changeRelationship(type: 0 | 1 | 2 | 3 | 4 | 5) {
|
||||
if (type !== 0) {
|
||||
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: this.owner.headers,
|
||||
|
@ -149,197 +154,213 @@ class User extends SnowFlake{
|
|||
type,
|
||||
}),
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
|
||||
method: "DELETE",
|
||||
headers: this.owner.headers
|
||||
headers: this.owner.headers,
|
||||
});
|
||||
}
|
||||
this.relationshipType=type;
|
||||
this.relationshipType = type;
|
||||
}
|
||||
static setUpContextMenu(): void{
|
||||
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.opendm();
|
||||
});
|
||||
static setUpContextMenu(): void {
|
||||
this.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("user.block"),
|
||||
function(this: User){
|
||||
() => I18n.getTranslation("user.copyId"),
|
||||
function (this: User) {
|
||||
navigator.clipboard.writeText(this.id);
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("user.message"),
|
||||
function (this: User) {
|
||||
this.opendm();
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("user.block"),
|
||||
function (this: User) {
|
||||
this.block();
|
||||
},
|
||||
null,
|
||||
function(){
|
||||
function () {
|
||||
return this.relationshipType !== 2;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("user.unblock"),
|
||||
function(this: User){
|
||||
() => I18n.getTranslation("user.unblock"),
|
||||
function (this: User) {
|
||||
this.unblock();
|
||||
},
|
||||
null,
|
||||
function(){
|
||||
function () {
|
||||
return this.relationshipType === 2;
|
||||
}
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(()=>I18n.getTranslation("user.friendReq"), function(this: User){
|
||||
this.changeRelationship(1);
|
||||
},null,function(){
|
||||
return this.relationshipType===0||this.relationshipType===3;
|
||||
});
|
||||
this.contextmenu.addbutton(()=>I18n.getTranslation("friends.removeFriend"), function(this: User){
|
||||
this.changeRelationship(0);
|
||||
},null,function(){
|
||||
return this.relationshipType===1;
|
||||
});
|
||||
this.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("user.kick"),
|
||||
function(this: User, member: Member | undefined){
|
||||
() => I18n.getTranslation("user.friendReq"),
|
||||
function (this: User) {
|
||||
this.changeRelationship(1);
|
||||
},
|
||||
null,
|
||||
function () {
|
||||
return this.relationshipType === 0 || this.relationshipType === 3;
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("friends.removeFriend"),
|
||||
function (this: User) {
|
||||
this.changeRelationship(0);
|
||||
},
|
||||
null,
|
||||
function () {
|
||||
return this.relationshipType === 1;
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
() => I18n.getTranslation("user.kick"),
|
||||
function (this: User, member: Member | undefined) {
|
||||
member?.kick();
|
||||
},
|
||||
null,
|
||||
member=>{
|
||||
if(!member)return false;
|
||||
(member) => {
|
||||
if (!member) return false;
|
||||
const us = member.guild.member;
|
||||
if(member.id === us.id){
|
||||
if (member.id === us.id) {
|
||||
return false;
|
||||
}
|
||||
if(member.id === member.guild.properties.owner_id){
|
||||
if (member.id === member.guild.properties.owner_id) {
|
||||
return false;
|
||||
}
|
||||
return us.hasPermission("KICK_MEMBERS") || false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("user.editServerProfile"),
|
||||
function(this: User, member: Member | undefined){
|
||||
if(!member) return;
|
||||
() => I18n.getTranslation("user.editServerProfile"),
|
||||
function (this: User, member: Member | undefined) {
|
||||
if (!member) return;
|
||||
member.showEditProfile();
|
||||
},
|
||||
null,
|
||||
function(member){
|
||||
return member?.id===this.localuser.user.id;
|
||||
}
|
||||
function (member) {
|
||||
return member?.id === this.localuser.user.id;
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("user.ban"),
|
||||
function(this: User, member: Member | undefined){
|
||||
() => I18n.getTranslation("user.ban"),
|
||||
function (this: User, member: Member | undefined) {
|
||||
member?.ban();
|
||||
},
|
||||
null,
|
||||
member=>{
|
||||
if(!member)return false;
|
||||
(member) => {
|
||||
if (!member) return false;
|
||||
const us = member.guild.member;
|
||||
if(member.id === us.id){
|
||||
if (member.id === us.id) {
|
||||
return false;
|
||||
}
|
||||
if(member.id === member.guild.properties.owner_id){
|
||||
if (member.id === member.guild.properties.owner_id) {
|
||||
return false;
|
||||
}
|
||||
return us.hasPermission("BAN_MEMBERS") || false;
|
||||
}
|
||||
},
|
||||
);
|
||||
this.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("user.addRole"),
|
||||
async function(this: User, member: Member | undefined,e){
|
||||
if(member){
|
||||
() => I18n.getTranslation("user.addRole"),
|
||||
async function (this: User, member: Member | undefined, e) {
|
||||
if (member) {
|
||||
e.stopPropagation();
|
||||
const roles:[Role,string[]][]=[];
|
||||
for(const role of member.guild.roles){
|
||||
if(!role.canManage()||member.roles.indexOf(role)!==-1){
|
||||
const roles: [Role, string[]][] = [];
|
||||
for (const role of member.guild.roles) {
|
||||
if (!role.canManage() || member.roles.indexOf(role) !== -1) {
|
||||
continue;
|
||||
}
|
||||
roles.push([role,[role.name]]);
|
||||
roles.push([role, [role.name]]);
|
||||
}
|
||||
const search=new Search(roles);
|
||||
const result=await search.find(e.x,e.y);
|
||||
if(!result) return;
|
||||
const search = new Search(roles);
|
||||
const result = await search.find(e.x, e.y);
|
||||
if (!result) return;
|
||||
member.addRole(result);
|
||||
}
|
||||
},
|
||||
null,
|
||||
member=>{
|
||||
if(!member)return false;
|
||||
(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"),
|
||||
async function(this: User, member: Member | undefined,e){
|
||||
if(member){
|
||||
() => I18n.getTranslation("user.removeRole"),
|
||||
async function (this: User, member: Member | undefined, e) {
|
||||
if (member) {
|
||||
e.stopPropagation();
|
||||
const roles:[Role,string[]][]=[];
|
||||
for(const role of member.roles){
|
||||
if(!role.canManage()){
|
||||
const roles: [Role, string[]][] = [];
|
||||
for (const role of member.roles) {
|
||||
if (!role.canManage()) {
|
||||
continue;
|
||||
}
|
||||
roles.push([role,[role.name]]);
|
||||
roles.push([role, [role.name]]);
|
||||
}
|
||||
const search=new Search(roles);
|
||||
const result=await search.find(e.x,e.y);
|
||||
if(!result) return;
|
||||
const search = new Search(roles);
|
||||
const result = await search.find(e.x, e.y);
|
||||
if (!result) return;
|
||||
member.removeRole(result);
|
||||
}
|
||||
},
|
||||
null,
|
||||
member=>{
|
||||
if(!member)return false;
|
||||
(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;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static checkuser(user: User | userjson, owner: Localuser): User{
|
||||
if(owner.userMap.has(user.id)){
|
||||
static checkuser(user: User | userjson, owner: Localuser): User {
|
||||
if (owner.userMap.has(user.id)) {
|
||||
return owner.userMap.get(user.id) as User;
|
||||
}else{
|
||||
} else {
|
||||
const tempuser = new User(user as userjson, owner, true);
|
||||
owner.userMap.set(user.id, tempuser);
|
||||
return tempuser;
|
||||
}
|
||||
}
|
||||
|
||||
get info(){
|
||||
get info() {
|
||||
return this.owner.info;
|
||||
}
|
||||
|
||||
get localuser(){
|
||||
get localuser() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
get name(){
|
||||
get name() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
async resolvemember(guild: Guild): Promise<Member | undefined>{
|
||||
async resolvemember(guild: Guild): Promise<Member | undefined> {
|
||||
return await Member.resolveMember(this, guild);
|
||||
}
|
||||
|
||||
async getUserProfile(): Promise<any>{
|
||||
async getUserProfile(): Promise<any> {
|
||||
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>{
|
||||
if(this.localuser.badges.has(id)){
|
||||
async getBadge(id: string): Promise<any> {
|
||||
if (this.localuser.badges.has(id)) {
|
||||
return this.localuser.badges.get(id);
|
||||
}else{
|
||||
if(this.resolving){
|
||||
} else {
|
||||
if (this.resolving) {
|
||||
await this.resolving;
|
||||
return this.localuser.badges.get(id);
|
||||
}
|
||||
|
@ -348,126 +369,121 @@ class User extends SnowFlake{
|
|||
this.resolving = prom;
|
||||
const badges = prom.badges;
|
||||
this.resolving = false;
|
||||
for(const badge of badges){
|
||||
for (const badge of badges) {
|
||||
this.localuser.badges.set(badge.id, badge);
|
||||
}
|
||||
return this.localuser.badges.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
buildpfp(guild:Guild|void|Member|null): HTMLImageElement{
|
||||
buildpfp(guild: Guild | void | Member | null): HTMLImageElement {
|
||||
const pfp = document.createElement("img");
|
||||
pfp.loading = "lazy";
|
||||
pfp.src = this.getpfpsrc();
|
||||
pfp.classList.add("pfp");
|
||||
pfp.classList.add("userid:" + this.id);
|
||||
if(guild){
|
||||
(async()=>{
|
||||
if(guild instanceof Guild){
|
||||
const memb= await Member.resolveMember(this,guild)
|
||||
if(!memb) return;
|
||||
if (guild) {
|
||||
(async () => {
|
||||
if (guild instanceof Guild) {
|
||||
const memb = await Member.resolveMember(this, guild);
|
||||
if (!memb) return;
|
||||
pfp.src = memb.getpfpsrc();
|
||||
}else{
|
||||
} else {
|
||||
pfp.src = guild.getpfpsrc();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
}
|
||||
return pfp;
|
||||
}
|
||||
|
||||
async buildstatuspfp(guild:Guild|void|Member|null): Promise<HTMLDivElement>{
|
||||
async buildstatuspfp(guild: Guild | void | Member | null): Promise<HTMLDivElement> {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("pfpDiv")
|
||||
div.classList.add("pfpDiv");
|
||||
const pfp = this.buildpfp(guild);
|
||||
div.append(pfp);
|
||||
const status = document.createElement("div");
|
||||
status.classList.add("statusDiv");
|
||||
switch(await this.getStatus()){
|
||||
case"offline":
|
||||
status.classList.add("offlinestatus");
|
||||
break;
|
||||
case"online":
|
||||
default:
|
||||
status.classList.add("onlinestatus");
|
||||
break;
|
||||
switch (await this.getStatus()) {
|
||||
case "offline":
|
||||
status.classList.add("offlinestatus");
|
||||
break;
|
||||
case "online":
|
||||
default:
|
||||
status.classList.add("onlinestatus");
|
||||
break;
|
||||
}
|
||||
div.append(status);
|
||||
return div;
|
||||
}
|
||||
|
||||
userupdate(json: userjson): void{
|
||||
if(json.avatar !== this.avatar){
|
||||
userupdate(json: userjson): void {
|
||||
if (json.avatar !== this.avatar) {
|
||||
this.changepfp(json.avatar);
|
||||
}
|
||||
}
|
||||
|
||||
bind(html: HTMLElement, guild: Guild | null = null, error = true): void{
|
||||
if(guild && guild.id !== "@me"){
|
||||
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){
|
||||
if (member === undefined && error) {
|
||||
const errorSpan = document.createElement("span");
|
||||
errorSpan.textContent = "!";
|
||||
errorSpan.classList.add("membererror");
|
||||
html.after(errorSpan);
|
||||
return;
|
||||
}
|
||||
if(member){
|
||||
if (member) {
|
||||
member.bind(html);
|
||||
}else{
|
||||
} else {
|
||||
User.contextmenu.bindContextmenu(html, this, undefined);
|
||||
}
|
||||
})
|
||||
.catch(err=>{
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
User.contextmenu.bindContextmenu(html, this, undefined);
|
||||
}
|
||||
if(guild){
|
||||
if (guild) {
|
||||
this.profileclick(html, guild);
|
||||
}else{
|
||||
} else {
|
||||
this.profileclick(html);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
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());
|
||||
return new User(json.user, localuser);
|
||||
}
|
||||
|
||||
changepfp(update: string | null): void{
|
||||
changepfp(update: string | null): void {
|
||||
this.avatar = update;
|
||||
this.hypotheticalpfp = false;
|
||||
const src = this.getpfpsrc();
|
||||
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach(
|
||||
element=>{
|
||||
(element as HTMLImageElement).src = src;
|
||||
}
|
||||
);
|
||||
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((element) => {
|
||||
(element as HTMLImageElement).src = src;
|
||||
});
|
||||
}
|
||||
|
||||
async block(){
|
||||
async block() {
|
||||
await this.changeRelationship(2);
|
||||
const channel = this.localuser.channelfocus;
|
||||
if(channel){
|
||||
for(const message of channel.messages){
|
||||
if (channel) {
|
||||
for (const message of channel.messages) {
|
||||
message[1].generateMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async unblock(){
|
||||
async unblock() {
|
||||
await this.changeRelationship(0);
|
||||
const channel = this.localuser.channelfocus;
|
||||
if(channel){
|
||||
for(const message of channel.messages){
|
||||
if (channel) {
|
||||
for (const message of channel.messages) {
|
||||
message[1].generateMessage();
|
||||
}
|
||||
}
|
||||
|
@ -475,81 +491,79 @@ class User extends SnowFlake{
|
|||
/**
|
||||
* @param guild this is an optional thing that'll get the src of the member if it exists, otherwise ignores it, this is meant to be fast, not accurate
|
||||
*/
|
||||
getpfpsrc(guild:Guild|void): string{
|
||||
if(this.hypotheticalpfp && this.avatar){
|
||||
getpfpsrc(guild: Guild | void): string {
|
||||
if (this.hypotheticalpfp && this.avatar) {
|
||||
return this.avatar;
|
||||
}
|
||||
if(guild){
|
||||
const member=this.members.get(guild)
|
||||
if(member instanceof Member){
|
||||
if (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`;
|
||||
}else{
|
||||
if (this.avatar !== null) {
|
||||
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`;
|
||||
return `${this.info.cdn}/embed/avatars/${int}.png`;
|
||||
}
|
||||
}
|
||||
|
||||
async buildprofile(
|
||||
x: number,
|
||||
y: number,
|
||||
guild: Guild | null | Member = null
|
||||
): Promise<HTMLDivElement>{
|
||||
if(Contextmenu.currentmenu != ""){
|
||||
guild: Guild | null | Member = null,
|
||||
): Promise<HTMLDivElement> {
|
||||
if (Contextmenu.currentmenu != "") {
|
||||
Contextmenu.currentmenu.remove();
|
||||
}
|
||||
const membres=(async ()=>{
|
||||
if(!guild) return;
|
||||
let member:Member|undefined;
|
||||
if(guild instanceof Guild){
|
||||
member=await Member.resolveMember(this,guild)
|
||||
}else{
|
||||
member=guild;
|
||||
const membres = (async () => {
|
||||
if (!guild) return;
|
||||
let member: Member | undefined;
|
||||
if (guild instanceof Guild) {
|
||||
member = await Member.resolveMember(this, guild);
|
||||
} else {
|
||||
member = guild;
|
||||
}
|
||||
return member;
|
||||
})()
|
||||
})();
|
||||
const div = document.createElement("div");
|
||||
|
||||
if(this.accent_color){
|
||||
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{
|
||||
} else {
|
||||
div.style.setProperty("--accent_color", "transparent");
|
||||
}
|
||||
const banner=this.getBanner(guild);
|
||||
const banner = this.getBanner(guild);
|
||||
div.append(banner);
|
||||
membres.then(member=>{
|
||||
if(!member) return;
|
||||
if(member.accent_color&&member.accent_color!==0){
|
||||
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){
|
||||
if (x !== -1) {
|
||||
div.style.left = `${x}px`;
|
||||
div.style.top = `${y}px`;
|
||||
div.classList.add("profile", "flexttb");
|
||||
}else{
|
||||
} else {
|
||||
this.setstatus("online");
|
||||
div.classList.add("hypoprofile", "profile", "flexttb");
|
||||
}
|
||||
const badgediv = document.createElement("div");
|
||||
badgediv.classList.add("badges");
|
||||
(async ()=>{
|
||||
if(!this.badge_ids)return;
|
||||
for(const id of this.badge_ids){
|
||||
(async () => {
|
||||
if (!this.badge_ids) return;
|
||||
for (const id of this.badge_ids) {
|
||||
const badgejson = await this.getBadge(id);
|
||||
if(badgejson){
|
||||
if (badgejson) {
|
||||
const badge = document.createElement(badgejson.link ? "a" : "div");
|
||||
badge.classList.add("badge");
|
||||
const img = document.createElement("img");
|
||||
|
@ -558,7 +572,7 @@ class User extends SnowFlake{
|
|||
const span = document.createElement("span");
|
||||
span.textContent = badgejson.description;
|
||||
badge.append(span);
|
||||
if(badge instanceof HTMLAnchorElement){
|
||||
if (badge instanceof HTMLAnchorElement) {
|
||||
badge.href = badgejson.link;
|
||||
}
|
||||
badgediv.append(badge);
|
||||
|
@ -568,7 +582,7 @@ class User extends SnowFlake{
|
|||
const pfp = await this.buildstatuspfp(guild);
|
||||
div.appendChild(pfp);
|
||||
const userbody = document.createElement("div");
|
||||
userbody.classList.add("flexttb","infosection");
|
||||
userbody.classList.add("flexttb", "infosection");
|
||||
div.appendChild(userbody);
|
||||
const usernamehtml = document.createElement("h2");
|
||||
usernamehtml.textContent = this.username;
|
||||
|
@ -580,14 +594,14 @@ class User extends SnowFlake{
|
|||
userbody.appendChild(discrimatorhtml);
|
||||
|
||||
const pronounshtml = document.createElement("p");
|
||||
pronounshtml.textContent = this.pronouns||"";
|
||||
pronounshtml.textContent = this.pronouns || "";
|
||||
pronounshtml.classList.add("pronouns");
|
||||
userbody.appendChild(pronounshtml);
|
||||
|
||||
membres.then(member=>{
|
||||
if(!member) return;
|
||||
if(member.pronouns&&member.pronouns!==""){
|
||||
pronounshtml.textContent=member.pronouns;
|
||||
membres.then((member) => {
|
||||
if (!member) return;
|
||||
if (member.pronouns && member.pronouns !== "") {
|
||||
pronounshtml.textContent = member.pronouns;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -596,31 +610,28 @@ class User extends SnowFlake{
|
|||
const biohtml = this.bio.makeHTML();
|
||||
userbody.appendChild(biohtml);
|
||||
|
||||
membres.then(member=>{
|
||||
if(!member)return;
|
||||
if(member.bio&&member.bio!==""){
|
||||
membres.then((member) => {
|
||||
if (!member) return;
|
||||
if (member.bio && member.bio !== "") {
|
||||
//TODO make markdown take Guild
|
||||
userbody.insertBefore(new MarkDown(member.bio,this.localuser).makeHTML(),biohtml);
|
||||
userbody.insertBefore(new MarkDown(member.bio, this.localuser).makeHTML(), biohtml);
|
||||
biohtml.remove();
|
||||
}
|
||||
});
|
||||
|
||||
if(guild){
|
||||
membres.then(member=>{
|
||||
if(!member)return;
|
||||
usernamehtml.textContent=member.name;
|
||||
if (guild) {
|
||||
membres.then((member) => {
|
||||
if (!member) return;
|
||||
usernamehtml.textContent = member.name;
|
||||
const roles = document.createElement("div");
|
||||
roles.classList.add("flexltr","rolesbox");
|
||||
for(const role of member.roles){
|
||||
if(role.id===member.guild.id) continue;
|
||||
roles.classList.add("flexltr", "rolesbox");
|
||||
for (const role of member.roles) {
|
||||
if (role.id === member.guild.id) continue;
|
||||
const roleDiv = document.createElement("div");
|
||||
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);
|
||||
|
@ -630,57 +641,55 @@ class User extends SnowFlake{
|
|||
userbody.append(roles);
|
||||
});
|
||||
}
|
||||
if(x !== -1){
|
||||
if (x !== -1) {
|
||||
Contextmenu.currentmenu = div;
|
||||
document.body.appendChild(div);
|
||||
Contextmenu.keepOnScreen(div);
|
||||
}
|
||||
return div;
|
||||
}
|
||||
getBanner(guild:Guild|null|Member):HTMLImageElement{
|
||||
getBanner(guild: Guild | null | Member): HTMLImageElement {
|
||||
const banner = document.createElement("img");
|
||||
|
||||
const bsrc=this.getBannerUrl();
|
||||
if(bsrc){
|
||||
const bsrc = this.getBannerUrl();
|
||||
if (bsrc) {
|
||||
banner.src = bsrc;
|
||||
banner.classList.add("banner");
|
||||
}
|
||||
|
||||
if(guild){
|
||||
if(guild instanceof Member){
|
||||
const bsrc=guild.getBannerUrl();
|
||||
if(bsrc){
|
||||
if (guild) {
|
||||
if (guild instanceof Member) {
|
||||
const bsrc = guild.getBannerUrl();
|
||||
if (bsrc) {
|
||||
banner.src = bsrc;
|
||||
banner.classList.add("banner");
|
||||
}
|
||||
}else{
|
||||
Member.resolveMember(this,guild).then(memb=>{
|
||||
if(!memb) return;
|
||||
const bsrc=memb.getBannerUrl();
|
||||
if(bsrc){
|
||||
} else {
|
||||
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`;
|
||||
}else{
|
||||
getBannerUrl(): string | undefined {
|
||||
if (this.banner) {
|
||||
if (!this.hypotheticalbanner) {
|
||||
return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${this.banner}.png`;
|
||||
} else {
|
||||
return this.banner;
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
profileclick(obj: HTMLElement, guild?: Guild): void{
|
||||
obj.onclick = (e: MouseEvent)=>{
|
||||
profileclick(obj: HTMLElement, guild?: Guild): void {
|
||||
obj.onclick = (e: MouseEvent) => {
|
||||
this.buildprofile(e.clientX, e.clientY, guild);
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
@ -688,4 +697,4 @@ class User extends SnowFlake{
|
|||
}
|
||||
|
||||
User.setUpContextMenu();
|
||||
export{ User };
|
||||
export {User};
|
||||
|
|
|
@ -1,88 +1,88 @@
|
|||
class BinRead{
|
||||
private i = 0;
|
||||
private view:DataView;
|
||||
constructor(buffer:ArrayBuffer){
|
||||
this.view=new DataView(buffer, 0)
|
||||
}
|
||||
read16(){
|
||||
const int = this.view.getUint16(this.i);
|
||||
this.i += 2;
|
||||
return int;
|
||||
}
|
||||
read8(){
|
||||
const int = this.view.getUint8(this.i);
|
||||
this.i += 1;
|
||||
return int;
|
||||
}
|
||||
readString8(){
|
||||
return this.readStringNo(this.read8());
|
||||
}
|
||||
readString16(){
|
||||
return this.readStringNo(this.read16());
|
||||
}
|
||||
readFloat32(){
|
||||
const float = this.view.getFloat32(this.i);
|
||||
this.i += 4;
|
||||
return float;
|
||||
}
|
||||
readStringNo(length: number){
|
||||
const array = new Uint8Array(length);
|
||||
for(let i = 0; i < length; i++){
|
||||
array[i] = this.read8();
|
||||
}
|
||||
//console.log(array);
|
||||
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
|
||||
}
|
||||
class BinRead {
|
||||
private i = 0;
|
||||
private view: DataView;
|
||||
constructor(buffer: ArrayBuffer) {
|
||||
this.view = new DataView(buffer, 0);
|
||||
}
|
||||
read16() {
|
||||
const int = this.view.getUint16(this.i);
|
||||
this.i += 2;
|
||||
return int;
|
||||
}
|
||||
read8() {
|
||||
const int = this.view.getUint8(this.i);
|
||||
this.i += 1;
|
||||
return int;
|
||||
}
|
||||
readString8() {
|
||||
return this.readStringNo(this.read8());
|
||||
}
|
||||
readString16() {
|
||||
return this.readStringNo(this.read16());
|
||||
}
|
||||
readFloat32() {
|
||||
const float = this.view.getFloat32(this.i);
|
||||
this.i += 4;
|
||||
return float;
|
||||
}
|
||||
readStringNo(length: number) {
|
||||
const array = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
array[i] = this.read8();
|
||||
}
|
||||
//console.log(array);
|
||||
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
class BinWrite{
|
||||
private view: DataView;
|
||||
private buffer:ArrayBuffer;
|
||||
private i=0;
|
||||
constructor(maxSize:number=2**26){
|
||||
this.buffer=new ArrayBuffer(maxSize);
|
||||
this.view=new DataView(this.buffer, 0);
|
||||
}
|
||||
write32Float(numb:number){
|
||||
this.view.setFloat32(this.i,numb);
|
||||
this.i+=4;
|
||||
}
|
||||
write16(numb:number){
|
||||
this.view.setUint16(this.i,numb);
|
||||
this.i+=2;
|
||||
}
|
||||
write8(numb:number){
|
||||
this.view.setUint8(this.i,numb);
|
||||
this.i+=1;
|
||||
}
|
||||
writeString8(str:string){
|
||||
const encode=new TextEncoder().encode(str);
|
||||
this.write8(encode.length);
|
||||
for(const thing of encode){
|
||||
this.write8(thing);
|
||||
}
|
||||
}
|
||||
writeString16(str:string){
|
||||
const encode=new TextEncoder().encode(str);
|
||||
this.write16(encode.length);
|
||||
for(const thing of encode){
|
||||
this.write8(thing);
|
||||
}
|
||||
}
|
||||
writeStringNo(str:string){
|
||||
const encode=new TextEncoder().encode(str);
|
||||
for(const thing of encode){
|
||||
this.write8(thing);
|
||||
}
|
||||
}
|
||||
getBuffer(){
|
||||
const buf=new ArrayBuffer(this.i);
|
||||
const ar1=new Uint8Array(buf);
|
||||
const ar2=new Uint8Array(this.buffer);
|
||||
for(let i in ar1){
|
||||
ar1[+i]=ar2[+i];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
class BinWrite {
|
||||
private view: DataView;
|
||||
private buffer: ArrayBuffer;
|
||||
private i = 0;
|
||||
constructor(maxSize: number = 2 ** 26) {
|
||||
this.buffer = new ArrayBuffer(maxSize);
|
||||
this.view = new DataView(this.buffer, 0);
|
||||
}
|
||||
write32Float(numb: number) {
|
||||
this.view.setFloat32(this.i, numb);
|
||||
this.i += 4;
|
||||
}
|
||||
write16(numb: number) {
|
||||
this.view.setUint16(this.i, numb);
|
||||
this.i += 2;
|
||||
}
|
||||
write8(numb: number) {
|
||||
this.view.setUint8(this.i, numb);
|
||||
this.i += 1;
|
||||
}
|
||||
writeString8(str: string) {
|
||||
const encode = new TextEncoder().encode(str);
|
||||
this.write8(encode.length);
|
||||
for (const thing of encode) {
|
||||
this.write8(thing);
|
||||
}
|
||||
}
|
||||
writeString16(str: string) {
|
||||
const encode = new TextEncoder().encode(str);
|
||||
this.write16(encode.length);
|
||||
for (const thing of encode) {
|
||||
this.write8(thing);
|
||||
}
|
||||
}
|
||||
writeStringNo(str: string) {
|
||||
const encode = new TextEncoder().encode(str);
|
||||
for (const thing of encode) {
|
||||
this.write8(thing);
|
||||
}
|
||||
}
|
||||
getBuffer() {
|
||||
const buf = new ArrayBuffer(this.i);
|
||||
const ar1 = new Uint8Array(buf);
|
||||
const ar2 = new Uint8Array(this.buffer);
|
||||
for (let i in ar1) {
|
||||
ar1[+i] = ar2[+i];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
export {BinRead,BinWrite}
|
||||
export {BinRead, BinWrite};
|
||||
|
|
|
@ -1,276 +1,259 @@
|
|||
import { I18n } from "../i18n.js";
|
||||
import {I18n} from "../i18n.js";
|
||||
setTheme();
|
||||
export function setTheme() {
|
||||
let name = localStorage.getItem("theme");
|
||||
if (!name) {
|
||||
localStorage.setItem("theme", "Dark");
|
||||
name = "Dark";
|
||||
}
|
||||
document.body.className = name + "-theme";
|
||||
export function setTheme() {
|
||||
let name = localStorage.getItem("theme");
|
||||
if (!name) {
|
||||
localStorage.setItem("theme", "Dark");
|
||||
name = "Dark";
|
||||
}
|
||||
document.body.className = name + "-theme";
|
||||
}
|
||||
export function getBulkUsers() {
|
||||
const json = getBulkInfo();
|
||||
for (const thing in json.users) {
|
||||
json.users[thing] = new Specialuser(json.users[thing]);
|
||||
}
|
||||
return json;
|
||||
const json = getBulkInfo();
|
||||
for (const thing in json.users) {
|
||||
json.users[thing] = new Specialuser(json.users[thing]);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
export function getBulkInfo() {
|
||||
return JSON.parse(localStorage.getItem("userinfos") as string);
|
||||
return JSON.parse(localStorage.getItem("userinfos") as string);
|
||||
}
|
||||
export function setDefaults() {
|
||||
let userinfos = getBulkInfo();
|
||||
if (!userinfos) {
|
||||
localStorage.setItem(
|
||||
"userinfos",
|
||||
JSON.stringify({
|
||||
currentuser: null,
|
||||
users: {},
|
||||
preferences: {
|
||||
theme: "Dark",
|
||||
notifications: false,
|
||||
notisound: "three",
|
||||
},
|
||||
})
|
||||
);
|
||||
userinfos = getBulkInfo();
|
||||
}
|
||||
if (userinfos.users === undefined) {
|
||||
userinfos.users = {};
|
||||
}
|
||||
if (userinfos.accent_color === undefined) {
|
||||
userinfos.accent_color = "#3096f7";
|
||||
}
|
||||
document.documentElement.style.setProperty(
|
||||
"--accent-color",
|
||||
userinfos.accent_color
|
||||
);
|
||||
if (userinfos.preferences === undefined) {
|
||||
userinfos.preferences = {
|
||||
theme: "Dark",
|
||||
notifications: false,
|
||||
notisound: "three",
|
||||
};
|
||||
}
|
||||
if (userinfos.preferences && userinfos.preferences.notisound === undefined) {
|
||||
console.warn("uhoh");
|
||||
userinfos.preferences.notisound = "three";
|
||||
}
|
||||
localStorage.setItem("userinfos", JSON.stringify(userinfos));
|
||||
let userinfos = getBulkInfo();
|
||||
if (!userinfos) {
|
||||
localStorage.setItem(
|
||||
"userinfos",
|
||||
JSON.stringify({
|
||||
currentuser: null,
|
||||
users: {},
|
||||
preferences: {
|
||||
theme: "Dark",
|
||||
notifications: false,
|
||||
notisound: "three",
|
||||
},
|
||||
}),
|
||||
);
|
||||
userinfos = getBulkInfo();
|
||||
}
|
||||
if (userinfos.users === undefined) {
|
||||
userinfos.users = {};
|
||||
}
|
||||
if (userinfos.accent_color === undefined) {
|
||||
userinfos.accent_color = "#3096f7";
|
||||
}
|
||||
document.documentElement.style.setProperty("--accent-color", userinfos.accent_color);
|
||||
if (userinfos.preferences === undefined) {
|
||||
userinfos.preferences = {
|
||||
theme: "Dark",
|
||||
notifications: false,
|
||||
notisound: "three",
|
||||
};
|
||||
}
|
||||
if (userinfos.preferences && userinfos.preferences.notisound === undefined) {
|
||||
console.warn("uhoh");
|
||||
userinfos.preferences.notisound = "three";
|
||||
}
|
||||
localStorage.setItem("userinfos", JSON.stringify(userinfos));
|
||||
}
|
||||
setDefaults();
|
||||
export class Specialuser {
|
||||
serverurls: {
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
wellknown: string;
|
||||
login: string;
|
||||
};
|
||||
email: string;
|
||||
token: string;
|
||||
loggedin;
|
||||
json;
|
||||
constructor(json: any) {
|
||||
if (json instanceof Specialuser) {
|
||||
console.error("specialuser can't construct from another specialuser");
|
||||
}
|
||||
this.serverurls = json.serverurls;
|
||||
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.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"
|
||||
);
|
||||
}
|
||||
}
|
||||
set pfpsrc(e) {
|
||||
this.json.pfpsrc = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get pfpsrc() {
|
||||
return this.json.pfpsrc;
|
||||
}
|
||||
set username(e) {
|
||||
this.json.username = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get username() {
|
||||
return this.json.username;
|
||||
}
|
||||
set localuserStore(e) {
|
||||
this.json.localuserStore = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get localuserStore() {
|
||||
return this.json.localuserStore;
|
||||
}
|
||||
set id(e) {
|
||||
this.json.id = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get id() {
|
||||
return this.json.id;
|
||||
}
|
||||
get uid() {
|
||||
return this.email + this.serverurls.wellknown;
|
||||
}
|
||||
toJSON() {
|
||||
return this.json;
|
||||
}
|
||||
updateLocal() {
|
||||
const info = getBulkInfo();
|
||||
info.users[this.uid] = this.toJSON();
|
||||
localStorage.setItem("userinfos", JSON.stringify(info));
|
||||
}
|
||||
}
|
||||
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||
const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||
export{
|
||||
mobile,
|
||||
iOS,
|
||||
}
|
||||
let instances:
|
||||
| {
|
||||
name: string;
|
||||
description?: string;
|
||||
descriptionLong?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: { alltime: number; daytime: number; weektime: number };
|
||||
urls: {
|
||||
wellknown: string;
|
||||
serverurls: {
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
wellknown: string;
|
||||
login: string;
|
||||
};
|
||||
}[]
|
||||
| null;
|
||||
email: string;
|
||||
token: string;
|
||||
loggedin;
|
||||
json;
|
||||
constructor(json: any) {
|
||||
if (json instanceof Specialuser) {
|
||||
console.error("specialuser can't construct from another specialuser");
|
||||
}
|
||||
this.serverurls = json.serverurls;
|
||||
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.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");
|
||||
}
|
||||
}
|
||||
set pfpsrc(e) {
|
||||
this.json.pfpsrc = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get pfpsrc() {
|
||||
return this.json.pfpsrc;
|
||||
}
|
||||
set username(e) {
|
||||
this.json.username = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get username() {
|
||||
return this.json.username;
|
||||
}
|
||||
set localuserStore(e) {
|
||||
this.json.localuserStore = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get localuserStore() {
|
||||
return this.json.localuserStore;
|
||||
}
|
||||
set id(e) {
|
||||
this.json.id = e;
|
||||
this.updateLocal();
|
||||
}
|
||||
get id() {
|
||||
return this.json.id;
|
||||
}
|
||||
get uid() {
|
||||
return this.email + this.serverurls.wellknown;
|
||||
}
|
||||
toJSON() {
|
||||
return this.json;
|
||||
}
|
||||
updateLocal() {
|
||||
const info = getBulkInfo();
|
||||
info.users[this.uid] = this.toJSON();
|
||||
localStorage.setItem("userinfos", JSON.stringify(info));
|
||||
}
|
||||
}
|
||||
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||
const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||
export {mobile, iOS};
|
||||
let instances:
|
||||
| {
|
||||
name: string;
|
||||
description?: string;
|
||||
descriptionLong?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: {alltime: number; daytime: number; weektime: number};
|
||||
urls: {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
};
|
||||
}[]
|
||||
| null;
|
||||
const datalist = document.getElementById("instances");
|
||||
console.warn(datalist);
|
||||
const instancefetch=fetch("/instances.json")
|
||||
.then(res=>res.json())
|
||||
.then(
|
||||
(json: {
|
||||
name: string;
|
||||
description?: string;
|
||||
descriptionLong?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: { alltime: number; daytime: number; weektime: number };
|
||||
urls: {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
}
|
||||
}[]
|
||||
)=>{
|
||||
instances = json;
|
||||
if(datalist){
|
||||
console.warn(json);
|
||||
const instancein = document.getElementById("instancein") as HTMLInputElement;
|
||||
if(instancein && instancein.value === ""){
|
||||
instancein.value = json[0].name;
|
||||
}
|
||||
for(const instance of json){
|
||||
if(instance.display === false){
|
||||
continue;
|
||||
}
|
||||
const option = document.createElement("option");
|
||||
option.disabled = !instance.online;
|
||||
option.value = instance.name;
|
||||
if(instance.url){
|
||||
stringURLMap.set(option.value, instance.url);
|
||||
if(instance.urls){
|
||||
stringURLsMap.set(instance.url, instance.urls);
|
||||
}
|
||||
}else if(instance.urls){
|
||||
stringURLsMap.set(option.value, instance.urls);
|
||||
}else{
|
||||
option.disabled = true;
|
||||
}
|
||||
if(instance.description){
|
||||
option.label = instance.description;
|
||||
}else{
|
||||
option.label = instance.name;
|
||||
}
|
||||
datalist.append(option);
|
||||
}
|
||||
checkInstance("");
|
||||
}
|
||||
}
|
||||
);
|
||||
const instancefetch = fetch("/instances.json")
|
||||
.then((res) => res.json())
|
||||
.then(
|
||||
(
|
||||
json: {
|
||||
name: string;
|
||||
description?: string;
|
||||
descriptionLong?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
display?: boolean;
|
||||
online?: boolean;
|
||||
uptime: {alltime: number; daytime: number; weektime: number};
|
||||
urls: {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
};
|
||||
}[],
|
||||
) => {
|
||||
instances = json;
|
||||
if (datalist) {
|
||||
console.warn(json);
|
||||
const instancein = document.getElementById("instancein") as HTMLInputElement;
|
||||
if (instancein && instancein.value === "") {
|
||||
instancein.value = json[0].name;
|
||||
}
|
||||
for (const instance of json) {
|
||||
if (instance.display === false) {
|
||||
continue;
|
||||
}
|
||||
const option = document.createElement("option");
|
||||
option.disabled = !instance.online;
|
||||
option.value = instance.name;
|
||||
if (instance.url) {
|
||||
stringURLMap.set(option.value, instance.url);
|
||||
if (instance.urls) {
|
||||
stringURLsMap.set(instance.url, instance.urls);
|
||||
}
|
||||
} else if (instance.urls) {
|
||||
stringURLsMap.set(option.value, instance.urls);
|
||||
} else {
|
||||
option.disabled = true;
|
||||
}
|
||||
if (instance.description) {
|
||||
option.label = instance.description;
|
||||
} else {
|
||||
option.label = instance.name;
|
||||
}
|
||||
datalist.append(option);
|
||||
}
|
||||
checkInstance("");
|
||||
}
|
||||
},
|
||||
);
|
||||
const stringURLMap = new Map<string, string>();
|
||||
|
||||
const stringURLsMap = new Map<
|
||||
string,
|
||||
{
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
wellknown: string;
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
login?: string;
|
||||
}
|
||||
>();
|
||||
>();
|
||||
export async function getapiurls(str: string): Promise<
|
||||
{
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
wellknown: string;
|
||||
login: string;
|
||||
}
|
||||
| {
|
||||
api: string;
|
||||
cdn: string;
|
||||
gateway: string;
|
||||
wellknown: string;
|
||||
login: string;
|
||||
}
|
||||
| false
|
||||
>{
|
||||
function appendApi(str:string){
|
||||
return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api");
|
||||
> {
|
||||
function appendApi(str: string) {
|
||||
return str.includes("api") ? "" : str.endsWith("/") ? "api" : "/api";
|
||||
}
|
||||
if(!URL.canParse(str)){
|
||||
if (!URL.canParse(str)) {
|
||||
const val = stringURLMap.get(str);
|
||||
if(stringURLMap.size===0){
|
||||
await new Promise<void>(res=>{
|
||||
setInterval(()=>{
|
||||
if(stringURLMap.size!==0){
|
||||
if (stringURLMap.size === 0) {
|
||||
await new Promise<void>((res) => {
|
||||
setInterval(() => {
|
||||
if (stringURLMap.size !== 0) {
|
||||
res();
|
||||
}
|
||||
},100);
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
if(val){
|
||||
if (val) {
|
||||
str = val;
|
||||
}else{
|
||||
} else {
|
||||
const val = stringURLsMap.get(str);
|
||||
if(val){
|
||||
const responce = await fetch(
|
||||
val.api + (val.api.endsWith("/") ? "" : "/") + "ping"
|
||||
);
|
||||
if(responce.ok){
|
||||
if(val.login){
|
||||
if (val) {
|
||||
const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
|
||||
if (responce.ok) {
|
||||
if (val.login) {
|
||||
return val as {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
|
@ -278,7 +261,7 @@ export async function getapiurls(str: string): Promise<
|
|||
gateway: string;
|
||||
login: string;
|
||||
};
|
||||
}else{
|
||||
} else {
|
||||
val.login = val.api;
|
||||
return val as {
|
||||
wellknown: string;
|
||||
|
@ -292,43 +275,38 @@ export async function getapiurls(str: string): Promise<
|
|||
}
|
||||
}
|
||||
}
|
||||
if(str.at(-1) !== "/"){
|
||||
if (str.at(-1) !== "/") {
|
||||
str += "/";
|
||||
}
|
||||
let api: string;
|
||||
try{
|
||||
const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json()
|
||||
);
|
||||
try {
|
||||
const info = await fetch(`${str}.well-known/spacebar`).then((x) => x.json());
|
||||
api = info.api;
|
||||
}catch{
|
||||
api=str;
|
||||
} catch {
|
||||
api = str;
|
||||
}
|
||||
if(!URL.canParse(api)){
|
||||
if (!URL.canParse(api)) {
|
||||
return false;
|
||||
}
|
||||
const url = new URL(api);
|
||||
try{
|
||||
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),
|
||||
return {
|
||||
api: info.apiEndpoint + appendApi(apiurl.pathname),
|
||||
gateway: info.gateway,
|
||||
cdn: info.cdn,
|
||||
wellknown: str,
|
||||
login: info.apiEndpoint+appendApi(apiurl.pathname),
|
||||
login: info.apiEndpoint + appendApi(apiurl.pathname),
|
||||
};
|
||||
}catch{
|
||||
} catch {
|
||||
const val = stringURLsMap.get(str);
|
||||
if(val){
|
||||
const responce = await fetch(
|
||||
val.api + (val.api.endsWith("/") ? "" : "/") + "ping"
|
||||
);
|
||||
if(responce.ok){
|
||||
if(val.login){
|
||||
if (val) {
|
||||
const responce = await fetch(val.api + (val.api.endsWith("/") ? "" : "/") + "ping");
|
||||
if (responce.ok) {
|
||||
if (val.login) {
|
||||
return val as {
|
||||
wellknown: string;
|
||||
api: string;
|
||||
|
@ -336,7 +314,7 @@ export async function getapiurls(str: string): Promise<
|
|||
gateway: string;
|
||||
login: string;
|
||||
};
|
||||
}else{
|
||||
} else {
|
||||
val.login = val.api;
|
||||
return val as {
|
||||
wellknown: string;
|
||||
|
@ -351,10 +329,10 @@ export async function getapiurls(str: string): Promise<
|
|||
return false;
|
||||
}
|
||||
}
|
||||
export async function checkInstance(instance: string){
|
||||
export async function checkInstance(instance: string) {
|
||||
await instancefetch;
|
||||
const verify = document.getElementById("verify");
|
||||
try{
|
||||
try {
|
||||
verify!.textContent = I18n.getTranslation("login.checking");
|
||||
const instanceValue = instance;
|
||||
const instanceinfo = (await getapiurls(instanceValue)) as {
|
||||
|
@ -365,72 +343,74 @@ export async function checkInstance(instance: string){
|
|||
login: string;
|
||||
value: string;
|
||||
};
|
||||
if(instanceinfo){
|
||||
if (instanceinfo) {
|
||||
instanceinfo.value = instanceValue;
|
||||
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
|
||||
verify!.textContent = I18n.getTranslation("login.allGood");
|
||||
// @ts-ignore
|
||||
if(checkInstance.alt){
|
||||
// @ts-ignore
|
||||
if (checkInstance.alt) {
|
||||
// @ts-ignore
|
||||
checkInstance.alt();
|
||||
}
|
||||
setTimeout((_: any)=>{
|
||||
setTimeout((_: any) => {
|
||||
console.log(verify!.textContent);
|
||||
verify!.textContent = "";
|
||||
verify!.textContent = "";
|
||||
}, 3000);
|
||||
}else{
|
||||
} else {
|
||||
verify!.textContent = I18n.getTranslation("login.invalid");
|
||||
}
|
||||
}catch{
|
||||
} catch {
|
||||
console.log("catch");
|
||||
verify!.textContent = I18n.getTranslation("login.invalid");
|
||||
}
|
||||
}
|
||||
export function getInstances(){
|
||||
export function getInstances() {
|
||||
return instances;
|
||||
}
|
||||
export class SW{
|
||||
static worker:undefined|ServiceWorker;
|
||||
static setMode(mode:"false"|"offlineOnly"|"true"){
|
||||
localStorage.setItem("SWMode",mode);
|
||||
if(this.worker){
|
||||
this.worker.postMessage({data:mode,code:"setMode"});
|
||||
export class SW {
|
||||
static worker: undefined | ServiceWorker;
|
||||
static setMode(mode: "false" | "offlineOnly" | "true") {
|
||||
localStorage.setItem("SWMode", mode);
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({data: mode, code: "setMode"});
|
||||
}
|
||||
}
|
||||
static checkUpdate(){
|
||||
if(this.worker){
|
||||
this.worker.postMessage({code:"CheckUpdate"});
|
||||
static checkUpdate() {
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({code: "CheckUpdate"});
|
||||
}
|
||||
}
|
||||
static forceClear(){
|
||||
if(this.worker){
|
||||
this.worker.postMessage({code:"ForceClear"});
|
||||
static forceClear() {
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({code: "ForceClear"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator){
|
||||
navigator.serviceWorker.register("/service.js", {
|
||||
scope: "/",
|
||||
}).then((registration) => {
|
||||
let serviceWorker:ServiceWorker|undefined;
|
||||
if (registration.installing) {
|
||||
serviceWorker = registration.installing;
|
||||
console.log("installing");
|
||||
} else if (registration.waiting) {
|
||||
serviceWorker = registration.waiting;
|
||||
console.log("waiting");
|
||||
} else if (registration.active) {
|
||||
serviceWorker = registration.active;
|
||||
console.log("active");
|
||||
}
|
||||
SW.worker=serviceWorker;
|
||||
SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true");
|
||||
if (serviceWorker) {
|
||||
console.log(serviceWorker.state);
|
||||
serviceWorker.addEventListener("statechange", (_) => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register("/service.js", {
|
||||
scope: "/",
|
||||
})
|
||||
.then((registration) => {
|
||||
let serviceWorker: ServiceWorker | undefined;
|
||||
if (registration.installing) {
|
||||
serviceWorker = registration.installing;
|
||||
console.log("installing");
|
||||
} else if (registration.waiting) {
|
||||
serviceWorker = registration.waiting;
|
||||
console.log("waiting");
|
||||
} else if (registration.active) {
|
||||
serviceWorker = registration.active;
|
||||
console.log("active");
|
||||
}
|
||||
SW.worker = serviceWorker;
|
||||
SW.setMode(localStorage.getItem("SWMode") as "false" | "offlineOnly" | "true");
|
||||
if (serviceWorker) {
|
||||
console.log(serviceWorker.state);
|
||||
});
|
||||
}
|
||||
})
|
||||
serviceWorker.addEventListener("statechange", (_) => {
|
||||
console.log(serviceWorker.state);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue