Ran eslint

Signed-off-by: Scott Gould <greysilly7@gmail.com>
This commit is contained in:
Scott Gould 2024-09-19 08:21:12 -04:00
parent a81bc02a5a
commit a5b521e373
No known key found for this signature in database
34 changed files with 11972 additions and 12065 deletions

View file

@ -1,310 +0,0 @@
const globals = require("globals");
const unicorn = require("eslint-plugin-unicorn");
const sonarjs = require("eslint-plugin-sonarjs");
const stylistic = require("@stylistic/eslint-plugin-js");
const htmlESLint = require("@html-eslint/eslint-plugin");
const html = require("eslint-plugin-html");
const tsParser = require("@typescript-eslint/parser");
const linterOptions = {
reportUnusedDisableDirectives: "error"
};
const global = {
...globals.browser
};
const rules = {
"array-callback-return": 2,
"block-scoped-var": 2,
"default-case-last": 2,
"default-param-last": 1,
"dot-notation": 1,
"func-name-matching": 2,
"func-style": 0,
"no-array-constructor": 2,
"no-compare-neg-zero": 2,
"no-const-assign": 2,
"no-constructor-return": 2,
"no-dupe-args": 1,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-div-regex": 2,
"no-eq-null": 2,
"no-extra-boolean-cast": 2,
"no-extra-bind": 2,
"no-extend-native": 2,
"no-empty-pattern": 2,
"no-duplicate-imports": 2,
"no-fallthrough": 2,
"no-func-assign": 2,
"no-import-assign": 2,
"no-invalid-regexp": 2,
"no-invalid-this": 2,
"no-implicit-coercion": [2, {
string: false
}],
"no-implied-eval": 2,
"no-loss-of-precision": 2,
"no-multi-assign": 1,
"no-negated-condition": 1,
"no-new-native-nonconstructor": 2,
"no-new-object": 2,
"no-obj-calls": 2,
"no-self-assign": 2,
"no-unreachable": 1,
"no-unreachable-loop": 1,
"no-unsafe-finally": 2,
"no-unused-vars": 1,
"no-useless-computed-key": 2,
"no-useless-rename": 1,
"no-useless-escape": 1,
"no-unused-expressions": 1,
"no-useless-return": 1,
"no-useless-call": 2,
"no-use-before-define": 0,
"no-useless-concat": 1,
"no-useless-backreference": 1,
"no-useless-catch": 1,
"no-unneeded-ternary": 1,
"no-undef": [2, {
typeof: true
}],
"no-undef-init": 2,
"no-useless-constructor": 1,
"no-redeclare": 1,
"no-shadow-restricted-names": 2,
"no-empty-static-block": 1,
"no-throw-literal": 2,
"no-template-curly-in-string": 1,
"no-unsafe-optional-chaining": 2,
"no-unmodified-loop-condition": 1,
"no-promise-executor-return": 2,
"no-warning-comments": 1,
"no-var": 1,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-multi-str": 2,
"no-shadow": [1, {
builtinGlobals: false
}],
"no-self-compare": 2,
"no-regex-spaces": 1,
"no-constant-binary-expression": 2,
"no-sequences": 2,
"no-irregular-whitespace": [2, {
skipRegExps: true
}],
"no-constant-condition": 1,
"no-unsafe-negation": 2,
"no-lone-blocks": 2,
"object-shorthand": 1,
"prefer-arrow-callback": 1,
"prefer-const": 1,
"use-isnan": 1,
"valid-typeof": 2,
yoda: 2,
"@stylistic/array-bracket-spacing": 2,
"@stylistic/arrow-parens": [2, "as-needed"],
"@stylistic/arrow-spacing": [2, { before: false, after: false }],
"@stylistic/block-spacing": [2, "never"],
"@stylistic/brace-style": 1,
"@stylistic/comma-style": 2,
"@stylistic/computed-property-spacing": 2,
"@stylistic/dot-location": [2, "property"],
"@stylistic/function-call-spacing": 2,
"@stylistic/generator-star-spacing": 2,
"@stylistic/key-spacing": 2,
"@stylistic/indent": [1, "tab"],
"@stylistic/keyword-spacing": [1, { before: false, after: false }],
"@stylistic/new-parens": 2,
"@stylistic/no-mixed-operators": [2, {
groups: [["*", "/"], ["+", "-"]]
}],
"@stylistic/no-extra-semi": 1,
"@stylistic/no-multi-spaces": 1,
"@stylistic/no-mixed-spaces-and-tabs": 2,
"@stylistic/no-floating-decimal": 2,
"@stylistic/no-whitespace-before-property": 2,
"@stylistic/no-trailing-spaces": 1,
"@stylistic/max-statements-per-line": 1,
"@stylistic/max-len": [1, {
code: 200
}],
"@stylistic/quote-props": [2, "as-needed"],
"@stylistic/quotes": [1, "double", {
avoidEscape: false
}],
"@stylistic/padded-blocks": [2, "never"],
"@stylistic/rest-spread-spacing": 2,
"@stylistic/semi": 1,
"@stylistic/space-before-blocks": [2, "never"],
"@stylistic/space-before-function-paren": [2, {
named: "never",
anonymous: "never",
asyncArrow: "always"
}],
"@stylistic/space-in-parens": 2,
"@stylistic/space-unary-ops": 2,
"@stylistic/yield-star-spacing": 2,
"unicorn/error-message": 2,
"unicorn/new-for-builtins": 2,
"unicorn/consistent-empty-array-spread": 2,
"unicorn/consistent-destructuring": 2,
"unicorn/consistent-function-scoping": 2,
"unicorn/no-array-method-this-argument": 2,
"unicorn/no-lonely-if": 1,
"unicorn/no-invalid-fetch-options": 2,
"unicorn/no-instanceof-array": 2,
"unicorn/no-magic-array-flat-depth": 2,
"unicorn/no-nested-ternary": 2,
"unicorn/no-new-buffer": 2,
"unicorn/no-console-spaces": 2,
"unicorn/no-for-loop": 2,
"unicorn/no-useless-undefined": 2,
"unicorn/no-unreadable-iife": 2,
"unicorn/no-unnecessary-await": 2,
"unicorn/no-unreadable-array-destructuring": 2,
"unicorn/no-useless-switch-case": 2,
"unicorn/no-typeof-undefined": 2,
"unicorn/no-useless-fallback-in-spread": 2,
"unicorn/no-useless-length-check": 2,
"unicorn/no-useless-spread": 2,
"unicorn/no-useless-promise-resolve-reject": 2,
"unicorn/no-zero-fractions": 2,
"unicorn/prefer-array-find": 1,
"unicorn/prefer-array-index-of": 1,
"unicorn/prefer-includes": 1,
"unicorn/prefer-logical-operator-over-ternary": 1,
"unicorn/prefer-date-now": 1,
"unicorn/prefer-default-parameters": 1,
"unicorn/prefer-array-some": 1,
"unicorn/prefer-blob-reading-methods": 1,
"unicorn/prefer-at": 1,
"unicorn/prefer-optional-catch-binding": 1,
"unicorn/prefer-regexp-test": 1,
"unicorn/prefer-set-has": 1,
"unicorn/prefer-set-size": 1,
"unicorn/prefer-keyboard-event-key": 1,
"unicorn/prefer-negative-index": 1,
"unicorn/prefer-node-protocol": 1,
"unicorn/prefer-number-properties": [1, {
checkInfinity: true
}],
"unicorn/prefer-prototype-methods": 1,
"unicorn/prefer-string-trim-start-end": 1,
"unicorn/prefer-string-starts-ends-with": 1,
"unicorn/prefer-structured-clone": 1,
"unicorn/throw-new-error": 2,
"unicorn/require-number-to-fixed-digits-argument": 2,
"unicorn/switch-case-braces": [1, "avoid"],
"unicorn/text-encoding-identifier-case": 2,
"unicorn/no-await-in-promise-methods": 2,
"unicorn/no-single-promise-in-promise-methods": 2,
"unicorn/no-negation-in-equality-check": 2,
"unicorn/no-length-as-slice-end": 2,
"sonarjs/no-extra-arguments": 2,
"sonarjs/no-empty-collection": 2,
"sonarjs/no-element-overwrite": 2,
"sonarjs/no-use-of-empty-return-value": 2,
"sonarjs/no-all-duplicated-branches": 2,
"sonarjs/no-ignored-return": 2,
"sonarjs/no-identical-expressions": 2,
"sonarjs/no-one-iteration-loop": 2,
"sonarjs/non-existent-operator": 2,
"sonarjs/no-redundant-boolean": 2,
"sonarjs/no-unused-collection": 1,
"sonarjs/prefer-immediate-return": 2,
"sonarjs/no-inverted-boolean-check": 2,
"sonarjs/no-redundant-jump": 2,
"sonarjs/no-same-line-conditional": 2,
"sonarjs/prefer-object-literal": 2,
"sonarjs/no-collection-size-mischeck": 2,
"sonarjs/prefer-while": 2,
"sonarjs/no-gratuitous-expressions": 2,
"sonarjs/no-duplicated-branches": 2
};
module.exports = [
{
linterOptions,
languageOptions: {
parser: tsParser,
globals: global
},
files: ["webpage/*.ts"],
ignores: ["!*.js", "!*.ts"],
plugins: {
unicorn,
sonarjs,
"@stylistic": stylistic
},
rules
},{
linterOptions,
languageOptions: {
parser: tsParser,
globals: globals.node
},
files: ["*.js", "*.ts"],
plugins: {
unicorn,
sonarjs,
"@stylistic": stylistic
},
rules
},{
linterOptions,
languageOptions: {
globals: global,
parser: require("@html-eslint/parser")
},
files: ["**/*.html"],
plugins: {
unicorn,
sonarjs,
"@stylistic": stylistic,
"@html-eslint": htmlESLint,
html
},
settings: {
"html/html-extensions": [".html"]
},
rules: {
...rules,
"@html-eslint/require-meta-charset": 2,
"@html-eslint/require-button-type": 2,
"@html-eslint/no-restricted-attrs": 2,
"@html-eslint/no-multiple-h1": 1,
"@html-eslint/require-meta-description": 1,
"@html-eslint/no-skip-heading-levels": 2,
"@html-eslint/require-frame-title": 2,
"@html-eslint/no-non-scalable-viewport": 2,
"@html-eslint/no-positive-tabindex": 2,
"@html-eslint/require-meta-viewport": 2,
"@html-eslint/no-abstract-roles": 2,
"@html-eslint/no-aria-hidden-body": 2,
"@html-eslint/no-accesskey-attrs": 2,
"@html-eslint/no-multiple-empty-lines": 2,
"@html-eslint/no-trailing-spaces": 2,
"@html-eslint/indent": [1, "tab"],
"@html-eslint/no-duplicate-attrs": 2,
"@html-eslint/no-inline-styles": 1,
"@html-eslint/no-duplicate-id": 2,
"@html-eslint/no-script-style-type": 2,
"@html-eslint/require-li-container": 2,
"@html-eslint/require-closing-tags": 2,
"@html-eslint/require-doctype": 2,
"@html-eslint/require-lang": 2,
"@html-eslint/require-title": 2,
"@html-eslint/no-extra-spacing-attrs": 2,
"@html-eslint/quotes": 2,
"@html-eslint/require-img-alt": 1
}
}
];

183
eslint.config.mjs Normal file
View file

@ -0,0 +1,183 @@
// @ts-check
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import html from "@html-eslint/eslint-plugin";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.stylisticTypeChecked,
html.configs["flat/recommended"],
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
files: ["**/*.ts", "**/*.html"],
rules: {
"array-callback-return": 2,
"block-scoped-var": 2,
"default-case-last": 2,
"default-param-last": 1,
"dot-notation": 1,
"func-name-matching": 2,
"func-style": 0,
"no-array-constructor": 2,
"no-compare-neg-zero": 2,
"no-const-assign": 2,
"no-constructor-return": 2,
"no-dupe-args": 1,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-div-regex": 2,
"no-eq-null": 2,
"no-extra-boolean-cast": 2,
"no-extra-bind": 2,
"no-extend-native": 2,
"no-empty-pattern": 2,
"no-duplicate-imports": 2,
"no-fallthrough": 2,
"no-func-assign": 2,
"no-import-assign": 2,
"no-invalid-regexp": 2,
"no-invalid-this": 2,
"no-implicit-coercion": [
2,
{
string: false,
},
],
"no-implied-eval": 2,
"no-loss-of-precision": 2,
"no-multi-assign": 1,
"no-negated-condition": 1,
"no-new-native-nonconstructor": 2,
"no-new-object": 2,
"no-obj-calls": 2,
"no-self-assign": 2,
"no-unreachable": 1,
"no-unreachable-loop": 1,
"no-unsafe-finally": 2,
"no-unused-vars": 1,
"no-useless-computed-key": 2,
"no-useless-rename": 1,
"no-useless-escape": 1,
"no-unused-expressions": 1,
"no-useless-return": 1,
"no-useless-call": 2,
"no-use-before-define": 0,
"no-useless-concat": 1,
"no-useless-backreference": 1,
"no-useless-catch": 1,
"no-unneeded-ternary": 1,
"no-undef": [
2,
{
typeof: true,
},
],
"no-undef-init": 2,
"no-useless-constructor": 1,
"no-redeclare": 1,
"no-shadow-restricted-names": 2,
"no-empty-static-block": 1,
"no-throw-literal": 2,
"no-template-curly-in-string": 1,
"no-unsafe-optional-chaining": 2,
"no-unmodified-loop-condition": 1,
"no-promise-executor-return": 2,
"no-warning-comments": 1,
"no-var": 1,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-multi-str": 2,
"no-shadow": [
1,
{
builtinGlobals: false,
},
],
"no-self-compare": 2,
"no-regex-spaces": 1,
"no-constant-binary-expression": 2,
"no-sequences": 2,
"no-irregular-whitespace": [
2,
{
skipRegExps: true,
},
],
"no-constant-condition": 1,
"no-unsafe-negation": 2,
"no-lone-blocks": 2,
"object-shorthand": 1,
"prefer-arrow-callback": 1,
"prefer-const": 1,
"use-isnan": 1,
"valid-typeof": 2,
yoda: 2,
"@stylistic/array-bracket-spacing": 2,
"@stylistic/arrow-parens": [2, "as-needed"],
"@stylistic/arrow-spacing": [2, { before: false, after: false }],
"@stylistic/block-spacing": [2, "never"],
"@stylistic/brace-style": 1,
"@stylistic/comma-style": 2,
"@stylistic/computed-property-spacing": 2,
"@stylistic/dot-location": [2, "property"],
"@stylistic/function-call-spacing": 2,
"@stylistic/generator-star-spacing": 2,
"@stylistic/key-spacing": 2,
"@stylistic/indent": [1, "tab"],
"@stylistic/keyword-spacing": [1, { before: false, after: false }],
"@stylistic/new-parens": 2,
"@stylistic/no-mixed-operators": [
2,
{
groups: [
["*", "/"],
["+", "-"],
],
},
],
"@stylistic/no-extra-semi": 1,
"@stylistic/no-multi-spaces": 1,
"@stylistic/no-mixed-spaces-and-tabs": 2,
"@stylistic/no-floating-decimal": 2,
"@stylistic/no-whitespace-before-property": 2,
"@stylistic/no-trailing-spaces": 1,
"@stylistic/max-statements-per-line": 1,
"@stylistic/max-len": [
1,
{
code: 200,
},
],
"@stylistic/quote-props": [2, "as-needed"],
"@stylistic/quotes": [
1,
"double",
{
avoidEscape: false,
},
],
"@stylistic/padded-blocks": [2, "never"],
"@stylistic/rest-spread-spacing": 2,
"@stylistic/semi": 1,
"@stylistic/space-before-blocks": [2, "never"],
"@stylistic/space-before-function-paren": [
2,
{
named: "never",
anonymous: "never",
asyncArrow: "always",
},
],
"@stylistic/space-in-parens": 2,
"@stylistic/space-unary-ops": 2,
"@stylistic/yield-star-spacing": 2,
},
}
);

240
package-lock.json generated
View file

@ -15,7 +15,7 @@
"ts-to-jsdoc": "^2.2.0" "ts-to-jsdoc": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.7.0", "@eslint/js": "^9.10.0",
"@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/eslint-plugin": "^0.25.0",
"@html-eslint/parser": "^0.25.0", "@html-eslint/parser": "^0.25.0",
"@stylistic/eslint-plugin": "^2.3.0", "@stylistic/eslint-plugin": "^2.3.0",
@ -23,7 +23,7 @@
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^8.42.3",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/node-fetch": "^2.6.11", "@types/node-fetch": "^2.6.11",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-plugin-html": "^8.1.1", "eslint-plugin-html": "^8.1.1",
"eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-sonarjs": "^1.0.4",
"eslint-plugin-unicorn": "^55.0.0", "eslint-plugin-unicorn": "^55.0.0",
@ -31,8 +31,8 @@
"gulp-copy": "^5.0.0", "gulp-copy": "^5.0.0",
"gulp-typescript": "^6.0.0-alpha.1", "gulp-typescript": "^6.0.0-alpha.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.5.4", "typescript": "^5.6.2",
"typescript-eslint": "^7.17.0" "typescript-eslint": "^7.18.0"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@ -290,10 +290,11 @@
"dev": true "dev": true
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.7.0", "version": "9.10.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
"integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
@ -326,6 +327,7 @@
"resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.25.0.tgz", "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.25.0.tgz",
"integrity": "sha512-5DlvqO8bbe90cKSfFDuEblyrEnhAdgNTjWxXeUxt/XXC2OuMC8CsxzLZjtK3+0X6yM8m4xcE3fymCPwg7zdcXQ==", "integrity": "sha512-5DlvqO8bbe90cKSfFDuEblyrEnhAdgNTjWxXeUxt/XXC2OuMC8CsxzLZjtK3+0X6yM8m4xcE3fymCPwg7zdcXQ==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
@ -335,6 +337,7 @@
"resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.25.0.tgz", "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.25.0.tgz",
"integrity": "sha512-dj6U1klMorS/g1QFI6j15AXknsqMDtdingSF6Sdtm1Z7WOVISfDwVyeNbb+JLUG+ATo9aECPQP5A7MHqgP6J9A==", "integrity": "sha512-dj6U1klMorS/g1QFI6j15AXknsqMDtdingSF6Sdtm1Z7WOVISfDwVyeNbb+JLUG+ATo9aECPQP5A7MHqgP6J9A==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"es-html-parser": "^0.0.9" "es-html-parser": "^0.0.9"
}, },
@ -343,13 +346,14 @@
} }
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
"deprecated": "Use @eslint/config-array instead", "deprecated": "Use @eslint/config-array instead",
"dev": true, "dev": true,
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^2.0.2", "@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1", "debug": "^4.3.1",
"minimatch": "^3.0.5" "minimatch": "^3.0.5"
}, },
@ -362,18 +366,20 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/@humanwhocodes/config-array/node_modules/debug": { "node_modules/@humanwhocodes/config-array/node_modules/debug": {
"version": "4.3.5", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -389,6 +395,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@ -397,10 +404,11 @@
} }
}, },
"node_modules/@humanwhocodes/config-array/node_modules/ms": { "node_modules/@humanwhocodes/config-array/node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/@humanwhocodes/module-importer": { "node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1", "version": "1.0.1",
@ -420,7 +428,8 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead", "deprecated": "Use @eslint/object-schema instead",
"dev": true "dev": true,
"license": "BSD-3-Clause"
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2", "version": "3.1.2",
@ -653,6 +662,7 @@
"resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz",
"integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@types/eslint": "*" "@types/eslint": "*"
} }
@ -774,16 +784,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
"integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.17.0", "@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/type-utils": "7.17.0", "@typescript-eslint/type-utils": "7.18.0",
"@typescript-eslint/utils": "7.17.0", "@typescript-eslint/utils": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0", "@typescript-eslint/visitor-keys": "7.18.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -807,15 +818,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
"integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "7.17.0", "@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/typescript-estree": "7.17.0", "@typescript-eslint/typescript-estree": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0", "@typescript-eslint/visitor-keys": "7.18.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -835,12 +847,13 @@
} }
}, },
"node_modules/@typescript-eslint/parser/node_modules/debug": { "node_modules/@typescript-eslint/parser/node_modules/debug": {
"version": "4.3.5", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -852,19 +865,21 @@
} }
}, },
"node_modules/@typescript-eslint/parser/node_modules/ms": { "node_modules/@typescript-eslint/parser/node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
"integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0" "@typescript-eslint/visitor-keys": "7.18.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -875,13 +890,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
"integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "7.17.0", "@typescript-eslint/typescript-estree": "7.18.0",
"@typescript-eslint/utils": "7.17.0", "@typescript-eslint/utils": "7.18.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@ -902,12 +918,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils/node_modules/debug": { "node_modules/@typescript-eslint/type-utils/node_modules/debug": {
"version": "4.3.5", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -919,16 +936,18 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils/node_modules/ms": { "node_modules/@typescript-eslint/type-utils/node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
"integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
}, },
@ -938,13 +957,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
"integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
"dev": true, "dev": true,
"license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0", "@typescript-eslint/visitor-keys": "7.18.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -966,12 +986,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
"version": "4.3.5", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -983,21 +1004,23 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
"integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "7.17.0", "@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/typescript-estree": "7.17.0" "@typescript-eslint/typescript-estree": "7.18.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -1011,12 +1034,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
"integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@ -1032,6 +1056,7 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}, },
@ -1257,6 +1282,7 @@
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -2024,6 +2050,7 @@
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"path-type": "^4.0.0" "path-type": "^4.0.0"
}, },
@ -2239,16 +2266,17 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.57.0", "version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4", "@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.0", "@eslint/js": "8.57.1",
"@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/config-array": "^0.13.0",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0", "@ungap/structured-clone": "^1.2.0",
@ -2391,10 +2419,11 @@
} }
}, },
"node_modules/eslint/node_modules/@eslint/js": { "node_modules/eslint/node_modules/@eslint/js": {
"version": "8.57.0", "version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
@ -3135,6 +3164,7 @@
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"array-union": "^2.1.0", "array-union": "^2.1.0",
"dir-glob": "^3.0.1", "dir-glob": "^3.0.1",
@ -4743,6 +4773,7 @@
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -5458,6 +5489,7 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -5893,10 +5925,11 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.5.4", "version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -5906,14 +5939,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.17.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz",
"integrity": "sha512-spQxsQvPguduCUfyUvLItvKqK3l8KJ/kqs5Pb/URtzQ5AC53Z6us32St37rpmlt2uESG23lOFpV4UErrmy4dZQ==", "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "7.18.0",
"@typescript-eslint/utils": "7.17.0" "@typescript-eslint/utils": "7.18.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"

View file

@ -18,7 +18,7 @@
"ts-to-jsdoc": "^2.2.0" "ts-to-jsdoc": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.7.0", "@eslint/js": "^9.10.0",
"@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/eslint-plugin": "^0.25.0",
"@html-eslint/parser": "^0.25.0", "@html-eslint/parser": "^0.25.0",
"@stylistic/eslint-plugin": "^2.3.0", "@stylistic/eslint-plugin": "^2.3.0",
@ -26,7 +26,7 @@
"@types/eslint__js": "^8.42.3", "@types/eslint__js": "^8.42.3",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/node-fetch": "^2.6.11", "@types/node-fetch": "^2.6.11",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-plugin-html": "^8.1.1", "eslint-plugin-html": "^8.1.1",
"eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-sonarjs": "^1.0.4",
"eslint-plugin-unicorn": "^55.0.0", "eslint-plugin-unicorn": "^55.0.0",
@ -34,7 +34,7 @@
"gulp-copy": "^5.0.0", "gulp-copy": "^5.0.0",
"gulp-typescript": "^6.0.0-alpha.1", "gulp-typescript": "^6.0.0-alpha.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.5.4", "typescript": "^5.6.2",
"typescript-eslint": "^7.17.0" "typescript-eslint": "^7.18.0"
} }
} }

View file

@ -13,108 +13,108 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
interface Instance { interface Instance {
name: string; name: string;
[key: string]: any; [key: string]: any;
} }
const app = express(); const app = express();
import instances from "./webpage/instances.json" with { type: "json" }; import instances from "./webpage/instances.json" with { type: "json" };
const instanceNames = new Map<string, Instance>(); const instanceNames = new Map<string, Instance>();
for (const instance of instances) { for (const instance of instances) {
instanceNames.set(instance.name, instance); instanceNames.set(instance.name, instance);
} }
app.use(compression()); app.use(compression());
async function updateInstances(): Promise<void> { async function updateInstances(): Promise<void> {
try { try {
const response = await fetch( const response = await fetch(
"https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json" "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json"
); );
const json = (await response.json()) as Instance[]; const json = (await response.json()) as Instance[];
for (const instance of json) { for (const instance of json) {
if (!instanceNames.has(instance.name)) { if (!instanceNames.has(instance.name)) {
instances.push(instance as any); instances.push(instance as any);
} else { } else {
const existingInstance = instanceNames.get(instance.name); const existingInstance = instanceNames.get(instance.name);
if (existingInstance) { if (existingInstance) {
for (const key of Object.keys(instance)) { for (const key of Object.keys(instance)) {
if (!existingInstance[key]) { if (!existingInstance[key]) {
existingInstance[key] = instance[key]; existingInstance[key] = instance[key];
} }
} }
} }
} }
} }
observe(instances); observe(instances);
} catch (error) { } catch (error) {
console.error("Error updating instances:", error); console.error("Error updating instances:", error);
} }
} }
updateInstances(); updateInstances();
app.use("/getupdates", (_req: Request, res: Response) => { app.use("/getupdates", (_req: Request, res: Response) => {
try { try {
const stats = fs.statSync(path.join(__dirname, "webpage")); const stats = fs.statSync(path.join(__dirname, "webpage"));
res.send(stats.mtimeMs.toString()); res.send(stats.mtimeMs.toString());
} catch (error) { } catch (error) {
console.error("Error getting updates:", error); console.error("Error getting updates:", error);
res.status(500).send("Error getting updates"); 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); inviteResponse(req, res);
}); });
app.use("/uptime", (req: Request, res: Response) => { app.use("/uptime", (req: Request, res: Response) => {
const instanceUptime = uptime[req.query.name as string]; const instanceUptime = uptime[req.query.name as string];
res.send(instanceUptime); res.send(instanceUptime);
}); });
app.use("/", async (req: Request, res: Response) => { app.use("/", async (req: Request, res: Response) => {
const scheme = req.secure ? "https" : "http"; const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`; const host = `${scheme}://${req.get("Host")}`;
const ref = host + req.originalUrl; const ref = host + req.originalUrl;
if (host && ref) { if (host && ref) {
const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`;
res.set( res.set(
"Link", "Link",
`<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"`
); );
} }
if (req.path === "/") { if (req.path === "/") {
res.sendFile(path.join(__dirname, "webpage", "home.html")); res.sendFile(path.join(__dirname, "webpage", "home.html"));
return; return;
} }
if (req.path.startsWith("/instances.json")) { if (req.path.startsWith("/instances.json")) {
res.json(instances); res.json(instances);
return; return;
} }
if (req.path.startsWith("/invite/")) { if (req.path.startsWith("/invite/")) {
res.sendFile(path.join(__dirname, "webpage", "invite.html")); res.sendFile(path.join(__dirname, "webpage", "invite.html"));
return; return;
} }
const filePath = path.join(__dirname, "webpage", req.path); const filePath = path.join(__dirname, "webpage", req.path);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
res.sendFile(filePath); res.sendFile(filePath);
} else if (fs.existsSync(`${filePath}.html`)) { } else if (fs.existsSync(`${filePath}.html`)) {
res.sendFile(`${filePath}.html`); res.sendFile(`${filePath}.html`);
} else { } else {
res.sendFile(path.join(__dirname, "webpage", "index.html")); res.sendFile(path.join(__dirname, "webpage", "index.html"));
} }
}); });
const PORT = process.env.PORT || Number(process.argv[2]) || 8080; const PORT = process.env.PORT || Number(process.argv[2]) || 8080;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`); console.log(`Server running on port ${PORT}`);
}); });
export { getApiUrls }; export { getApiUrls };

View file

@ -8,244 +8,244 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
interface UptimeEntry { interface UptimeEntry {
time: number; time: number;
online: boolean; online: boolean;
} }
interface UptimeObject { interface UptimeObject {
[key: string]: UptimeEntry[]; [key: string]: UptimeEntry[];
} }
interface Instance { interface Instance {
name: string; name: string;
urls?: { api: string }; urls?: { api: string };
url?: string; url?: string;
online?: boolean; online?: boolean;
uptime?: { uptime?: {
daytime: number; daytime: number;
weektime: number; weektime: number;
alltime: number; alltime: number;
}; };
} }
let uptimeObject: UptimeObject = loadUptimeObject(); let uptimeObject: UptimeObject = loadUptimeObject();
export { uptimeObject as uptime }; export { uptimeObject as uptime };
function loadUptimeObject(): UptimeObject { function loadUptimeObject(): UptimeObject {
const filePath = path.join(__dirname, "..", "uptime.json"); const filePath = path.join(__dirname, "..", "uptime.json");
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
try { try {
return JSON.parse(fs.readFileSync(filePath, "utf8")); return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (error) { } catch (error) {
console.error("Error reading uptime.json:", error); console.error("Error reading uptime.json:", error);
return {}; return {};
} }
} }
return {}; return {};
} }
function saveUptimeObject(): void { function saveUptimeObject(): void {
fs.writeFile( fs.writeFile(
`${__dirname}/uptime.json`, `${__dirname}/uptime.json`,
JSON.stringify(uptimeObject), JSON.stringify(uptimeObject),
(error) => { (error) => {
if (error) { if (error) {
console.error("Error saving uptime.json:", error); console.error("Error saving uptime.json:", error);
} }
} }
); );
} }
function removeUndefinedKey(): void { function removeUndefinedKey(): void {
if (uptimeObject.undefined) { if (uptimeObject.undefined) {
delete uptimeObject.undefined; delete uptimeObject.undefined;
saveUptimeObject(); saveUptimeObject();
} }
} }
removeUndefinedKey(); removeUndefinedKey();
export async function observe(instances: Instance[]): Promise<void> { export async function observe(instances: Instance[]): Promise<void> {
const activeInstances = new Set<string>(); const activeInstances = new Set<string>();
const instancePromises = instances.map((instance) => const instancePromises = instances.map((instance) =>
resolveInstance(instance, activeInstances) resolveInstance(instance, activeInstances)
); );
await Promise.allSettled(instancePromises); await Promise.allSettled(instancePromises);
updateInactiveInstances(activeInstances); updateInactiveInstances(activeInstances);
} }
async function resolveInstance( async function resolveInstance(
instance: Instance, instance: Instance,
activeInstances: Set<string> activeInstances: Set<string>
): Promise<void> { ): Promise<void> {
try { try {
calcStats(instance); calcStats(instance);
const api = await getApiUrl(instance); const api = await getApiUrl(instance);
if (!api) { if (!api) {
handleUnresolvedApi(instance); handleUnresolvedApi(instance);
return; return;
} }
activeInstances.add(instance.name); activeInstances.add(instance.name);
scheduleHealthCheck(instance, api); scheduleHealthCheck(instance, api);
} catch (error) { } catch (error) {
console.error("Error resolving instance:", error); console.error("Error resolving instance:", error);
} }
} }
async function getApiUrl(instance: Instance): Promise<string | null> { async function getApiUrl(instance: Instance): Promise<string | null> {
if (instance.urls) { if (instance.urls) {
return instance.urls.api; return instance.urls.api;
} }
if (instance.url) { if (instance.url) {
const urls = await getApiUrls(instance.url); const urls = await getApiUrls(instance.url);
return urls ? urls.api : null; return urls ? urls.api : null;
} }
return null; return null;
} }
function handleUnresolvedApi(instance: Instance): void { function handleUnresolvedApi(instance: Instance): void {
setStatus(instance, false); setStatus(instance, false);
console.warn(`${instance.name} does not resolve api URL`, instance); 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 checkInterval = 1000 * 60 * 30;
const initialDelay = Math.random() * 1000 * 60 * 10; const initialDelay = Math.random() * 1000 * 60 * 10;
setTimeout(() => { setTimeout(() => {
checkHealth(instance, api); checkHealth(instance, api);
setInterval(() => checkHealth(instance, api), checkInterval); setInterval(() => checkHealth(instance, api), checkInterval);
}, initialDelay); }, initialDelay);
} }
async function checkHealth( async function checkHealth(
instance: Instance, instance: Instance,
api: string, api: string,
tries = 0 tries = 0
): Promise<void> { ): Promise<void> {
try { try {
const response = await fetch(`${api}ping`, { method: "HEAD" }); const response = await fetch(`${api}ping`, { method: "HEAD" });
if (response.ok || tries > 3) { if (response.ok || tries > 3) {
setStatus(instance, response.ok); setStatus(instance, response.ok);
} else { } else {
retryHealthCheck(instance, api, tries); retryHealthCheck(instance, api, tries);
} }
} catch (error) { } catch (error) {
console.error("Error checking health:", error); console.error("Error checking health:", error);
if (tries > 3) { if (tries > 3) {
setStatus(instance, false); setStatus(instance, false);
} else { } else {
retryHealthCheck(instance, api, tries); retryHealthCheck(instance, api, tries);
} }
} }
} }
function retryHealthCheck( function retryHealthCheck(
instance: Instance, instance: Instance,
api: string, api: string,
tries: number tries: number
): void { ): void {
setTimeout(() => checkHealth(instance, api, tries + 1), 30000); setTimeout(() => checkHealth(instance, api, tries + 1), 30000);
} }
function updateInactiveInstances(activeInstances: Set<string>): void { function updateInactiveInstances(activeInstances: Set<string>): void {
for (const key of Object.keys(uptimeObject)) { for (const key of Object.keys(uptimeObject)) {
if (!activeInstances.has(key)) { if (!activeInstances.has(key)) {
setStatus(key, false); setStatus(key, false);
} }
} }
} }
function calcStats(instance: Instance): void { function calcStats(instance: Instance): void {
const obj = uptimeObject[instance.name]; const obj = uptimeObject[instance.name];
if (!obj) return; if (!obj) return;
const now = Date.now(); const now = Date.now();
const day = now - 1000 * 60 * 60 * 24; const day = now - 1000 * 60 * 60 * 24;
const week = now - 1000 * 60 * 60 * 24 * 7; const week = now - 1000 * 60 * 60 * 24 * 7;
let totalTimePassed = 0; let totalTimePassed = 0;
let alltime = 0; let alltime = 0;
let daytime = 0; let daytime = 0;
let weektime = 0; let weektime = 0;
let online = false; let online = false;
for (let i = 0; i < obj.length; i++) { for (let i = 0; i < obj.length; i++) {
const entry = obj[i]; const entry = obj[i];
online = entry.online; online = entry.online;
const stamp = entry.time; const stamp = entry.time;
const nextStamp = obj[i + 1]?.time || now; const nextStamp = obj[i + 1]?.time || now;
const timePassed = nextStamp - stamp; const timePassed = nextStamp - stamp;
totalTimePassed += timePassed; totalTimePassed += timePassed;
alltime += Number(online) * timePassed; alltime += Number(online) * timePassed;
if (stamp + timePassed > week) { if (stamp + timePassed > week) {
const weekTimePassed = Math.min(timePassed, nextStamp - week); const weekTimePassed = Math.min(timePassed, nextStamp - week);
weektime += Number(online) * weekTimePassed; weektime += Number(online) * weekTimePassed;
if (stamp + timePassed > day) { if (stamp + timePassed > day) {
const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); const dayTimePassed = Math.min(weekTimePassed, nextStamp - day);
daytime += Number(online) * dayTimePassed; daytime += Number(online) * dayTimePassed;
} }
} }
} }
instance.online = online; instance.online = online;
instance.uptime = calculateUptimeStats( instance.uptime = calculateUptimeStats(
totalTimePassed, totalTimePassed,
alltime, alltime,
daytime, daytime,
weektime, weektime,
online online
); );
} }
function calculateUptimeStats( function calculateUptimeStats(
totalTimePassed: number, totalTimePassed: number,
alltime: number, alltime: number,
daytime: number, daytime: number,
weektime: number, weektime: number,
online: boolean online: boolean
): { daytime: number; weektime: number; alltime: number } { ): { daytime: number; weektime: number; alltime: number } {
const dayInMs = 1000 * 60 * 60 * 24; const dayInMs = 1000 * 60 * 60 * 24;
const weekInMs = dayInMs * 7; const weekInMs = dayInMs * 7;
alltime /= totalTimePassed; alltime /= totalTimePassed;
if (totalTimePassed > dayInMs) { if (totalTimePassed > dayInMs) {
daytime = daytime || (online ? dayInMs : 0); daytime = daytime || (online ? dayInMs : 0);
daytime /= dayInMs; daytime /= dayInMs;
if (totalTimePassed > weekInMs) { if (totalTimePassed > weekInMs) {
weektime = weektime || (online ? weekInMs : 0); weektime = weektime || (online ? weekInMs : 0);
weektime /= weekInMs; weektime /= weekInMs;
} else { } else {
weektime = alltime; weektime = alltime;
} }
} else { } else {
weektime = alltime; weektime = alltime;
daytime = 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; const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject[name]; let obj = uptimeObject[name];
if (!obj) { if (!obj) {
obj = []; obj = [];
uptimeObject[name] = obj; uptimeObject[name] = obj;
} }
if (obj.at(-1)?.online !== status) { if (obj.at(-1)?.online !== status) {
obj.push({ time: Date.now(), online: status }); obj.push({ time: Date.now(), online: status });
saveUptimeObject(); saveUptimeObject();
} }
if (typeof instance !== "string") { if (typeof instance !== "string") {
calcStats(instance); calcStats(instance);
} }
} }

View file

@ -2,113 +2,113 @@ import fetch from "node-fetch";
import { Request, Response } from "express"; import { Request, Response } from "express";
interface ApiUrls { interface ApiUrls {
api: string; api: string;
gateway: string; gateway: string;
cdn: string; cdn: string;
wellknown: string; wellknown: string;
} }
interface Invite { interface Invite {
guild: { guild: {
name: string; name: string;
description?: string; description?: string;
icon?: string; icon?: string;
id: string; id: string;
}; };
inviter?: { inviter?: {
username: string; username: string;
}; };
} }
export async function getApiUrls(url: string): Promise<ApiUrls | null> { export async function getApiUrls(url: string): Promise<ApiUrls | null> {
if (!url.endsWith("/")) { if (!url.endsWith("/")) {
url += "/"; url += "/";
} }
try { try {
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(
(res) => res.json() as Promise<ApiUrls> (res) => res.json() as Promise<ApiUrls>
); );
const api = info.api; const api = info.api;
const apiUrl = new URL(api); const apiUrl = new URL(api);
const policies: any = await fetch( const policies: any = await fetch(
`${api}${ `${api}${
apiUrl.pathname.includes("api") ? "" : "api" apiUrl.pathname.includes("api") ? "" : "api"
}/policies/instance/domains` }/policies/instance/domains`
).then((res) => res.json()); ).then((res) => res.json());
return { return {
api: policies.apiEndpoint, api: policies.apiEndpoint,
gateway: policies.gateway, gateway: policies.gateway,
cdn: policies.cdn, cdn: policies.cdn,
wellknown: url, wellknown: url,
}; };
} catch (error) { } catch (error) {
console.error("Error fetching API URLs:", error); console.error("Error fetching API URLs:", error);
return null; return null;
} }
} }
export async function inviteResponse( export async function inviteResponse(
req: Request, req: Request,
res: Response res: Response
): Promise<void> { ): Promise<void> {
let url: URL; let url: URL;
if (URL.canParse(req.query.url as string)) { if (URL.canParse(req.query.url as string)) {
url = new URL(req.query.url as string); url = new URL(req.query.url as string);
} else { } else {
const scheme = req.secure ? "https" : "http"; const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`; const host = `${scheme}://${req.get("Host")}`;
url = new URL(host); url = new URL(host);
} }
try { try {
if (url.pathname.startsWith("invite")) { if (url.pathname.startsWith("invite")) {
throw new Error("Invalid invite URL"); throw new Error("Invalid invite URL");
} }
const code = url.pathname.split("/")[2]; const code = url.pathname.split("/")[2];
const instance = url.searchParams.get("instance"); const instance = url.searchParams.get("instance");
if (!instance) { if (!instance) {
throw new Error("Instance not specified"); throw new Error("Instance not specified");
} }
const urls = await getApiUrls(instance); const urls = await getApiUrls(instance);
if (!urls) { if (!urls) {
throw new Error("Failed to get API URLs"); throw new Error("Failed to get API URLs");
} }
const invite = await fetch(`${urls.api}/invites/${code}`).then( const invite = await fetch(`${urls.api}/invites/${code}`).then(
(res) => res.json() as Promise<Invite> (res) => res.json() as Promise<Invite>
); );
const title = invite.guild.name; const title = invite.guild.name;
const description = invite.inviter const description = invite.inviter
? `${invite.inviter.username} has invited you to ${invite.guild.name}${ ? `${invite.inviter.username} has invited you to ${invite.guild.name}${
invite.guild.description ? `\n${invite.guild.description}` : "" invite.guild.description ? `\n${invite.guild.description}` : ""
}` }`
: `You've been invited to ${invite.guild.name}${ : `You've been invited to ${invite.guild.name}${
invite.guild.description ? `\n${invite.guild.description}` : "" invite.guild.description ? `\n${invite.guild.description}` : ""
}`; }`;
const thumbnail = invite.guild.icon const thumbnail = invite.guild.icon
? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png` ? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png`
: ""; : "";
const jsonResponse = { const jsonResponse = {
type: "link", type: "link",
version: "1.0", version: "1.0",
title, title,
thumbnail, thumbnail,
description, description,
}; };
res.json(jsonResponse); res.json(jsonResponse);
} catch (error) { } catch (error) {
console.error("Error processing invite response:", error); console.error("Error processing invite response:", error);
const jsonResponse = { const jsonResponse = {
type: "link", type: "link",
version: "1.0", version: "1.0",
title: "Jank Client", title: "Jank Client",
thumbnail: "/logo.webp", thumbnail: "/logo.webp",
description: "A spacebar client that has DMs, replying and more", description: "A spacebar client that has DMs, replying and more",
url: url.toString(), url: url.toString(),
}; };
res.json(jsonResponse); res.json(jsonResponse);
} }
} }

View file

@ -1,164 +1,164 @@
import { getBulkInfo } from "./login.js"; import { getBulkInfo } from "./login.js";
class Voice { class Voice {
audioCtx: AudioContext; audioCtx: AudioContext;
info: { wave: string | Function; freq: number }; info: { wave: string | Function; freq: number };
playing: boolean; playing: boolean;
myArrayBuffer: AudioBuffer; myArrayBuffer: AudioBuffer;
gainNode: GainNode; gainNode: GainNode;
buffer: Float32Array; buffer: Float32Array;
source: AudioBufferSourceNode; source: AudioBufferSourceNode;
constructor(wave: string | Function, freq: number, volume = 1) { constructor(wave: string | Function, freq: number, volume = 1) {
this.audioCtx = new window.AudioContext(); this.audioCtx = new window.AudioContext();
this.info = { wave, freq }; this.info = { wave, freq };
this.playing = false; this.playing = false;
this.myArrayBuffer = this.audioCtx.createBuffer( this.myArrayBuffer = this.audioCtx.createBuffer(
1, 1,
this.audioCtx.sampleRate, this.audioCtx.sampleRate,
this.audioCtx.sampleRate this.audioCtx.sampleRate
); );
this.gainNode = this.audioCtx.createGain(); this.gainNode = this.audioCtx.createGain();
this.gainNode.gain.value = volume; this.gainNode.gain.value = volume;
this.gainNode.connect(this.audioCtx.destination); this.gainNode.connect(this.audioCtx.destination);
this.buffer = this.myArrayBuffer.getChannelData(0); this.buffer = this.myArrayBuffer.getChannelData(0);
this.source = this.audioCtx.createBufferSource(); this.source = this.audioCtx.createBufferSource();
this.source.buffer = this.myArrayBuffer; this.source.buffer = this.myArrayBuffer;
this.source.loop = true; this.source.loop = true;
this.source.start(); this.source.start();
this.updateWave(); this.updateWave();
} }
get wave(): string | Function { get wave(): string | Function {
return this.info.wave; return this.info.wave;
} }
get freq(): number { get freq(): number {
return this.info.freq; return this.info.freq;
} }
set wave(wave: string | Function) { set wave(wave: string | Function) {
this.info.wave = wave; this.info.wave = wave;
this.updateWave(); this.updateWave();
} }
set freq(freq: number) { set freq(freq: number) {
this.info.freq = freq; this.info.freq = freq;
this.updateWave(); this.updateWave();
} }
updateWave(): void { updateWave(): void {
const func = this.waveFunction(); 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); this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq);
} }
} }
waveFunction(): Function { waveFunction(): Function {
if (typeof this.wave === "function") { if (typeof this.wave === "function") {
return this.wave; return this.wave;
} }
switch (this.wave) { switch (this.wave) {
case "sin": case "sin":
return (t: number, freq: number) => { return (t: number, freq: number) => {
return Math.sin(t * Math.PI * 2 * freq); return Math.sin(t * Math.PI * 2 * freq);
}; };
case "triangle": case "triangle":
return (t: number, freq: number) => { return (t: number, freq: number) => {
return Math.abs(((4 * t * freq) % 4) - 2) - 1; return Math.abs(((4 * t * freq) % 4) - 2) - 1;
}; };
case "sawtooth": case "sawtooth":
return (t: number, freq: number) => { return (t: number, freq: number) => {
return ((t * freq) % 1) * 2 - 1; return ((t * freq) % 1) * 2 - 1;
}; };
case "square": case "square":
return (t: number, freq: number) => { return (t: number, freq: number) => {
return (t * freq) % 2 < 1 ? 1 : -1; return (t * freq) % 2 < 1 ? 1 : -1;
}; };
case "white": case "white":
return (_t: number, _freq: number) => { return (_t: number, _freq: number) => {
return Math.random() * 2 - 1; return Math.random() * 2 - 1;
}; };
case "noise": case "noise":
return (_t: number, _freq: number) => { return (_t: number, _freq: number) => {
return 0; return 0;
}; };
} }
return new Function(); return new Function();
} }
play(): void { play(): void {
if (this.playing) { if (this.playing) {
return; return;
} }
this.source.connect(this.gainNode); this.source.connect(this.gainNode);
this.playing = true; this.playing = true;
} }
stop(): void { stop(): void {
if (this.playing) { if (this.playing) {
this.source.disconnect(); this.source.disconnect();
this.playing = false; this.playing = false;
} }
} }
static noises(noise: string): void { static noises(noise: string): void {
switch (noise) { switch (noise) {
case "three": { case "three": {
const voicy = new Voice("sin", 800); const voicy = new Voice("sin", 800);
voicy.play(); voicy.play();
setTimeout((_) => { setTimeout((_) => {
voicy.freq = 1000; voicy.freq = 1000;
}, 50); }, 50);
setTimeout((_) => { setTimeout((_) => {
voicy.freq = 1300; voicy.freq = 1300;
}, 100); }, 100);
setTimeout((_) => { setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
} }
case "zip": { case "zip": {
const voicy = new Voice((t: number, freq: number) => { const voicy = new Voice((t: number, freq: number) => {
return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq);
}, 700); }, 700);
voicy.play(); voicy.play();
setTimeout((_) => { setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
} }
case "square": { case "square": {
const voicy = new Voice("square", 600, 0.4); const voicy = new Voice("square", 600, 0.4);
voicy.play(); voicy.play();
setTimeout((_) => { setTimeout((_) => {
voicy.freq = 800; voicy.freq = 800;
}, 50); }, 50);
setTimeout((_) => { setTimeout((_) => {
voicy.freq = 1000; voicy.freq = 1000;
}, 100); }, 100);
setTimeout((_) => { setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
} }
case "beep": { case "beep": {
const voicy = new Voice("sin", 800); const voicy = new Voice("sin", 800);
voicy.play(); voicy.play();
setTimeout((_) => { setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 50); }, 50);
setTimeout((_) => { setTimeout((_) => {
voicy.play(); voicy.play();
}, 100); }, 100);
setTimeout((_) => { setTimeout((_) => {
voicy.stop(); voicy.stop();
}, 150); }, 150);
break; break;
} }
} }
} }
static get sounds() { static get sounds() {
return ["three", "zip", "square", "beep"]; return ["three", "zip", "square", "beep"];
} }
static setNotificationSound(sound: string) { static setNotificationSound(sound: string) {
const userinfos = getBulkInfo(); const userinfos = getBulkInfo();
userinfos.preferences.notisound = sound; userinfos.preferences.notisound = sound;
localStorage.setItem("userinfos", JSON.stringify(userinfos)); localStorage.setItem("userinfos", JSON.stringify(userinfos));
} }
static getNotificationSound() { static getNotificationSound() {
const userinfos = getBulkInfo(); const userinfos = getBulkInfo();
return userinfos.preferences.notisound; return userinfos.preferences.notisound;
} }
} }
export { Voice }; export { Voice };

File diff suppressed because it is too large Load diff

View file

@ -1,107 +1,107 @@
class Contextmenu<x, y> { class Contextmenu<x, y> {
static currentmenu: HTMLElement | ""; static currentmenu: HTMLElement | "";
name: string; name: string;
buttons: [ buttons: [
string, string,
(this: x, arg: y, e: MouseEvent) => void, (this: x, arg: y, e: MouseEvent) => void,
string | null, string | null,
(this: x, arg: y) => boolean, (this: x, arg: y) => boolean,
(this: x, arg: y) => boolean, (this: x, arg: y) => boolean,
string string
][]; ][];
div!: HTMLDivElement; div!: HTMLDivElement;
static setup() { static setup() {
Contextmenu.currentmenu = ""; Contextmenu.currentmenu = "";
document.addEventListener("click", (event) => { document.addEventListener("click", (event) => {
if (Contextmenu.currentmenu === "") { if (Contextmenu.currentmenu === "") {
return; return;
} }
if (!Contextmenu.currentmenu.contains(event.target as Node)) { if (!Contextmenu.currentmenu.contains(event.target as Node)) {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
Contextmenu.currentmenu = ""; Contextmenu.currentmenu = "";
} }
}); });
} }
constructor(name: string) { constructor(name: string) {
this.name = name; this.name = name;
this.buttons = []; this.buttons = [];
} }
addbutton( addbutton(
text: string, text: string,
onclick: (this: x, arg: y, e: MouseEvent) => void, onclick: (this: x, arg: y, e: MouseEvent) => void,
img: null | string = null, img: null | string = null,
shown: (this: x, arg: y) => boolean = (_) => true, shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = (_) => true enabled: (this: x, arg: y) => boolean = (_) => true
) { ) {
this.buttons.push([text, onclick, img, shown, enabled, "button"]); this.buttons.push([text, onclick, img, shown, enabled, "button"]);
return {}; return {};
} }
addsubmenu( addsubmenu(
text: string, text: string,
onclick: (this: x, arg: y, e: MouseEvent) => void, onclick: (this: x, arg: y, e: MouseEvent) => void,
img = null, img = null,
shown: (this: x, arg: y) => boolean = (_) => true, shown: (this: x, arg: y) => boolean = (_) => true,
enabled: (this: x, arg: y) => boolean = (_) => true enabled: (this: x, arg: y) => boolean = (_) => true
) { ) {
this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); this.buttons.push([text, onclick, img, shown, enabled, "submenu"]);
return {}; return {};
} }
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"); const div = document.createElement("div");
div.classList.add("contextmenu", "flexttb"); div.classList.add("contextmenu", "flexttb");
let visibleButtons = 0; let visibleButtons = 0;
for (const thing of this.buttons) { for (const thing of this.buttons) {
if (!thing[3].bind(addinfo).call(addinfo, other)) continue; if (!thing[3].bind(addinfo).call(addinfo, other)) continue;
visibleButtons++; visibleButtons++;
const intext = document.createElement("button"); const intext = document.createElement("button");
intext.disabled = !thing[4].bind(addinfo).call(addinfo, other); intext.disabled = !thing[4].bind(addinfo).call(addinfo, other);
intext.classList.add("contextbutton"); intext.classList.add("contextbutton");
intext.textContent = thing[0]; intext.textContent = thing[0];
console.log(thing); console.log(thing);
if (thing[5] === "button" || thing[5] === "submenu") { if (thing[5] === "button" || thing[5] === "submenu") {
intext.onclick = thing[1].bind(addinfo, other); intext.onclick = thing[1].bind(addinfo, other);
} }
div.appendChild(intext); div.appendChild(intext);
} }
if (visibleButtons == 0) return; if (visibleButtons == 0) return;
if (Contextmenu.currentmenu != "") { if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
div.style.top = y + "px"; div.style.top = y + "px";
div.style.left = x + "px"; div.style.left = x + "px";
document.body.appendChild(div); document.body.appendChild(div);
Contextmenu.keepOnScreen(div); Contextmenu.keepOnScreen(div);
console.log(div); console.log(div);
Contextmenu.currentmenu = div; Contextmenu.currentmenu = div;
return this.div; return this.div;
} }
bindContextmenu(obj: HTMLElement, addinfo: x, other: y) { bindContextmenu(obj: HTMLElement, addinfo: x, other: y) {
const func = (event: MouseEvent) => { const func = (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.makemenu(event.clientX, event.clientY, addinfo, other); this.makemenu(event.clientX, event.clientY, addinfo, other);
}; };
obj.addEventListener("contextmenu", func); obj.addEventListener("contextmenu", func);
return func; return func;
} }
static keepOnScreen(obj: HTMLElement) { static keepOnScreen(obj: HTMLElement) {
const html = document.documentElement.getBoundingClientRect(); const html = document.documentElement.getBoundingClientRect();
const docheight = html.height; const docheight = html.height;
const docwidth = html.width; const docwidth = html.width;
const box = obj.getBoundingClientRect(); const box = obj.getBoundingClientRect();
console.log(box, docheight, docwidth); console.log(box, docheight, docwidth);
if (box.right > docwidth) { if (box.right > docwidth) {
console.log("test"); console.log("test");
obj.style.left = docwidth - box.width + "px"; obj.style.left = docwidth - box.width + "px";
} }
if (box.bottom > docheight) { if (box.bottom > docheight) {
obj.style.top = docheight - box.height + "px"; obj.style.top = docheight - box.height + "px";
} }
} }
} }
Contextmenu.setup(); Contextmenu.setup();
export { Contextmenu }; export { Contextmenu };

View file

@ -1,273 +1,273 @@
type dialogjson = type dialogjson =
| ["hdiv", ...dialogjson[]] | ["hdiv", ...dialogjson[]]
| ["vdiv", ...dialogjson[]] | ["vdiv", ...dialogjson[]]
| ["img", string, [number, number] | undefined | ["fit"]] | ["img", string, [number, number] | undefined | ["fit"]]
| ["checkbox", string, boolean, (this: HTMLInputElement, e: Event) => unknown] | ["checkbox", string, boolean, (this: HTMLInputElement, e: Event) => unknown]
| ["button", string, string, (this: HTMLButtonElement, e: Event) => unknown] | ["button", string, string, (this: HTMLButtonElement, e: Event) => unknown]
| ["mdbox", string, string, (this: HTMLTextAreaElement, e: Event) => unknown] | ["mdbox", string, string, (this: HTMLTextAreaElement, e: Event) => unknown]
| ["textbox", string, string, (this: HTMLInputElement, e: Event) => unknown] | ["textbox", string, string, (this: HTMLInputElement, e: Event) => unknown]
| ["fileupload", string, (this: HTMLInputElement, e: Event) => unknown] | ["fileupload", string, (this: HTMLInputElement, e: Event) => unknown]
| ["text", string] | ["text", string]
| ["title", string] | ["title", string]
| ["radio", string, string[], (this: unknown, e: string) => unknown, number] | ["radio", string, string[], (this: unknown, e: string) => unknown, number]
| ["html", HTMLElement] | ["html", HTMLElement]
| [ | [
"select", "select",
string, string,
string[], string[],
(this: HTMLSelectElement, e: Event) => unknown, (this: HTMLSelectElement, e: Event) => unknown,
number number
] ]
| ["tabs", [string, dialogjson][]]; | ["tabs", [string, dialogjson][]];
class Dialog { class Dialog {
layout: dialogjson; layout: dialogjson;
onclose: Function; onclose: Function;
onopen: Function; onopen: Function;
html: HTMLDivElement; html: HTMLDivElement;
background!: HTMLDivElement; background!: HTMLDivElement;
constructor( constructor(
layout: dialogjson, layout: dialogjson,
onclose = (_: any) => {}, onclose = (_: any) => {},
onopen = (_: any) => {} onopen = (_: any) => {}
) { ) {
this.layout = layout; this.layout = layout;
this.onclose = onclose; this.onclose = onclose;
this.onopen = onopen; this.onopen = onopen;
const div = document.createElement("div"); const div = document.createElement("div");
div.appendChild(this.tohtml(layout)); div.appendChild(this.tohtml(layout));
this.html = div; this.html = div;
this.html.classList.add("centeritem"); this.html.classList.add("centeritem");
if (!(layout[0] === "img")) { if (!(layout[0] === "img")) {
this.html.classList.add("nonimagecenter"); this.html.classList.add("nonimagecenter");
} }
} }
tohtml(array: dialogjson): HTMLElement { tohtml(array: dialogjson): HTMLElement {
switch (array[0]) { switch (array[0]) {
case "img": case "img":
const img = document.createElement("img"); const img = document.createElement("img");
img.src = array[1]; img.src = array[1];
if (array[2] != undefined) { if (array[2] != undefined) {
if (array[2].length === 2) { if (array[2].length === 2) {
img.width = array[2][0]; img.width = array[2][0];
img.height = array[2][1]; img.height = array[2][1];
} else if (array[2][0] === "fit") { } else if (array[2][0] === "fit") {
img.classList.add("imgfit"); img.classList.add("imgfit");
} }
} }
return img; return img;
case "hdiv": case "hdiv":
const hdiv = document.createElement("div"); const hdiv = document.createElement("div");
hdiv.classList.add("flexltr"); hdiv.classList.add("flexltr");
for (const thing of array) { for (const thing of array) {
if (thing === "hdiv") { if (thing === "hdiv") {
continue; continue;
} }
hdiv.appendChild(this.tohtml(thing)); hdiv.appendChild(this.tohtml(thing));
} }
return hdiv; return hdiv;
case "vdiv": case "vdiv":
const vdiv = document.createElement("div"); const vdiv = document.createElement("div");
vdiv.classList.add("flexttb"); vdiv.classList.add("flexttb");
for (const thing of array) { for (const thing of array) {
if (thing === "vdiv") { if (thing === "vdiv") {
continue; continue;
} }
vdiv.appendChild(this.tohtml(thing)); vdiv.appendChild(this.tohtml(thing));
} }
return vdiv; return vdiv;
case "checkbox": { case "checkbox": {
const div = document.createElement("div"); const div = document.createElement("div");
const checkbox = document.createElement("input"); const checkbox = document.createElement("input");
div.appendChild(checkbox); div.appendChild(checkbox);
const label = document.createElement("span"); const label = document.createElement("span");
checkbox.checked = array[2]; checkbox.checked = array[2];
label.textContent = array[1]; label.textContent = array[1];
div.appendChild(label); div.appendChild(label);
checkbox.addEventListener("change", array[3]); checkbox.addEventListener("change", array[3]);
checkbox.type = "checkbox"; checkbox.type = "checkbox";
return div; return div;
} }
case "button": { case "button": {
const div = document.createElement("div"); const div = document.createElement("div");
const input = document.createElement("button"); const input = document.createElement("button");
const label = document.createElement("span"); const label = document.createElement("span");
input.textContent = array[2]; input.textContent = array[2];
label.textContent = array[1]; label.textContent = array[1];
div.appendChild(label); div.appendChild(label);
div.appendChild(input); div.appendChild(input);
input.addEventListener("click", array[3]); input.addEventListener("click", array[3]);
return div; return div;
} }
case "mdbox": { case "mdbox": {
const div = document.createElement("div"); const div = document.createElement("div");
const input = document.createElement("textarea"); const input = document.createElement("textarea");
input.value = array[2]; input.value = array[2];
const label = document.createElement("span"); const label = document.createElement("span");
label.textContent = array[1]; label.textContent = array[1];
input.addEventListener("input", array[3]); input.addEventListener("input", array[3]);
div.appendChild(label); div.appendChild(label);
div.appendChild(document.createElement("br")); div.appendChild(document.createElement("br"));
div.appendChild(input); div.appendChild(input);
return div; return div;
} }
case "textbox": { case "textbox": {
const div = document.createElement("div"); const div = document.createElement("div");
const input = document.createElement("input"); const input = document.createElement("input");
input.value = array[2]; input.value = array[2];
input.type = "text"; input.type = "text";
const label = document.createElement("span"); const label = document.createElement("span");
label.textContent = array[1]; label.textContent = array[1];
console.log(array[3]); console.log(array[3]);
input.addEventListener("input", array[3]); input.addEventListener("input", array[3]);
div.appendChild(label); div.appendChild(label);
div.appendChild(input); div.appendChild(input);
return div; return div;
} }
case "fileupload": { case "fileupload": {
const div = document.createElement("div"); const div = document.createElement("div");
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
const label = document.createElement("span"); const label = document.createElement("span");
label.textContent = array[1]; label.textContent = array[1];
div.appendChild(label); div.appendChild(label);
div.appendChild(input); div.appendChild(input);
input.addEventListener("change", array[2]); input.addEventListener("change", array[2]);
console.log(array); console.log(array);
return div; return div;
} }
case "text": { case "text": {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = array[1]; span.textContent = array[1];
return span; return span;
} }
case "title": { case "title": {
const span = document.createElement("span"); const span = document.createElement("span");
span.classList.add("title"); span.classList.add("title");
span.textContent = array[1]; span.textContent = array[1];
return span; return span;
} }
case "radio": { case "radio": {
const div = document.createElement("div"); const div = document.createElement("div");
const fieldset = document.createElement("fieldset"); const fieldset = document.createElement("fieldset");
fieldset.addEventListener("change", () => { fieldset.addEventListener("change", () => {
let i = -1; let i = -1;
for (const thing of Array.from(fieldset.children)) { for (const thing of Array.from(fieldset.children)) {
i++; i++;
if (i === 0) { if (i === 0) {
continue; continue;
} }
const checkbox = thing.children[0].children[0] as HTMLInputElement; const checkbox = thing.children[0].children[0] as HTMLInputElement;
if (checkbox.checked) { if (checkbox.checked) {
array[3](checkbox.value); array[3](checkbox.value);
} }
} }
}); });
const legend = document.createElement("legend"); const legend = document.createElement("legend");
legend.textContent = array[1]; legend.textContent = array[1];
fieldset.appendChild(legend); fieldset.appendChild(legend);
let i = 0; let i = 0;
for (const thing of array[2]) { for (const thing of array[2]) {
const div = document.createElement("div"); const div = document.createElement("div");
const input = document.createElement("input"); const input = document.createElement("input");
input.classList.add("radio"); input.classList.add("radio");
input.type = "radio"; input.type = "radio";
input.name = array[1]; input.name = array[1];
input.value = thing; input.value = thing;
if (i === array[4]) { if (i === array[4]) {
input.checked = true; input.checked = true;
} }
const label = document.createElement("label"); const label = document.createElement("label");
label.appendChild(input); label.appendChild(input);
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = thing; span.textContent = thing;
label.appendChild(span); label.appendChild(span);
div.appendChild(label); div.appendChild(label);
fieldset.appendChild(div); fieldset.appendChild(div);
i++; i++;
} }
div.appendChild(fieldset); div.appendChild(fieldset);
return div; return div;
} }
case "html": case "html":
return array[1]; return array[1];
case "select": { case "select": {
const div = document.createElement("div"); const div = document.createElement("div");
const label = document.createElement("label"); const label = document.createElement("label");
const select = document.createElement("select"); const select = document.createElement("select");
label.textContent = array[1]; label.textContent = array[1];
div.append(label); div.append(label);
div.appendChild(select); div.appendChild(select);
for (const thing of array[2]) { for (const thing of array[2]) {
const option = document.createElement("option"); const option = document.createElement("option");
option.textContent = thing; option.textContent = thing;
select.appendChild(option); select.appendChild(option);
} }
select.selectedIndex = array[4]; select.selectedIndex = array[4];
select.addEventListener("change", array[3]); select.addEventListener("change", array[3]);
return div; return div;
} }
case "tabs": { case "tabs": {
const table = document.createElement("div"); const table = document.createElement("div");
table.classList.add("flexttb"); table.classList.add("flexttb");
const tabs = document.createElement("div"); const tabs = document.createElement("div");
tabs.classList.add("flexltr"); tabs.classList.add("flexltr");
tabs.classList.add("tabbed-head"); tabs.classList.add("tabbed-head");
table.appendChild(tabs); table.appendChild(tabs);
const content = document.createElement("div"); const content = document.createElement("div");
content.classList.add("tabbed-content"); content.classList.add("tabbed-content");
table.appendChild(content); table.appendChild(content);
let shown: HTMLElement | undefined; let shown: HTMLElement | undefined;
for (const thing of array[1]) { for (const thing of array[1]) {
const button = document.createElement("button"); const button = document.createElement("button");
button.textContent = thing[0]; button.textContent = thing[0];
tabs.appendChild(button); tabs.appendChild(button);
const html = this.tohtml(thing[1]); const html = this.tohtml(thing[1]);
content.append(html); content.append(html);
if (!shown) { if (!shown) {
shown = html; shown = html;
} else { } else {
html.style.display = "none"; html.style.display = "none";
} }
button.addEventListener("click", (_) => { button.addEventListener("click", (_) => {
if (shown) { if (shown) {
shown.style.display = "none"; shown.style.display = "none";
} }
html.style.display = ""; html.style.display = "";
shown = html; shown = html;
}); });
} }
return table; return table;
} }
default: default:
console.error( console.error(
"can't find element:" + array[0], "can't find element:" + array[0],
" full element:", " full element:",
array array
); );
return document.createElement("span"); return document.createElement("span");
} }
} }
show() { show() {
this.onopen(); this.onopen();
console.log("fullscreen"); console.log("fullscreen");
this.background = document.createElement("div"); this.background = document.createElement("div");
this.background.classList.add("background"); this.background.classList.add("background");
document.body.appendChild(this.background); document.body.appendChild(this.background);
document.body.appendChild(this.html); document.body.appendChild(this.html);
this.background.onclick = (_) => { this.background.onclick = (_) => {
this.hide(); this.hide();
}; };
} }
hide() { hide() {
document.body.removeChild(this.background); document.body.removeChild(this.background);
document.body.removeChild(this.html); document.body.removeChild(this.html);
} }
} }
export { Dialog }; export { Dialog };

View file

@ -4,75 +4,75 @@ import { Message } from "./message.js";
import { Localuser } from "./localuser.js"; import { Localuser } from "./localuser.js";
import { User } from "./user.js"; import { User } from "./user.js";
import { import {
channeljson, channeljson,
dirrectjson, dirrectjson,
memberjson, memberjson,
messagejson, messagejson,
} from "./jsontypes.js"; } from "./jsontypes.js";
import { Permissions } from "./permissions.js"; import { Permissions } from "./permissions.js";
import { SnowFlake } from "./snowflake.js"; import { SnowFlake } from "./snowflake.js";
import { Contextmenu } from "./contextmenu.js"; import { Contextmenu } from "./contextmenu.js";
class Direct extends Guild { class Direct extends Guild {
declare channelids: { [key: string]: Group }; declare channelids: { [key: string]: Group };
getUnixTime(): number { getUnixTime(): number {
throw new Error("Do not call this for Direct, it does not make sense"); 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); super(-1, owner, null);
this.message_notifications = 0; this.message_notifications = 0;
this.owner = owner; this.owner = owner;
if (!this.localuser) { if (!this.localuser) {
console.error("Owner was not included, please fix"); console.error("Owner was not included, please fix");
} }
this.headers = this.localuser.headers; this.headers = this.localuser.headers;
this.channels = []; this.channels = [];
this.channelids = {}; this.channelids = {};
// @ts-ignore // @ts-ignore
this.properties = {}; this.properties = {};
this.roles = []; this.roles = [];
this.roleids = new Map(); this.roleids = new Map();
this.prevchannel = undefined; this.prevchannel = undefined;
this.properties.name = "Direct Messages"; this.properties.name = "Direct Messages";
for (const thing of json) { for (const thing of json) {
const temp = new Group(thing, this); const temp = new Group(thing, this);
this.channels.push(temp); this.channels.push(temp);
this.channelids[temp.id] = temp; this.channelids[temp.id] = temp;
} }
this.headchannels = this.channels; this.headchannels = this.channels;
} }
createChannelpac(json: any) { createChannelpac(json: any) {
const thischannel = new Group(json, this); const thischannel = new Group(json, this);
this.channelids[thischannel.id] = thischannel; this.channelids[thischannel.id] = thischannel;
this.channels.push(thischannel); this.channels.push(thischannel);
this.sortchannels(); this.sortchannels();
this.printServers(); this.printServers();
return thischannel; return thischannel;
} }
delChannel(json: channeljson) { delChannel(json: channeljson) {
const channel = this.channelids[json.id]; const channel = this.channelids[json.id];
super.delChannel(json); super.delChannel(json);
if (channel) { if (channel) {
channel.del(); channel.del();
} }
} }
giveMember(_member: memberjson) { giveMember(_member: memberjson) {
console.error("not a real guild, can't give member object"); console.error("not a real guild, can't give member object");
} }
getRole(/* ID: string */) { getRole(/* ID: string */) {
return null; return null;
} }
hasRole(/* r: string */) { hasRole(/* r: string */) {
return false; return false;
} }
isAdmin() { isAdmin() {
return false; return false;
} }
unreaddms() { unreaddms() {
for (const thing of this.channels) { for (const thing of this.channels) {
(thing as Group).unreads(); (thing as Group).unreads();
} }
} }
} }
const dmPermissions = new Permissions("0"); const dmPermissions = new Permissions("0");
@ -100,207 +100,207 @@ dmPermissions.setPermission("USE_VAD", 1);
// @ts-ignore // @ts-ignore
class Group extends Channel { class Group extends Channel {
user: User; user: User;
static contextmenu = new Contextmenu<Group, undefined>("channel menu"); static contextmenu = new Contextmenu<Group, undefined>("channel menu");
static setupcontextmenu() { static setupcontextmenu() {
this.contextmenu.addbutton("Copy DM id", function (this: Group) { this.contextmenu.addbutton("Copy DM id", function (this: Group) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); });
this.contextmenu.addbutton("Mark as read", function (this: Group) { this.contextmenu.addbutton("Mark as read", function (this: Group) {
this.readbottom(); this.readbottom();
}); });
this.contextmenu.addbutton("Close DM", function (this: Group) { this.contextmenu.addbutton("Close DM", function (this: Group) {
this.deleteChannel(); this.deleteChannel();
}); });
this.contextmenu.addbutton("Copy user ID", function () { this.contextmenu.addbutton("Copy user ID", function () {
navigator.clipboard.writeText(this.user.id); navigator.clipboard.writeText(this.user.id);
}); });
} }
constructor(json: dirrectjson, owner: Direct) { constructor(json: dirrectjson, owner: Direct) {
super(-1, owner, json.id); super(-1, owner, json.id);
this.owner = owner; this.owner = owner;
this.headers = this.guild.headers; this.headers = this.guild.headers;
this.name = json.recipients[0]?.username; this.name = json.recipients[0]?.username;
if (json.recipients[0]) { if (json.recipients[0]) {
this.user = new User(json.recipients[0], this.localuser); this.user = new User(json.recipients[0], this.localuser);
} else { } else {
this.user = this.localuser.user; this.user = this.localuser.user;
} }
this.name ??= this.localuser.user.username; this.name ??= this.localuser.user.username;
this.parent_id!; this.parent_id!;
this.parent!; this.parent!;
this.children = []; this.children = [];
this.guild_id = "@me"; this.guild_id = "@me";
this.permission_overwrites = new Map(); this.permission_overwrites = new Map();
this.lastmessageid = json.last_message_id; this.lastmessageid = json.last_message_id;
this.mentions = 0; this.mentions = 0;
this.setUpInfiniteScroller(); this.setUpInfiniteScroller();
this.updatePosition(); this.updatePosition();
} }
updatePosition() { updatePosition() {
if (this.lastmessageid) { if (this.lastmessageid) {
this.position = SnowFlake.stringToUnixTime(this.lastmessageid); this.position = SnowFlake.stringToUnixTime(this.lastmessageid);
} else { } else {
this.position = 0; this.position = 0;
} }
this.position = -Math.max(this.position, this.getUnixTime()); this.position = -Math.max(this.position, this.getUnixTime());
} }
createguildHTML() { createguildHTML() {
const div = document.createElement("div"); const div = document.createElement("div");
Group.contextmenu.bindContextmenu(div, this, undefined); Group.contextmenu.bindContextmenu(div, this, undefined);
this.html = new WeakRef(div); this.html = new WeakRef(div);
div.classList.add("channeleffects"); div.classList.add("channeleffects");
const myhtml = document.createElement("span"); const myhtml = document.createElement("span");
myhtml.textContent = this.name; myhtml.textContent = this.name;
div.appendChild(this.user.buildpfp()); div.appendChild(this.user.buildpfp());
div.appendChild(myhtml); div.appendChild(myhtml);
(div as any)["myinfo"] = this; (div as any)["myinfo"] = this;
div.onclick = (_) => { div.onclick = (_) => {
this.getHTML(); this.getHTML();
}; };
return div; return div;
} }
async getHTML() { async getHTML() {
const id = ++Channel.genid; const id = ++Channel.genid;
if (this.localuser.channelfocus) { if (this.localuser.channelfocus) {
this.localuser.channelfocus.infinite.delete(); this.localuser.channelfocus.infinite.delete();
} }
if (this.guild !== this.localuser.lookingguild) { if (this.guild !== this.localuser.lookingguild) {
this.guild.loadGuild(); this.guild.loadGuild();
} }
this.guild.prevchannel = this; this.guild.prevchannel = this;
this.localuser.channelfocus = this; this.localuser.channelfocus = this;
const prom = this.infinite.delete(); const prom = this.infinite.delete();
history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id);
this.localuser.pageTitle("@" + this.name); this.localuser.pageTitle("@" + this.name);
(document.getElementById("channelTopic") as HTMLElement).setAttribute( (document.getElementById("channelTopic") as HTMLElement).setAttribute(
"hidden", "hidden",
"" ""
); );
const loading = document.getElementById("loadingdiv") as HTMLDivElement; const loading = document.getElementById("loadingdiv") as HTMLDivElement;
Channel.regenLoadingMessages(); Channel.regenLoadingMessages();
loading.classList.add("loading"); loading.classList.add("loading");
this.rendertyping(); this.rendertyping();
await this.putmessages(); await this.putmessages();
await prom; await prom;
if (id !== Channel.genid) { if (id !== Channel.genid) {
return; return;
} }
this.buildmessages(); this.buildmessages();
(document.getElementById("typebox") as HTMLDivElement).contentEditable = (document.getElementById("typebox") as HTMLDivElement).contentEditable =
"" + true; "" + true;
} }
messageCreate(messagep: { d: messagejson }) { messageCreate(messagep: { d: messagejson }) {
const messagez = new Message(messagep.d, this); const messagez = new Message(messagep.d, this);
if (this.lastmessageid) { if (this.lastmessageid) {
this.idToNext.set(this.lastmessageid, messagez.id); this.idToNext.set(this.lastmessageid, messagez.id);
this.idToPrev.set(messagez.id, this.lastmessageid); this.idToPrev.set(messagez.id, this.lastmessageid);
} }
this.lastmessageid = messagez.id; this.lastmessageid = messagez.id;
if (messagez.author === this.localuser.user) { if (messagez.author === this.localuser.user) {
this.lastreadmessageid = messagez.id; this.lastreadmessageid = messagez.id;
if (this.myhtml) { if (this.myhtml) {
this.myhtml.classList.remove("cunread"); this.myhtml.classList.remove("cunread");
} }
} else { } else {
if (this.myhtml) { if (this.myhtml) {
this.myhtml.classList.add("cunread"); this.myhtml.classList.add("cunread");
} }
} }
this.unreads(); this.unreads();
this.updatePosition(); this.updatePosition();
this.infinite.addedBottom(); this.infinite.addedBottom();
this.guild.sortchannels(); this.guild.sortchannels();
if (this.myhtml) { if (this.myhtml) {
const parrent = this.myhtml.parentElement as HTMLElement; const parrent = this.myhtml.parentElement as HTMLElement;
parrent.prepend(this.myhtml); parrent.prepend(this.myhtml);
} }
if (this === this.localuser.channelfocus) { if (this === this.localuser.channelfocus) {
if (!this.infinitefocus) { if (!this.infinitefocus) {
this.tryfocusinfinate(); this.tryfocusinfinate();
} }
this.infinite.addedBottom(); this.infinite.addedBottom();
} }
this.unreads(); this.unreads();
if (messagez.author === this.localuser.user) { if (messagez.author === this.localuser.user) {
return; return;
} }
if ( if (
this.localuser.lookingguild?.prevchannel === this && this.localuser.lookingguild?.prevchannel === this &&
document.hasFocus() document.hasFocus()
) { ) {
return; return;
} }
if (this.notification === "all") { if (this.notification === "all") {
this.notify(messagez); this.notify(messagez);
} else if ( } else if (
this.notification === "mentions" && this.notification === "mentions" &&
messagez.mentionsuser(this.localuser.user) messagez.mentionsuser(this.localuser.user)
) { ) {
this.notify(messagez); this.notify(messagez);
} }
} }
notititle(message: Message) { notititle(message: Message) {
return message.author.username; return message.author.username;
} }
readbottom() { readbottom() {
super.readbottom(); super.readbottom();
this.unreads(); this.unreads();
} }
all: WeakRef<HTMLElement> = new WeakRef(document.createElement("div")); all: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div")); noti: WeakRef<HTMLElement> = new WeakRef(document.createElement("div"));
del() { del() {
const all = this.all.deref(); const all = this.all.deref();
if (all) { if (all) {
all.remove(); all.remove();
} }
if (this.myhtml) { if (this.myhtml) {
this.myhtml.remove(); this.myhtml.remove();
} }
} }
unreads() { unreads() {
const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime
const current = this.all.deref(); const current = this.all.deref();
if (this.hasunreads) { if (this.hasunreads) {
{ {
const noti = this.noti.deref(); const noti = this.noti.deref();
if (noti) { if (noti) {
noti.textContent = this.mentions + ""; noti.textContent = this.mentions + "";
return; return;
} }
} }
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("servernoti"); div.classList.add("servernoti");
const noti = document.createElement("div"); const noti = document.createElement("div");
noti.classList.add("unread", "notiunread", "pinged"); noti.classList.add("unread", "notiunread", "pinged");
noti.textContent = "" + this.mentions; noti.textContent = "" + this.mentions;
this.noti = new WeakRef(noti); this.noti = new WeakRef(noti);
div.append(noti); div.append(noti);
const buildpfp = this.user.buildpfp(); const buildpfp = this.user.buildpfp();
this.all = new WeakRef(div); this.all = new WeakRef(div);
buildpfp.classList.add("mentioned"); buildpfp.classList.add("mentioned");
div.append(buildpfp); div.append(buildpfp);
sentdms.append(div); sentdms.append(div);
div.onclick = (_) => { div.onclick = (_) => {
this.guild.loadGuild(); this.guild.loadGuild();
this.getHTML(); this.getHTML();
}; };
} else if (current) { } else if (current) {
current.remove(); current.remove();
} else { } else {
} }
} }
isAdmin(): boolean { isAdmin(): boolean {
return false; return false;
} }
hasPermission(name: string): boolean { hasPermission(name: string): boolean {
return dmPermissions.hasPermission(name); return dmPermissions.hasPermission(name);
} }
} }
export { Direct, Group }; export { Direct, Group };
Group.setupcontextmenu(); Group.setupcontextmenu();

View file

@ -6,406 +6,406 @@ import { getapiurls, getInstances } from "./login.js";
import { Guild } from "./guild.js"; import { Guild } from "./guild.js";
class Embed { class Embed {
type: string; type: string;
owner: Message; owner: Message;
json: embedjson; json: embedjson;
constructor(json: embedjson, owner: Message) { constructor(json: embedjson, owner: Message) {
this.type = this.getType(json); this.type = this.getType(json);
this.owner = owner; this.owner = owner;
this.json = json; this.json = json;
} }
getType(json: embedjson) { getType(json: embedjson) {
const instances = getInstances(); const instances = getInstances();
if ( if (
instances && instances &&
json.type === "link" && json.type === "link" &&
json.url && json.url &&
URL.canParse(json.url) URL.canParse(json.url)
) { ) {
const Url = new URL(json.url); const Url = new URL(json.url);
for (const instance of instances) { for (const instance of instances) {
if (instance.url && URL.canParse(instance.url)) { if (instance.url && URL.canParse(instance.url)) {
const IUrl = new URL(instance.url); const IUrl = new URL(instance.url);
const params = new URLSearchParams(Url.search); const params = new URLSearchParams(Url.search);
let host: string; let host: string;
if (params.has("instance")) { if (params.has("instance")) {
const url = params.get("instance") as string; const url = params.get("instance") as string;
if (URL.canParse(url)) { if (URL.canParse(url)) {
host = new URL(url).host; host = new URL(url).host;
} else { } else {
host = Url.host; host = Url.host;
} }
} else { } else {
host = Url.host; host = Url.host;
} }
if (IUrl.host === host) { if (IUrl.host === host) {
const code = const code =
Url.pathname.split("/")[Url.pathname.split("/").length - 1]; Url.pathname.split("/")[Url.pathname.split("/").length - 1];
json.invite = { json.invite = {
url: instance.url, url: instance.url,
code, code,
}; };
return "invite"; return "invite";
} }
} }
} }
} }
return json.type || "rich"; return json.type || "rich";
} }
generateHTML() { generateHTML() {
switch (this.type) { switch (this.type) {
case "rich": case "rich":
return this.generateRich(); return this.generateRich();
case "image": case "image":
return this.generateImage(); return this.generateImage();
case "invite": case "invite":
return this.generateInvite(); return this.generateInvite();
case "link": case "link":
return this.generateLink(); return this.generateLink();
case "video": case "video":
case "article": case "article":
return this.generateArticle(); return this.generateArticle();
default: default:
console.warn( console.warn(
`unsupported embed type ${this.type}, please add support dev :3`, `unsupported embed type ${this.type}, please add support dev :3`,
this.json this.json
); );
return document.createElement("div"); //prevent errors by giving blank div return document.createElement("div"); //prevent errors by giving blank div
} }
} }
get message() { get message() {
return this.owner; return this.owner;
} }
get channel() { get channel() {
return this.message.channel; return this.message.channel;
} }
get guild() { get guild() {
return this.channel.guild; return this.channel.guild;
} }
get localuser() { get localuser() {
return this.guild.localuser; return this.guild.localuser;
} }
generateRich() { generateRich() {
const div = document.createElement("div"); const div = document.createElement("div");
if (this.json.color) { if (this.json.color) {
div.style.backgroundColor = "#" + this.json.color.toString(16); div.style.backgroundColor = "#" + this.json.color.toString(16);
} }
div.classList.add("embed-color"); div.classList.add("embed-color");
const embed = document.createElement("div"); const embed = document.createElement("div");
embed.classList.add("embed"); embed.classList.add("embed");
div.append(embed); div.append(embed);
if (this.json.author) { if (this.json.author) {
const authorline = document.createElement("div"); const authorline = document.createElement("div");
if (this.json.author.icon_url) { if (this.json.author.icon_url) {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("embedimg"); img.classList.add("embedimg");
img.src = this.json.author.icon_url; img.src = this.json.author.icon_url;
authorline.append(img); authorline.append(img);
} }
const a = document.createElement("a"); const a = document.createElement("a");
a.textContent = this.json.author.name as string; 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); MarkDown.safeLink(a, this.json.author.url);
} }
a.classList.add("username"); a.classList.add("username");
authorline.append(a); authorline.append(a);
embed.append(authorline); embed.append(authorline);
} }
if (this.json.title) { if (this.json.title) {
const title = document.createElement("a"); const title = document.createElement("a");
title.append(new MarkDown(this.json.title, this.channel).makeHTML()); title.append(new MarkDown(this.json.title, this.channel).makeHTML());
if (this.json.url) { if (this.json.url) {
MarkDown.safeLink(title, this.json.url); MarkDown.safeLink(title, this.json.url);
} }
title.classList.add("embedtitle"); title.classList.add("embedtitle");
embed.append(title); embed.append(title);
} }
if (this.json.description) { if (this.json.description) {
const p = document.createElement("p"); const p = document.createElement("p");
p.append(new MarkDown(this.json.description, this.channel).makeHTML()); p.append(new MarkDown(this.json.description, this.channel).makeHTML());
embed.append(p); embed.append(p);
} }
embed.append(document.createElement("br")); embed.append(document.createElement("br"));
if (this.json.fields) { if (this.json.fields) {
for (const thing of this.json.fields) { for (const thing of this.json.fields) {
const div = document.createElement("div"); const div = document.createElement("div");
const b = document.createElement("b"); const b = document.createElement("b");
b.textContent = thing.name; b.textContent = thing.name;
div.append(b); div.append(b);
const p = document.createElement("p"); const p = document.createElement("p");
p.append(new MarkDown(thing.value, this.channel).makeHTML()); p.append(new MarkDown(thing.value, this.channel).makeHTML());
p.classList.add("embedp"); p.classList.add("embedp");
div.append(p); div.append(p);
if (thing.inline) { if (thing.inline) {
div.classList.add("inline"); div.classList.add("inline");
} }
embed.append(div); embed.append(div);
} }
} }
if (this.json.footer || this.json.timestamp) { if (this.json.footer || this.json.timestamp) {
const footer = document.createElement("div"); const footer = document.createElement("div");
if (this.json?.footer?.icon_url) { if (this.json?.footer?.icon_url) {
const img = document.createElement("img"); const img = document.createElement("img");
img.src = this.json.footer.icon_url; img.src = this.json.footer.icon_url;
img.classList.add("embedicon"); img.classList.add("embedicon");
footer.append(img); footer.append(img);
} }
if (this.json?.footer?.text) { if (this.json?.footer?.text) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = this.json.footer.text; span.textContent = this.json.footer.text;
span.classList.add("spaceright"); span.classList.add("spaceright");
footer.append(span); footer.append(span);
} }
if (this.json?.footer && this.json?.timestamp) { if (this.json?.footer && this.json?.timestamp) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = "•"; span.textContent = "•";
span.classList.add("spaceright"); span.classList.add("spaceright");
footer.append(span); footer.append(span);
} }
if (this.json?.timestamp) { if (this.json?.timestamp) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = new Date(this.json.timestamp).toLocaleString(); span.textContent = new Date(this.json.timestamp).toLocaleString();
footer.append(span); footer.append(span);
} }
embed.append(footer); embed.append(footer);
} }
return div; return div;
} }
generateImage() { generateImage() {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("messageimg"); img.classList.add("messageimg");
img.onclick = function () { img.onclick = function () {
const full = new Dialog(["img", img.src, ["fit"]]); const full = new Dialog(["img", img.src, ["fit"]]);
full.show(); full.show();
}; };
img.src = this.json.thumbnail.proxy_url; img.src = this.json.thumbnail.proxy_url;
if (this.json.thumbnail.width) { if (this.json.thumbnail.width) {
let scale = 1; let scale = 1;
const max = 96 * 3; const max = 96 * 3;
scale = Math.max(scale, this.json.thumbnail.width / max); scale = Math.max(scale, this.json.thumbnail.width / max);
scale = Math.max(scale, this.json.thumbnail.height / max); scale = Math.max(scale, this.json.thumbnail.height / max);
this.json.thumbnail.width /= scale; this.json.thumbnail.width /= scale;
this.json.thumbnail.height /= scale; this.json.thumbnail.height /= scale;
} }
img.style.width = this.json.thumbnail.width + "px"; img.style.width = this.json.thumbnail.width + "px";
img.style.height = this.json.thumbnail.height + "px"; img.style.height = this.json.thumbnail.height + "px";
console.log(this.json, "Image fix"); console.log(this.json, "Image fix");
return img; return img;
} }
generateLink() { generateLink() {
const table = document.createElement("table"); const table = document.createElement("table");
table.classList.add("embed", "linkembed"); table.classList.add("embed", "linkembed");
const trtop = document.createElement("tr"); const trtop = document.createElement("tr");
table.append(trtop); table.append(trtop);
if (this.json.url && this.json.title) { if (this.json.url && this.json.title) {
const td = document.createElement("td"); const td = document.createElement("td");
const a = document.createElement("a"); const a = document.createElement("a");
MarkDown.safeLink(a, this.json.url); MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.title; a.textContent = this.json.title;
td.append(a); td.append(a);
trtop.append(td); trtop.append(td);
} }
{ {
const td = document.createElement("td"); const td = document.createElement("td");
const img = document.createElement("img"); const img = document.createElement("img");
if (this.json.thumbnail) { if (this.json.thumbnail) {
img.classList.add("embedimg"); img.classList.add("embedimg");
img.onclick = function () { img.onclick = function () {
const full = new Dialog(["img", img.src, ["fit"]]); const full = new Dialog(["img", img.src, ["fit"]]);
full.show(); full.show();
}; };
img.src = this.json.thumbnail.proxy_url; img.src = this.json.thumbnail.proxy_url;
td.append(img); td.append(img);
} }
trtop.append(td); trtop.append(td);
} }
const bottomtr = document.createElement("tr"); const bottomtr = document.createElement("tr");
const td = document.createElement("td"); const td = document.createElement("td");
if (this.json.description) { if (this.json.description) {
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = this.json.description; span.textContent = this.json.description;
td.append(span); td.append(span);
} }
bottomtr.append(td); bottomtr.append(td);
table.append(bottomtr); table.append(bottomtr);
return table; return table;
} }
invcache: [invitejson, { cdn: string; api: string }] | undefined; invcache: [invitejson, { cdn: string; api: string }] | undefined;
generateInvite() { generateInvite() {
if (this.invcache && (!this.json.invite || !this.localuser)) { if (this.invcache && (!this.json.invite || !this.localuser)) {
return this.generateLink(); return this.generateLink();
} }
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("embed", "inviteEmbed", "flexttb"); div.classList.add("embed", "inviteEmbed", "flexttb");
const json1 = this.json.invite; const json1 = this.json.invite;
(async () => { (async () => {
let json: invitejson; let json: invitejson;
let info: { cdn: string; api: string }; let info: { cdn: string; api: string };
if (!this.invcache) { if (!this.invcache) {
if (!json1) { if (!json1) {
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
const tempinfo = await getapiurls(json1.url); const tempinfo = await getapiurls(json1.url);
if (!tempinfo) { if (!tempinfo) {
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
info = tempinfo; info = tempinfo;
const res = await fetch(info.api + "/invites/" + json1.code); const res = await fetch(info.api + "/invites/" + json1.code);
if (!res.ok) { if (!res.ok) {
div.append(this.generateLink()); div.append(this.generateLink());
} }
json = (await res.json()) as invitejson; json = (await res.json()) as invitejson;
this.invcache = [json, info]; this.invcache = [json, info];
} else { } else {
[json, info] = this.invcache; [json, info] = this.invcache;
} }
if (!json) { if (!json) {
div.append(this.generateLink()); div.append(this.generateLink());
return; return;
} }
if (json.guild.banner) { if (json.guild.banner) {
const banner = document.createElement("img"); const banner = document.createElement("img");
banner.src = banner.src =
this.localuser.info.cdn + this.localuser.info.cdn +
"/icons/" + "/icons/" +
json.guild.id + json.guild.id +
"/" + "/" +
json.guild.banner + json.guild.banner +
".png?size=256"; ".png?size=256";
banner.classList.add("banner"); banner.classList.add("banner");
div.append(banner); div.append(banner);
} }
const guild: invitejson["guild"] & { info?: { cdn: string } } = const guild: invitejson["guild"] & { info?: { cdn: string } } =
json.guild; json.guild;
guild.info = info; guild.info = info;
const icon = Guild.generateGuildIcon( const icon = Guild.generateGuildIcon(
guild as invitejson["guild"] & { info: { cdn: string } } guild as invitejson["guild"] & { info: { cdn: string } }
); );
const iconrow = document.createElement("div"); const iconrow = document.createElement("div");
iconrow.classList.add("flexltr", "flexstart"); iconrow.classList.add("flexltr", "flexstart");
iconrow.append(icon); iconrow.append(icon);
{ {
const guildinfo = document.createElement("div"); const guildinfo = document.createElement("div");
guildinfo.classList.add("flexttb", "invguildinfo"); guildinfo.classList.add("flexttb", "invguildinfo");
const name = document.createElement("b"); const name = document.createElement("b");
name.textContent = guild.name; name.textContent = guild.name;
guildinfo.append(name); guildinfo.append(name);
const members = document.createElement("span"); const members = document.createElement("span");
members.innerText = members.innerText =
"#" + json.channel.name + " • Members: " + guild.member_count; "#" + json.channel.name + " • Members: " + guild.member_count;
guildinfo.append(members); guildinfo.append(members);
members.classList.add("subtext"); members.classList.add("subtext");
iconrow.append(guildinfo); iconrow.append(guildinfo);
} }
div.append(iconrow); div.append(iconrow);
const h2 = document.createElement("h2"); const h2 = document.createElement("h2");
h2.textContent = `You've been invited by ${json.inviter.username}`; h2.textContent = `You've been invited by ${json.inviter.username}`;
div.append(h2); div.append(h2);
const button = document.createElement("button"); const button = document.createElement("button");
button.textContent = "Accept"; button.textContent = "Accept";
if (this.localuser.info.api.startsWith(info.api)) { if (this.localuser.info.api.startsWith(info.api)) {
if (this.localuser.guildids.has(guild.id)) { if (this.localuser.guildids.has(guild.id)) {
button.textContent = "Already joined"; button.textContent = "Already joined";
button.disabled = true; button.disabled = true;
} }
} }
button.classList.add("acceptinvbutton"); button.classList.add("acceptinvbutton");
div.append(button); div.append(button);
button.onclick = (_) => { button.onclick = (_) => {
if (this.localuser.info.api.startsWith(info.api)) { if (this.localuser.info.api.startsWith(info.api)) {
fetch(this.localuser.info.api + "/invites/" + json.code, { fetch(this.localuser.info.api + "/invites/" + json.code, {
method: "POST", method: "POST",
headers: this.localuser.headers, headers: this.localuser.headers,
}) })
.then((r) => r.json()) .then((r) => r.json())
.then((_) => { .then((_) => {
if (_.message) { if (_.message) {
alert(_.message); alert(_.message);
} }
}); });
} else { } else {
if (this.json.invite) { if (this.json.invite) {
const params = new URLSearchParams(""); const params = new URLSearchParams("");
params.set("instance", this.json.invite.url); params.set("instance", this.json.invite.url);
const encoded = params.toString(); const encoded = params.toString();
const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`; const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`;
window.open(url, "_blank"); window.open(url, "_blank");
} }
} }
}; };
})(); })();
return div; return div;
} }
generateArticle() { generateArticle() {
const colordiv = document.createElement("div"); const colordiv = document.createElement("div");
colordiv.style.backgroundColor = "#000000"; colordiv.style.backgroundColor = "#000000";
colordiv.classList.add("embed-color"); colordiv.classList.add("embed-color");
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("embed"); div.classList.add("embed");
if (this.json.provider) { if (this.json.provider) {
const provider = document.createElement("p"); const provider = document.createElement("p");
provider.classList.add("provider"); provider.classList.add("provider");
provider.textContent = this.json.provider.name; provider.textContent = this.json.provider.name;
div.append(provider); div.append(provider);
} }
const a = document.createElement("a"); 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); MarkDown.safeLink(a, this.json.url);
a.textContent = this.json.url; a.textContent = this.json.url;
div.append(a); div.append(a);
} }
if (this.json.description) { if (this.json.description) {
const description = document.createElement("p"); const description = document.createElement("p");
description.textContent = this.json.description; description.textContent = this.json.description;
div.append(description); div.append(description);
} }
if (this.json.thumbnail) { if (this.json.thumbnail) {
const img = document.createElement("img"); 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; let scale = 1;
const inch = 96; const inch = 96;
scale = Math.max(scale, this.json.thumbnail.width / inch / 4); scale = Math.max(scale, this.json.thumbnail.width / inch / 4);
scale = Math.max(scale, this.json.thumbnail.height / inch / 3); scale = Math.max(scale, this.json.thumbnail.height / inch / 3);
this.json.thumbnail.width /= scale; this.json.thumbnail.width /= scale;
this.json.thumbnail.height /= scale; this.json.thumbnail.height /= scale;
img.style.width = this.json.thumbnail.width + "px"; img.style.width = this.json.thumbnail.width + "px";
img.style.height = this.json.thumbnail.height + "px"; img.style.height = this.json.thumbnail.height + "px";
} }
img.classList.add("bigembedimg"); img.classList.add("bigembedimg");
if (this.json.video) { if (this.json.video) {
img.onclick = async () => { img.onclick = async () => {
if (this.json.video) { if (this.json.video) {
img.remove(); img.remove();
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.src = this.json.video.url + "?autoplay=1"; 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.width = this.json.thumbnail.width + "px";
iframe.style.height = this.json.thumbnail.height + "px"; iframe.style.height = this.json.thumbnail.height + "px";
} }
div.append(iframe); div.append(iframe);
} }
}; };
} else { } else {
img.onclick = async () => { img.onclick = async () => {
const full = new Dialog(["img", img.src, ["fit"]]); const full = new Dialog(["img", img.src, ["fit"]]);
full.show(); full.show();
}; };
} }
img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url; img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url;
div.append(img); div.append(img);
} }
colordiv.append(div); colordiv.append(div);
return colordiv; return colordiv;
} }
} }
export { Embed }; export { Embed };

View file

@ -3,257 +3,257 @@ import { Guild } from "./guild.js";
import { Localuser } from "./localuser.js"; import { Localuser } from "./localuser.js";
class Emoji { class Emoji {
static emojis: { static emojis: {
name: string; name: string;
emojis: { emojis: {
name: string; name: string;
emoji: string; emoji: string;
}[]; }[];
}[]; }[];
name: string; name: string;
id: string; id: string;
animated: boolean; animated: boolean;
owner: Guild | Localuser; owner: Guild | Localuser;
get guild() { get guild() {
if (this.owner instanceof Guild) { if (this.owner instanceof Guild) {
return this.owner; return this.owner;
}
return;
}
get localuser() {
if (this.owner instanceof Guild) {
return this.owner.localuser;
} else {
return this.owner;
}
}
get info() {
return this.owner.info;
}
constructor(
json: { name: string; id: string; animated: boolean },
owner: Guild | Localuser
) {
this.name = json.name;
this.id = json.id;
this.animated = json.animated;
this.owner = owner;
}
getHTML(bigemoji: boolean = false) {
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.alt = this.name;
emojiElem.loading = "lazy";
return emojiElem;
}
static decodeEmojiList(buffer: ArrayBuffer) {
const view = new DataView(buffer, 0);
let i = 0;
function read16() {
const int = view.getUint16(i);
i += 2;
return int;
}
function read8() {
const int = view.getUint8(i);
i += 1;
return int;
}
function readString8() {
return readStringNo(read8());
}
function readString16() {
return readStringNo(read16());
}
function readStringNo(length: number) {
const array = new Uint8Array(length);
for (let i = 0; i < length; i++) {
array[i] = read8();
}
//console.log(array);
return new TextDecoder("utf-8").decode(array.buffer);
}
const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
[];
let cats = read16();
for (; cats !== 0; cats--) {
const name = readString16();
const emojis: {
name: string;
skin_tone_support: boolean;
emoji: string;
}[] = [];
let emojinumber = read16();
for (; emojinumber !== 0; emojinumber--) {
//console.log(emojis);
const name = readString8();
const len = read8();
const skin_tone_support = len > 127;
const emoji = readStringNo(len - Number(skin_tone_support) * 128);
emojis.push({
name,
skin_tone_support,
emoji,
});
}
build.push({
name,
emojis,
});
}
this.emojis = build;
console.log(build);
}
static grabEmoji() {
fetch("/emoji.bin")
.then((e) => {
return e.arrayBuffer();
})
.then((e) => {
Emoji.decodeEmojiList(e);
});
}
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) => {
res = r;
});
const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
menu.style.top = y + "px";
menu.style.left = x + "px";
const title = document.createElement("h2");
title.textContent = Emoji.emojis[0].name;
title.classList.add("emojiTitle");
menu.append(title);
const selection = document.createElement("div");
selection.classList.add("flexltr", "dontshrink", "emojirow");
const body = document.createElement("div");
body.classList.add("emojiBody");
let isFirst = true;
localuser.guilds
.filter((guild) => guild.id != "@me" && guild.emojis.length > 0)
.forEach((guild) => {
const select = document.createElement("div");
select.classList.add("emojiSelect");
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.alt = "Server: " + guild.properties.name;
select.appendChild(img);
} else {
const div = document.createElement("span");
div.textContent = guild.properties.name
.replace(/'s /g, " ")
.replace(/\w+/g, (word) => word[0])
.replace(/\s/g, "");
select.append(div);
}
selection.append(select);
const clickEvent = () => {
title.textContent = guild.properties.name;
body.innerHTML = "";
for (const emojit of guild.emojis) {
const emojiElem = document.createElement("div");
emojiElem.classList.add("emojiSelect");
const emojiClass = new Emoji(
{
id: emojit.id as string,
name: emojit.name,
animated: emojit.animated as boolean,
},
localuser
);
emojiElem.append(emojiClass.getHTML());
body.append(emojiElem);
emojiElem.addEventListener("click", () => {
res(emojiClass);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
});
}
};
select.addEventListener("click", clickEvent);
if (isFirst) {
clickEvent();
isFirst = false;
}
});
setTimeout(() => {
if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove();
}
document.body.append(menu);
Contextmenu.currentmenu = menu;
Contextmenu.keepOnScreen(menu);
}, 10);
let i = 0;
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 = () => {
title.textContent = thing.name;
body.innerHTML = "";
for (const emojit of thing.emojis) {
const emoji = document.createElement("div");
emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji;
body.append(emoji);
emoji.onclick = (_) => {
res(emojit.emoji);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
};
}
};
select.onclick = clickEvent;
if (i === 0) {
clickEvent();
}
i++;
}
menu.append(selection);
menu.append(body);
return promise;
}
} }
Emoji.grabEmoji(); return;
export { Emoji }; }
get localuser() {
if (this.owner instanceof Guild) {
return this.owner.localuser;
} else {
return this.owner;
}
}
get info() {
return this.owner.info;
}
constructor(
json: { name: string; id: string; animated: boolean },
owner: Guild | Localuser
) {
this.name = json.name;
this.id = json.id;
this.animated = json.animated;
this.owner = owner;
}
getHTML(bigemoji: boolean = false) {
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.alt = this.name;
emojiElem.loading = "lazy";
return emojiElem;
}
static decodeEmojiList(buffer: ArrayBuffer) {
const view = new DataView(buffer, 0);
let i = 0;
function read16() {
const int = view.getUint16(i);
i += 2;
return int;
}
function read8() {
const int = view.getUint8(i);
i += 1;
return int;
}
function readString8() {
return readStringNo(read8());
}
function readString16() {
return readStringNo(read16());
}
function readStringNo(length: number) {
const array = new Uint8Array(length);
for (let i = 0; i < length; i++) {
array[i] = read8();
}
//console.log(array);
return new TextDecoder("utf-8").decode(array.buffer);
}
const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
[];
let cats = read16();
for (; cats !== 0; cats--) {
const name = readString16();
const emojis: {
name: string;
skin_tone_support: boolean;
emoji: string;
}[] = [];
let emojinumber = read16();
for (; emojinumber !== 0; emojinumber--) {
//console.log(emojis);
const name = readString8();
const len = read8();
const skin_tone_support = len > 127;
const emoji = readStringNo(len - Number(skin_tone_support) * 128);
emojis.push({
name,
skin_tone_support,
emoji,
});
}
build.push({
name,
emojis,
});
}
this.emojis = build;
console.log(build);
}
static grabEmoji() {
fetch("/emoji.bin")
.then((e) => {
return e.arrayBuffer();
})
.then((e) => {
Emoji.decodeEmojiList(e);
});
}
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) => {
res = r;
});
const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
menu.style.top = y + "px";
menu.style.left = x + "px";
const title = document.createElement("h2");
title.textContent = Emoji.emojis[0].name;
title.classList.add("emojiTitle");
menu.append(title);
const selection = document.createElement("div");
selection.classList.add("flexltr", "dontshrink", "emojirow");
const body = document.createElement("div");
body.classList.add("emojiBody");
let isFirst = true;
localuser.guilds
.filter((guild) => guild.id != "@me" && guild.emojis.length > 0)
.forEach((guild) => {
const select = document.createElement("div");
select.classList.add("emojiSelect");
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.alt = "Server: " + guild.properties.name;
select.appendChild(img);
} else {
const div = document.createElement("span");
div.textContent = guild.properties.name
.replace(/'s /g, " ")
.replace(/\w+/g, (word) => word[0])
.replace(/\s/g, "");
select.append(div);
}
selection.append(select);
const clickEvent = () => {
title.textContent = guild.properties.name;
body.innerHTML = "";
for (const emojit of guild.emojis) {
const emojiElem = document.createElement("div");
emojiElem.classList.add("emojiSelect");
const emojiClass = new Emoji(
{
id: emojit.id as string,
name: emojit.name,
animated: emojit.animated as boolean,
},
localuser
);
emojiElem.append(emojiClass.getHTML());
body.append(emojiElem);
emojiElem.addEventListener("click", () => {
res(emojiClass);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
});
}
};
select.addEventListener("click", clickEvent);
if (isFirst) {
clickEvent();
isFirst = false;
}
});
setTimeout(() => {
if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove();
}
document.body.append(menu);
Contextmenu.currentmenu = menu;
Contextmenu.keepOnScreen(menu);
}, 10);
let i = 0;
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 = () => {
title.textContent = thing.name;
body.innerHTML = "";
for (const emojit of thing.emojis) {
const emoji = document.createElement("div");
emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji;
body.append(emoji);
emoji.onclick = (_) => {
res(emojit.emoji);
if (Contextmenu.currentmenu !== "") {
Contextmenu.currentmenu.remove();
}
};
}
};
select.onclick = clickEvent;
if (i === 0) {
clickEvent();
}
i++;
}
menu.append(selection);
menu.append(body);
return promise;
}
}
Emoji.grabEmoji();
export { Emoji };

View file

@ -3,150 +3,150 @@ import { Dialog } from "./dialog.js";
import { filejson } from "./jsontypes.js"; import { filejson } from "./jsontypes.js";
class File { class File {
owner: Message | null; owner: Message | null;
id: string; id: string;
filename: string; filename: string;
content_type: string; content_type: string;
width: number | undefined; width: number | undefined;
height: number | undefined; height: number | undefined;
proxy_url: string | undefined; proxy_url: string | undefined;
url: string; url: string;
size: number; size: number;
constructor(fileJSON: filejson, owner: Message | null) { constructor(fileJSON: filejson, owner: Message | null) {
this.owner = owner; this.owner = owner;
this.id = fileJSON.id; this.id = fileJSON.id;
this.filename = fileJSON.filename; this.filename = fileJSON.filename;
this.content_type = fileJSON.content_type; this.content_type = fileJSON.content_type;
this.width = fileJSON.width; this.width = fileJSON.width;
this.height = fileJSON.height; this.height = fileJSON.height;
this.url = fileJSON.url; this.url = fileJSON.url;
this.proxy_url = fileJSON.proxy_url; this.proxy_url = fileJSON.proxy_url;
this.content_type = fileJSON.content_type; this.content_type = fileJSON.content_type;
this.size = fileJSON.size; this.size = fileJSON.size;
} }
getHTML(temp: boolean = false): HTMLElement { getHTML(temp: boolean = false): HTMLElement {
const src = this.proxy_url || this.url; const src = this.proxy_url || this.url;
if (this.width && this.height) { if (this.width && this.height) {
let scale = 1; let scale = 1;
const max = 96 * 3; const max = 96 * 3;
scale = Math.max(scale, this.width / max); scale = Math.max(scale, this.width / max);
scale = Math.max(scale, this.height / max); scale = Math.max(scale, this.height / max);
this.width /= scale; this.width /= scale;
this.height /= scale; this.height /= scale;
} }
if (this.content_type.startsWith("image/")) { if (this.content_type.startsWith("image/")) {
const div = document.createElement("div"); const div = document.createElement("div");
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("messageimg"); img.classList.add("messageimg");
div.classList.add("messageimgdiv"); div.classList.add("messageimgdiv");
img.onclick = function () { img.onclick = function () {
const full = new Dialog(["img", img.src, ["fit"]]); const full = new Dialog(["img", img.src, ["fit"]]);
full.show(); full.show();
}; };
img.src = src; img.src = src;
div.append(img); div.append(img);
if (this.width) { if (this.width) {
div.style.width = this.width + "px"; div.style.width = this.width + "px";
div.style.height = this.height + "px"; div.style.height = this.height + "px";
} }
console.log(img); console.log(img);
console.log(this.width, this.height); console.log(this.width, this.height);
return div; return div;
} else if (this.content_type.startsWith("video/")) { } else if (this.content_type.startsWith("video/")) {
const video = document.createElement("video"); const video = document.createElement("video");
const source = document.createElement("source"); const source = document.createElement("source");
source.src = src; source.src = src;
video.append(source); video.append(source);
source.type = this.content_type; source.type = this.content_type;
video.controls = !temp; video.controls = !temp;
if (this.width && this.height) { if (this.width && this.height) {
video.width = this.width; video.width = this.width;
video.height = this.height; video.height = this.height;
} }
return video; return video;
} else if (this.content_type.startsWith("audio/")) { } else if (this.content_type.startsWith("audio/")) {
const audio = document.createElement("audio"); const audio = document.createElement("audio");
const source = document.createElement("source"); const source = document.createElement("source");
source.src = src; source.src = src;
audio.append(source); audio.append(source);
source.type = this.content_type; source.type = this.content_type;
audio.controls = !temp; audio.controls = !temp;
return audio; return audio;
} else { } else {
return this.createunknown(); return this.createunknown();
} }
} }
upHTML(files: Blob[], file: globalThis.File): HTMLElement { upHTML(files: Blob[], file: globalThis.File): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
const contained = this.getHTML(true); const contained = this.getHTML(true);
div.classList.add("containedFile"); div.classList.add("containedFile");
div.append(contained); div.append(contained);
const controls = document.createElement("div"); const controls = document.createElement("div");
const garbage = document.createElement("button"); const garbage = document.createElement("button");
garbage.textContent = "🗑"; garbage.textContent = "🗑";
garbage.onclick = (_) => { garbage.onclick = (_) => {
div.remove(); div.remove();
files.splice(files.indexOf(file), 1); files.splice(files.indexOf(file), 1);
}; };
controls.classList.add("controls"); controls.classList.add("controls");
div.append(controls); div.append(controls);
controls.append(garbage); controls.append(garbage);
return div; return div;
} }
static initFromBlob(file: globalThis.File) { static initFromBlob(file: globalThis.File) {
return new File( return new File(
{ {
filename: file.name, filename: file.name,
size: file.size, size: file.size,
id: "null", id: "null",
content_type: file.type, content_type: file.type,
width: undefined, width: undefined,
height: undefined, height: undefined,
url: URL.createObjectURL(file), url: URL.createObjectURL(file),
proxy_url: undefined, proxy_url: undefined,
}, },
null null
); );
} }
createunknown(): HTMLElement { createunknown(): HTMLElement {
console.log("🗎"); console.log("🗎");
const src = this.proxy_url || this.url; const src = this.proxy_url || this.url;
const div = document.createElement("table"); const div = document.createElement("table");
div.classList.add("unknownfile"); div.classList.add("unknownfile");
const nametr = document.createElement("tr"); const nametr = document.createElement("tr");
div.append(nametr); div.append(nametr);
const fileicon = document.createElement("td"); const fileicon = document.createElement("td");
nametr.append(fileicon); nametr.append(fileicon);
fileicon.append("🗎"); fileicon.append("🗎");
fileicon.classList.add("fileicon"); fileicon.classList.add("fileicon");
fileicon.rowSpan = 2; fileicon.rowSpan = 2;
const nametd = document.createElement("td"); const nametd = document.createElement("td");
if (src) { if (src) {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = src; a.href = src;
a.textContent = this.filename; a.textContent = this.filename;
nametd.append(a); nametd.append(a);
} else { } else {
nametd.textContent = this.filename; nametd.textContent = this.filename;
} }
nametd.classList.add("filename"); nametd.classList.add("filename");
nametr.append(nametd); nametr.append(nametd);
const sizetr = document.createElement("tr"); const sizetr = document.createElement("tr");
const size = document.createElement("td"); const size = document.createElement("td");
sizetr.append(size); sizetr.append(size);
size.textContent = "Size:" + File.filesizehuman(this.size); size.textContent = "Size:" + File.filesizehuman(this.size);
size.classList.add("filesize"); size.classList.add("filesize");
div.appendChild(sizetr); div.appendChild(sizetr);
return div; return div;
} }
static filesizehuman(fsize: number) { static filesizehuman(fsize: number) {
const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024));
return ( return (
Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 +
" " + " " +
["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i]
); );
} }
} }
export { File }; export { File };

File diff suppressed because it is too large Load diff

View file

@ -3,87 +3,87 @@ console.log(mobile);
const serverbox = document.getElementById("instancebox") as HTMLDivElement; const serverbox = document.getElementById("instancebox") as HTMLDivElement;
fetch("/instances.json") fetch("/instances.json")
.then((_) => _.json()) .then((_) => _.json())
.then( .then(
( (
json: { json: {
name: string; name: string;
description?: string; description?: string;
descriptionLong?: string; descriptionLong?: string;
image?: string; image?: string;
url?: string; url?: string;
display?: boolean; display?: boolean;
online?: boolean; online?: boolean;
uptime: { alltime: number; daytime: number; weektime: number }; uptime: { alltime: number; daytime: number; weektime: number };
urls: { urls: {
wellknown: string; wellknown: string;
api: string; api: string;
cdn: string; cdn: string;
gateway: string; gateway: string;
login?: string; login?: string;
}; };
}[] }[]
) => { ) => {
console.warn(json); console.warn(json);
for (const instance of json) { for (const instance of json) {
if (instance.display === false) { if (instance.display === false) {
continue; continue;
} }
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("flexltr", "instance"); div.classList.add("flexltr", "instance");
if (instance.image) { if (instance.image) {
const img = document.createElement("img"); const img = document.createElement("img");
img.src = instance.image; img.src = instance.image;
div.append(img); div.append(img);
} }
const statbox = document.createElement("div"); const statbox = document.createElement("div");
statbox.classList.add("flexttb"); statbox.classList.add("flexttb");
{ {
const textbox = document.createElement("div"); const textbox = document.createElement("div");
textbox.classList.add("flexttb", "instatancetextbox"); textbox.classList.add("flexttb", "instatancetextbox");
const title = document.createElement("h2"); const title = document.createElement("h2");
title.innerText = instance.name; title.innerText = instance.name;
if (instance.online !== undefined) { if (instance.online !== undefined) {
const status = document.createElement("span"); const status = document.createElement("span");
status.innerText = instance.online ? "Online" : "Offline"; status.innerText = instance.online ? "Online" : "Offline";
status.classList.add("instanceStatus"); status.classList.add("instanceStatus");
title.append(status); title.append(status);
} }
textbox.append(title); textbox.append(title);
if (instance.description || instance.descriptionLong) { if (instance.description || instance.descriptionLong) {
const p = document.createElement("p"); const p = document.createElement("p");
if (instance.descriptionLong) { if (instance.descriptionLong) {
p.innerText = instance.descriptionLong; p.innerText = instance.descriptionLong;
} else if (instance.description) { } else if (instance.description) {
p.innerText = instance.description; p.innerText = instance.description;
} }
textbox.append(p); textbox.append(p);
} }
statbox.append(textbox); statbox.append(textbox);
} }
if (instance.uptime) { if (instance.uptime) {
const stats = document.createElement("div"); const stats = document.createElement("div");
stats.classList.add("flexltr"); stats.classList.add("flexltr");
const span = document.createElement("span"); const span = document.createElement("span");
span.innerText = `Uptime: All time: ${Math.round( span.innerText = `Uptime: All time: ${Math.round(
instance.uptime.alltime * 100 instance.uptime.alltime * 100
)}% This week: ${Math.round( )}% This week: ${Math.round(
instance.uptime.weektime * 100 instance.uptime.weektime * 100
)}% Today: ${Math.round(instance.uptime.daytime * 100)}%`; )}% Today: ${Math.round(instance.uptime.daytime * 100)}%`;
stats.append(span); stats.append(span);
statbox.append(stats); statbox.append(stats);
} }
div.append(statbox); div.append(statbox);
div.onclick = (_) => { div.onclick = (_) => {
if (instance.online) { if (instance.online) {
window.location.href = window.location.href =
"/register.html?instance=" + encodeURI(instance.name); "/register.html?instance=" + encodeURI(instance.name);
} else { } else {
alert("Instance is offline, can't connect"); alert("Instance is offline, can't connect");
} }
}; };
serverbox.append(div); serverbox.append(div);
} }
} }
); );

View file

@ -6,254 +6,254 @@ import { Message } from "./message.js";
import { File } from "./file.js"; import { File } from "./file.js";
(async () => { (async () => {
async function waitForLoad(): Promise<void> { async function waitForLoad(): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
document.addEventListener("DOMContentLoaded", (_) => resolve()); document.addEventListener("DOMContentLoaded", (_) => resolve());
}); });
} }
await waitForLoad(); await waitForLoad();
const users = getBulkUsers(); const users = getBulkUsers();
if (!users.currentuser) { if (!users.currentuser) {
window.location.href = "/login.html"; window.location.href = "/login.html";
return; return;
} }
function showAccountSwitcher(): void { function showAccountSwitcher(): void {
const table = document.createElement("div"); const table = document.createElement("div");
table.classList.add("accountSwitcher"); table.classList.add("accountSwitcher");
for (const user of Object.values(users.users)) { for (const user of Object.values(users.users)) {
const specialUser = user as Specialuser; const specialUser = user as Specialuser;
const userInfo = document.createElement("div"); const userInfo = document.createElement("div");
userInfo.classList.add("flexltr", "switchtable"); userInfo.classList.add("flexltr", "switchtable");
const pfp = document.createElement("img"); const pfp = document.createElement("img");
pfp.src = specialUser.pfpsrc; pfp.src = specialUser.pfpsrc;
pfp.classList.add("pfp"); pfp.classList.add("pfp");
userInfo.append(pfp); userInfo.append(pfp);
const userDiv = document.createElement("div"); const userDiv = document.createElement("div");
userDiv.classList.add("userinfo"); userDiv.classList.add("userinfo");
userDiv.textContent = specialUser.username; userDiv.textContent = specialUser.username;
userDiv.append(document.createElement("br")); userDiv.append(document.createElement("br"));
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = specialUser.serverurls.wellknown span.textContent = specialUser.serverurls.wellknown
.replace("https://", "") .replace("https://", "")
.replace("http://", ""); .replace("http://", "");
span.classList.add("serverURL"); span.classList.add("serverURL");
userDiv.append(span); userDiv.append(span);
userInfo.append(userDiv); userInfo.append(userDiv);
table.append(userInfo); table.append(userInfo);
userInfo.addEventListener("click", () => { userInfo.addEventListener("click", () => {
thisUser.unload(); thisUser.unload();
thisUser.swapped = true; thisUser.swapped = true;
const loading = document.getElementById("loading") as HTMLDivElement; const loading = document.getElementById("loading") as HTMLDivElement;
loading.classList.remove("doneloading"); loading.classList.remove("doneloading");
loading.classList.add("loading"); loading.classList.add("loading");
thisUser = new Localuser(specialUser); thisUser = new Localuser(specialUser);
users.currentuser = specialUser.uid; users.currentuser = specialUser.uid;
localStorage.setItem("userinfos", JSON.stringify(users)); localStorage.setItem("userinfos", JSON.stringify(users));
thisUser.initwebsocket().then(() => { thisUser.initwebsocket().then(() => {
thisUser.loaduser(); thisUser.loaduser();
thisUser.init(); thisUser.init();
loading.classList.add("doneloading"); loading.classList.add("doneloading");
loading.classList.remove("loading"); loading.classList.remove("loading");
console.log("done loading"); console.log("done loading");
}); });
userInfo.remove(); userInfo.remove();
}); });
} }
const switchAccountDiv = document.createElement("div"); const switchAccountDiv = document.createElement("div");
switchAccountDiv.classList.add("switchtable"); switchAccountDiv.classList.add("switchtable");
switchAccountDiv.textContent = "Switch accounts ⇌"; switchAccountDiv.textContent = "Switch accounts ⇌";
switchAccountDiv.addEventListener("click", () => { switchAccountDiv.addEventListener("click", () => {
window.location.href = "/login.html"; window.location.href = "/login.html";
}); });
table.append(switchAccountDiv); table.append(switchAccountDiv);
if (Contextmenu.currentmenu) { if (Contextmenu.currentmenu) {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
Contextmenu.currentmenu = table; Contextmenu.currentmenu = table;
document.body.append(table); document.body.append(table);
} }
const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; const userInfoElement = document.getElementById("userinfo") as HTMLDivElement;
userInfoElement.addEventListener("click", (event) => { userInfoElement.addEventListener("click", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
const switchAccountsElement = document.getElementById( const switchAccountsElement = document.getElementById(
"switchaccounts" "switchaccounts"
) as HTMLDivElement; ) as HTMLDivElement;
switchAccountsElement.addEventListener("click", (event) => { switchAccountsElement.addEventListener("click", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
showAccountSwitcher(); showAccountSwitcher();
}); });
let thisUser: Localuser; let thisUser: Localuser;
try { try {
console.log(users.users, users.currentuser); console.log(users.users, users.currentuser);
thisUser = new Localuser(users.users[users.currentuser]); thisUser = new Localuser(users.users[users.currentuser]);
thisUser.initwebsocket().then(() => { thisUser.initwebsocket().then(() => {
thisUser.loaduser(); thisUser.loaduser();
thisUser.init(); thisUser.init();
const loading = document.getElementById("loading") as HTMLDivElement; const loading = document.getElementById("loading") as HTMLDivElement;
loading.classList.add("doneloading"); loading.classList.add("doneloading");
loading.classList.remove("loading"); loading.classList.remove("loading");
console.log("done loading"); console.log("done loading");
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
(document.getElementById("load-desc") as HTMLSpanElement).textContent = (document.getElementById("load-desc") as HTMLSpanElement).textContent =
"Account unable to start"; "Account unable to start";
thisUser = new Localuser(-1); thisUser = new Localuser(-1);
} }
const menu = new Contextmenu("create rightclick"); const menu = new Contextmenu("create rightclick");
menu.addbutton( menu.addbutton(
"Create channel", "Create channel",
() => { () => {
if (thisUser.lookingguild) { if (thisUser.lookingguild) {
thisUser.lookingguild.createchannels(); thisUser.lookingguild.createchannels();
} }
}, },
null, null,
() => thisUser.isAdmin() () => thisUser.isAdmin()
); );
menu.addbutton( menu.addbutton(
"Create category", "Create category",
() => { () => {
if (thisUser.lookingguild) { if (thisUser.lookingguild) {
thisUser.lookingguild.createcategory(); thisUser.lookingguild.createcategory();
} }
}, },
null, null,
() => thisUser.isAdmin() () => thisUser.isAdmin()
); );
menu.bindContextmenu( menu.bindContextmenu(
document.getElementById("channels") as HTMLDivElement, document.getElementById("channels") as HTMLDivElement,
0, 0,
0 0
); );
const pasteImageElement = document.getElementById( const pasteImageElement = document.getElementById(
"pasteimage" "pasteimage"
) as HTMLDivElement; ) as HTMLDivElement;
let replyingTo: Message | null = null; let replyingTo: Message | null = null;
async function handleEnter(event: KeyboardEvent): Promise<void> { async function handleEnter(event: KeyboardEvent): Promise<void> {
const channel = thisUser.channelfocus; const channel = thisUser.channelfocus;
if (!channel) return; if (!channel) return;
channel.typingstart(); channel.typingstart();
if (event.key === "Enter" && !event.shiftKey) { if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault(); event.preventDefault();
if (channel.editing) { if (channel.editing) {
channel.editing.edit(markdown.rawString); channel.editing.edit(markdown.rawString);
channel.editing = null; channel.editing = null;
} else { } else {
replyingTo = thisUser.channelfocus replyingTo = thisUser.channelfocus
? thisUser.channelfocus.replyingto ? thisUser.channelfocus.replyingto
: null; : null;
if (replyingTo?.div) { if (replyingTo?.div) {
replyingTo.div.classList.remove("replying"); replyingTo.div.classList.remove("replying");
} }
if (thisUser.channelfocus) { if (thisUser.channelfocus) {
thisUser.channelfocus.replyingto = null; thisUser.channelfocus.replyingto = null;
} }
channel.sendMessage(markdown.rawString, { channel.sendMessage(markdown.rawString, {
attachments: images, attachments: images,
// @ts-ignore This is valid according to the API // @ts-ignore This is valid according to the API
embeds: [], // Add an empty array for the embeds property embeds: [], // Add an empty array for the embeds property
replyingto: replyingTo, replyingto: replyingTo,
}); });
if (thisUser.channelfocus) { if (thisUser.channelfocus) {
thisUser.channelfocus.makereplybox(); thisUser.channelfocus.makereplybox();
} }
} }
while (images.length) { while (images.length) {
images.pop(); images.pop();
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);
} }
typebox.innerHTML = ""; typebox.innerHTML = "";
} }
} }
interface CustomHTMLDivElement extends HTMLDivElement { interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown; markdown: MarkDown;
} }
const typebox = document.getElementById("typebox") as CustomHTMLDivElement; const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const markdown = new MarkDown("", thisUser); const markdown = new MarkDown("", thisUser);
typebox.markdown = markdown; typebox.markdown = markdown;
typebox.addEventListener("keyup", handleEnter); typebox.addEventListener("keyup", handleEnter);
typebox.addEventListener("keydown", (event) => { typebox.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); if (event.key === "Enter" && !event.shiftKey) event.preventDefault();
}); });
markdown.giveBox(typebox); markdown.giveBox(typebox);
const images: Blob[] = []; const images: Blob[] = [];
const imagesHtml: HTMLElement[] = []; const imagesHtml: HTMLElement[] = [];
document.addEventListener("paste", async (e: ClipboardEvent) => { document.addEventListener("paste", async (e: ClipboardEvent) => {
if (!e.clipboardData) return; 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); const fileInstance = File.initFromBlob(file);
e.preventDefault(); e.preventDefault();
const html = fileInstance.upHTML(images, file); const html = fileInstance.upHTML(images, file);
pasteImageElement.appendChild(html); pasteImageElement.appendChild(html);
images.push(file); images.push(file);
imagesHtml.push(html); imagesHtml.push(html);
} }
}); });
setTheme(); setTheme();
function userSettings(): void { function userSettings(): void {
thisUser.showusersettings(); thisUser.showusersettings();
} }
(document.getElementById("settings") as HTMLImageElement).onclick = (document.getElementById("settings") as HTMLImageElement).onclick =
userSettings; userSettings;
if (mobile) { if (mobile) {
const channelWrapper = document.getElementById( const channelWrapper = document.getElementById(
"channelw" "channelw"
) as HTMLDivElement; ) as HTMLDivElement;
channelWrapper.onclick = () => { channelWrapper.onclick = () => {
( (
document.getElementById("channels")!.parentNode as HTMLElement document.getElementById("channels")!.parentNode as HTMLElement
).classList.add("collapse"); ).classList.add("collapse");
document.getElementById("servertd")!.classList.add("collapse"); document.getElementById("servertd")!.classList.add("collapse");
document.getElementById("servers")!.classList.add("collapse"); document.getElementById("servers")!.classList.add("collapse");
}; };
const mobileBack = document.getElementById("mobileback") as HTMLDivElement; const mobileBack = document.getElementById("mobileback") as HTMLDivElement;
mobileBack.textContent = "#"; mobileBack.textContent = "#";
mobileBack.onclick = () => { mobileBack.onclick = () => {
( (
document.getElementById("channels")!.parentNode as HTMLElement document.getElementById("channels")!.parentNode as HTMLElement
).classList.remove("collapse"); ).classList.remove("collapse");
document.getElementById("servertd")!.classList.remove("collapse"); document.getElementById("servertd")!.classList.remove("collapse");
document.getElementById("servers")!.classList.remove("collapse"); document.getElementById("servers")!.classList.remove("collapse");
}; };
} }
})(); })();

View file

@ -1,323 +1,323 @@
class InfiniteScroller { class InfiniteScroller {
readonly getIDFromOffset: ( readonly getIDFromOffset: (
ID: string, ID: string,
offset: number offset: number
) => Promise<string | undefined>; ) => Promise<string | undefined>;
readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>; readonly getHTMLFromID: (ID: string) => Promise<HTMLElement>;
readonly destroyFromID: (ID: string) => Promise<boolean>; readonly destroyFromID: (ID: string) => Promise<boolean>;
readonly reachesBottom: () => void; readonly reachesBottom: () => void;
private readonly minDist = 2000; private readonly minDist = 2000;
private readonly fillDist = 3000; private readonly fillDist = 3000;
private readonly maxDist = 6000; private readonly maxDist = 6000;
HTMLElements: [HTMLElement, string][] = []; HTMLElements: [HTMLElement, string][] = [];
div: HTMLDivElement | null = null; div: HTMLDivElement | null = null;
timeout: NodeJS.Timeout | null = null; timeout: NodeJS.Timeout | null = null;
beenloaded = false; beenloaded = false;
scrollBottom = 0; scrollBottom = 0;
scrollTop = 0; scrollTop = 0;
needsupdate = true; needsupdate = true;
averageheight = 60; averageheight = 60;
watchtime = false; watchtime = false;
changePromise: Promise<boolean> | undefined; changePromise: Promise<boolean> | undefined;
scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number };
constructor( constructor(
getIDFromOffset: InfiniteScroller["getIDFromOffset"], getIDFromOffset: InfiniteScroller["getIDFromOffset"],
getHTMLFromID: InfiniteScroller["getHTMLFromID"], getHTMLFromID: InfiniteScroller["getHTMLFromID"],
destroyFromID: InfiniteScroller["destroyFromID"], destroyFromID: InfiniteScroller["destroyFromID"],
reachesBottom: InfiniteScroller["reachesBottom"] = () => {} reachesBottom: InfiniteScroller["reachesBottom"] = () => {}
) { ) {
this.getIDFromOffset = getIDFromOffset; this.getIDFromOffset = getIDFromOffset;
this.getHTMLFromID = getHTMLFromID; this.getHTMLFromID = getHTMLFromID;
this.destroyFromID = destroyFromID; this.destroyFromID = destroyFromID;
this.reachesBottom = reachesBottom; this.reachesBottom = reachesBottom;
} }
async getDiv(initialId: string): Promise<HTMLDivElement> { async getDiv(initialId: string): Promise<HTMLDivElement> {
if (this.div) { if (this.div) {
throw new Error("Div already exists, exiting."); throw new Error("Div already exists, exiting.");
} }
const scroll = document.createElement("div"); const scroll = document.createElement("div");
scroll.classList.add("flexttb", "scroller"); scroll.classList.add("flexttb", "scroller");
this.div = scroll; this.div = scroll;
this.div.addEventListener("scroll", () => { this.div.addEventListener("scroll", () => {
this.checkscroll(); this.checkscroll();
if (this.scrollBottom < 5) { if (this.scrollBottom < 5) {
this.scrollBottom = 5; this.scrollBottom = 5;
} }
if (this.timeout === null) { if (this.timeout === null) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
this.watchForChange(); this.watchForChange();
}); });
let oldheight = 0; let oldheight = 0;
new ResizeObserver(() => { new ResizeObserver(() => {
this.checkscroll(); this.checkscroll();
const func = this.snapBottom(); const func = this.snapBottom();
this.updatestuff(); this.updatestuff();
const change = oldheight - scroll.offsetHeight; const change = oldheight - scroll.offsetHeight;
if (change > 0 && this.div) { if (change > 0 && this.div) {
this.div.scrollTop += change; this.div.scrollTop += change;
} }
oldheight = scroll.offsetHeight; oldheight = scroll.offsetHeight;
this.watchForChange(); this.watchForChange();
func(); func();
}).observe(scroll); }).observe(scroll);
new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); new ResizeObserver(this.watchForChange.bind(this)).observe(scroll);
await this.firstElement(initialId); await this.firstElement(initialId);
this.updatestuff(); this.updatestuff();
await this.watchForChange().then(() => { await this.watchForChange().then(() => {
this.updatestuff(); this.updatestuff();
this.beenloaded = true; this.beenloaded = true;
}); });
return scroll; return scroll;
} }
checkscroll(): void { checkscroll(): void {
if (this.beenloaded && this.div && !document.body.contains(this.div)) { if (this.beenloaded && this.div && !document.body.contains(this.div)) {
console.warn("not in document"); console.warn("not in document");
this.div = null; this.div = null;
} }
} }
async updatestuff(): Promise<void> { async updatestuff(): Promise<void> {
this.timeout = null; this.timeout = null;
if (!this.div) return; if (!this.div) return;
this.scrollBottom = this.scrollBottom =
this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight; this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight;
this.averageheight = this.div.scrollHeight / this.HTMLElements.length; this.averageheight = this.div.scrollHeight / this.HTMLElements.length;
if (this.averageheight < 10) { if (this.averageheight < 10) {
this.averageheight = 60; this.averageheight = 60;
} }
this.scrollTop = this.div.scrollTop; this.scrollTop = this.div.scrollTop;
if (!this.scrollBottom && !(await this.watchForChange())) { if (!this.scrollBottom && !(await this.watchForChange())) {
this.reachesBottom(); this.reachesBottom();
} }
if (!this.scrollTop) { if (!this.scrollTop) {
await this.watchForChange(); await this.watchForChange();
} }
this.needsupdate = false; this.needsupdate = false;
} }
async firstElement(id: string): Promise<void> { async firstElement(id: string): Promise<void> {
if (!this.div) return; if (!this.div) return;
const html = await this.getHTMLFromID(id); const html = await this.getHTMLFromID(id);
this.div.appendChild(html); this.div.appendChild(html);
this.HTMLElements.push([html, id]); this.HTMLElements.push([html, id]);
} }
async addedBottom(): Promise<void> { async addedBottom(): Promise<void> {
await this.updatestuff(); await this.updatestuff();
const func = this.snapBottom(); const func = this.snapBottom();
await this.watchForChange(); await this.watchForChange();
func(); func();
} }
snapBottom(): () => void { snapBottom(): () => void {
const scrollBottom = this.scrollBottom; const scrollBottom = this.scrollBottom;
return () => { return () => {
if (this.div && scrollBottom < 4) { if (this.div && scrollBottom < 4) {
this.div.scrollTop = this.div.scrollHeight; this.div.scrollTop = this.div.scrollHeight;
} }
}; };
} }
private async watchForTop( private async watchForTop(
already = false, already = false,
fragment = new DocumentFragment() fragment = new DocumentFragment()
): Promise<boolean> { ): Promise<boolean> {
if (!this.div) return false; if (!this.div) return false;
try { try {
let again = false; let again = false;
if (this.scrollTop < (already ? this.fillDist : this.minDist)) { if (this.scrollTop < (already ? this.fillDist : this.minDist)) {
let nextid: string | undefined; let nextid: string | undefined;
const firstelm = this.HTMLElements.at(0); const firstelm = this.HTMLElements.at(0);
if (firstelm) { if (firstelm) {
const previd = firstelm[1]; const previd = firstelm[1];
nextid = await this.getIDFromOffset(previd, 1); nextid = await this.getIDFromOffset(previd, 1);
} }
if (nextid) { if (nextid) {
const html = await this.getHTMLFromID(nextid); const html = await this.getHTMLFromID(nextid);
if (!html) { if (!html) {
this.destroyFromID(nextid); this.destroyFromID(nextid);
return false; return false;
} }
again = true; again = true;
fragment.prepend(html); fragment.prepend(html);
this.HTMLElements.unshift([html, nextid]); this.HTMLElements.unshift([html, nextid]);
this.scrollTop += this.averageheight; this.scrollTop += this.averageheight;
} }
} }
if (this.scrollTop > this.maxDist) { if (this.scrollTop > this.maxDist) {
const html = this.HTMLElements.shift(); const html = this.HTMLElements.shift();
if (html) { if (html) {
again = true; again = true;
await this.destroyFromID(html[1]); await this.destroyFromID(html[1]);
this.scrollTop -= this.averageheight; this.scrollTop -= this.averageheight;
} }
} }
if (again) { if (again) {
await this.watchForTop(true, fragment); await this.watchForTop(true, fragment);
} }
return again; return again;
} finally { } finally {
if (!already) { if (!already) {
if (this.div.scrollTop === 0) { if (this.div.scrollTop === 0) {
this.scrollTop = 1; this.scrollTop = 1;
this.div.scrollTop = 10; this.div.scrollTop = 10;
} }
this.div.prepend(fragment, fragment); this.div.prepend(fragment, fragment);
} }
} }
} }
async watchForBottom( async watchForBottom(
already = false, already = false,
fragment = new DocumentFragment() fragment = new DocumentFragment()
): Promise<boolean> { ): Promise<boolean> {
let func: Function | undefined; let func: Function | undefined;
if (!already) func = this.snapBottom(); if (!already) func = this.snapBottom();
if (!this.div) return false; if (!this.div) return false;
try { try {
let again = false; let again = false;
const scrollBottom = this.scrollBottom; const scrollBottom = this.scrollBottom;
if (scrollBottom < (already ? this.fillDist : this.minDist)) { if (scrollBottom < (already ? this.fillDist : this.minDist)) {
let nextid: string | undefined; let nextid: string | undefined;
const lastelm = this.HTMLElements.at(-1); const lastelm = this.HTMLElements.at(-1);
if (lastelm) { if (lastelm) {
const previd = lastelm[1]; const previd = lastelm[1];
nextid = await this.getIDFromOffset(previd, -1); nextid = await this.getIDFromOffset(previd, -1);
} }
if (nextid) { if (nextid) {
again = true; again = true;
const html = await this.getHTMLFromID(nextid); const html = await this.getHTMLFromID(nextid);
fragment.appendChild(html); fragment.appendChild(html);
this.HTMLElements.push([html, nextid]); this.HTMLElements.push([html, nextid]);
this.scrollBottom += this.averageheight; this.scrollBottom += this.averageheight;
} }
} }
if (scrollBottom > this.maxDist) { if (scrollBottom > this.maxDist) {
const html = this.HTMLElements.pop(); const html = this.HTMLElements.pop();
if (html) { if (html) {
await this.destroyFromID(html[1]); await this.destroyFromID(html[1]);
this.scrollBottom -= this.averageheight; this.scrollBottom -= this.averageheight;
again = true; again = true;
} }
} }
if (again) { if (again) {
await this.watchForBottom(true, fragment); await this.watchForBottom(true, fragment);
} }
return again; return again;
} finally { } finally {
if (!already) { if (!already) {
this.div.append(fragment); this.div.append(fragment);
if (func) { if (func) {
func(); func();
} }
} }
} }
} }
async watchForChange(): Promise<boolean> { async watchForChange(): Promise<boolean> {
if (this.changePromise) { if (this.changePromise) {
this.watchtime = true; this.watchtime = true;
return await this.changePromise; return await this.changePromise;
} else { } else {
this.watchtime = false; this.watchtime = false;
} }
this.changePromise = new Promise<boolean>(async (res) => { this.changePromise = new Promise<boolean>(async (res) => {
try { try {
if (!this.div) { if (!this.div) {
res(false); res(false);
return false; return false;
} }
const out = (await Promise.allSettled([ const out = (await Promise.allSettled([
this.watchForTop(), this.watchForTop(),
this.watchForBottom(), this.watchForBottom(),
])) as { value: boolean }[]; ])) as { value: boolean }[];
const changed = out[0].value || out[1].value; const changed = out[0].value || out[1].value;
if (this.timeout === null && changed) { if (this.timeout === null && changed) {
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
res(Boolean(changed)); res(Boolean(changed));
return Boolean(changed); return Boolean(changed);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
res(false); res(false);
return false; return false;
} finally { } finally {
setTimeout(() => { setTimeout(() => {
this.changePromise = undefined; this.changePromise = undefined;
if (this.watchtime) { if (this.watchtime) {
this.watchForChange(); this.watchForChange();
} }
}, 300); }, 300);
} }
}); });
return await this.changePromise; return await this.changePromise;
} }
async focus(id: string, flash = true): Promise<void> { async focus(id: string, flash = true): Promise<void> {
let element: HTMLElement | undefined; let element: HTMLElement | undefined;
for (const thing of this.HTMLElements) { for (const thing of this.HTMLElements) {
if (thing[1] === id) { if (thing[1] === id) {
element = thing[0]; element = thing[0];
} }
} }
if (element) { if (element) {
if (flash) { if (flash) {
element.scrollIntoView({ element.scrollIntoView({
behavior: "smooth", behavior: "smooth",
block: "center", block: "center",
}); });
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
element.classList.remove("jumped"); element.classList.remove("jumped");
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
element.classList.add("jumped"); element.classList.add("jumped");
} else { } else {
element.scrollIntoView(); element.scrollIntoView();
} }
} else { } else {
for (const thing of this.HTMLElements) { for (const thing of this.HTMLElements) {
await this.destroyFromID(thing[1]); await this.destroyFromID(thing[1]);
} }
this.HTMLElements = []; this.HTMLElements = [];
await this.firstElement(id); await this.firstElement(id);
this.updatestuff(); this.updatestuff();
await this.watchForChange(); await this.watchForChange();
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
await this.focus(id, true); await this.focus(id, true);
} }
} }
async delete(): Promise<void> { async delete(): Promise<void> {
if (this.div) { if (this.div) {
this.div.remove(); this.div.remove();
this.div = null; this.div = null;
} }
try { try {
for (const thing of this.HTMLElements) { for (const thing of this.HTMLElements) {
await this.destroyFromID(thing[1]); await this.destroyFromID(thing[1]);
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
this.HTMLElements = []; this.HTMLElements = [];
if (this.timeout) { if (this.timeout) {
clearTimeout(this.timeout); clearTimeout(this.timeout);
} }
} }
} }
export { InfiniteScroller }; export { InfiniteScroller };

View file

@ -1,147 +1,147 @@
import { getBulkUsers, Specialuser, getapiurls } from "./login.js"; import { getBulkUsers, Specialuser, getapiurls } from "./login.js";
(async () => { (async () => {
const users = getBulkUsers(); const users = getBulkUsers();
const well = new URLSearchParams(window.location.search).get("instance"); const well = new URLSearchParams(window.location.search).get("instance");
const joinable: Specialuser[] = []; const joinable: Specialuser[] = [];
for (const key in users.users) { for (const key in users.users) {
if (Object.prototype.hasOwnProperty.call(users.users, key)) { if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = 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); joinable.push(user);
} }
console.log(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); const out = await getapiurls(well);
if (out) { if (out) {
urls = out; urls = out;
for (const key in users.users) { for (const key in users.users) {
if (Object.prototype.hasOwnProperty.call(users.users, key)) { if (Object.prototype.hasOwnProperty.call(users.users, key)) {
const user: Specialuser = 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); joinable.push(user);
} }
console.log(user); console.log(user);
} }
} }
} else { } else {
throw new Error( throw new Error(
"Someone needs to handle the case where the servers don't exist" "Someone needs to handle the case where the servers don't exist"
); );
} }
} else { } else {
urls = joinable[0].serverurls; urls = joinable[0].serverurls;
} }
if (!joinable.length) { if (!joinable.length) {
document.getElementById("AcceptInvite")!.textContent = document.getElementById("AcceptInvite")!.textContent =
"Create an account to accept the invite"; "Create an account to accept the invite";
} }
const code = window.location.pathname.split("/")[2]; const code = window.location.pathname.split("/")[2];
let guildinfo: any; let guildinfo: any;
fetch(`${urls!.api}/invites/${code}`, { fetch(`${urls!.api}/invites/${code}`, {
method: "GET", method: "GET",
}) })
.then((response) => response.json()) .then((response) => response.json())
.then((json) => { .then((json) => {
const guildjson = json.guild; const guildjson = json.guild;
guildinfo = guildjson; guildinfo = guildjson;
document.getElementById("invitename")!.textContent = guildjson.name; document.getElementById("invitename")!.textContent = guildjson.name;
document.getElementById( document.getElementById(
"invitedescription" "invitedescription"
)!.textContent = `${json.inviter.username} invited you to join ${guildjson.name}`; )!.textContent = `${json.inviter.username} invited you to join ${guildjson.name}`;
if (guildjson.icon) { if (guildjson.icon) {
const img = document.createElement("img"); const img = document.createElement("img");
img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`;
img.classList.add("inviteGuild"); img.classList.add("inviteGuild");
document.getElementById("inviteimg")!.append(img); document.getElementById("inviteimg")!.append(img);
} else { } else {
const txt = guildjson.name const txt = guildjson.name
.replace(/'s /g, " ") .replace(/'s /g, " ")
.replace(/\w+/g, (word: any[]) => word[0]) .replace(/\w+/g, (word: any[]) => word[0])
.replace(/\s/g, ""); .replace(/\s/g, "");
const div = document.createElement("div"); const div = document.createElement("div");
div.textContent = txt; div.textContent = txt;
div.classList.add("inviteGuild"); div.classList.add("inviteGuild");
document.getElementById("inviteimg")!.append(div); document.getElementById("inviteimg")!.append(div);
} }
}); });
function showAccounts(): void { function showAccounts(): void {
const table = document.createElement("dialog"); const table = document.createElement("dialog");
for (const user of joinable) { for (const user of joinable) {
console.log(user.pfpsrc); console.log(user.pfpsrc);
const userinfo = document.createElement("div"); const userinfo = document.createElement("div");
userinfo.classList.add("flexltr", "switchtable"); userinfo.classList.add("flexltr", "switchtable");
const pfp = document.createElement("img"); const pfp = document.createElement("img");
pfp.src = user.pfpsrc; pfp.src = user.pfpsrc;
pfp.classList.add("pfp"); pfp.classList.add("pfp");
userinfo.append(pfp); userinfo.append(pfp);
const userDiv = document.createElement("div"); const userDiv = document.createElement("div");
userDiv.classList.add("userinfo"); userDiv.classList.add("userinfo");
userDiv.textContent = user.username; userDiv.textContent = user.username;
userDiv.append(document.createElement("br")); userDiv.append(document.createElement("br"));
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = user.serverurls.wellknown span.textContent = user.serverurls.wellknown
.replace("https://", "") .replace("https://", "")
.replace("http://", ""); .replace("http://", "");
span.classList.add("serverURL"); span.classList.add("serverURL");
userDiv.append(span); userDiv.append(span);
userinfo.append(userDiv); userinfo.append(userDiv);
table.append(userinfo); table.append(userinfo);
userinfo.addEventListener("click", () => { userinfo.addEventListener("click", () => {
console.log(user); console.log(user);
fetch(`${urls!.api}/invites/${code}`, { fetch(`${urls!.api}/invites/${code}`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: user.token, Authorization: user.token,
}, },
}).then(() => { }).then(() => {
users.currentuser = user.uid; users.currentuser = user.uid;
localStorage.setItem("userinfos", JSON.stringify(users)); localStorage.setItem("userinfos", JSON.stringify(users));
window.location.href = "/channels/" + guildinfo.id; window.location.href = "/channels/" + guildinfo.id;
}); });
}); });
} }
const td = document.createElement("div"); const td = document.createElement("div");
td.classList.add("switchtable"); td.classList.add("switchtable");
td.textContent = "Login or create an account ⇌"; td.textContent = "Login or create an account ⇌";
td.addEventListener("click", () => { td.addEventListener("click", () => {
const l = new URLSearchParams("?"); const l = new URLSearchParams("?");
l.set("goback", window.location.href); l.set("goback", window.location.href);
l.set("instance", well!); l.set("instance", well!);
window.location.href = "/login?" + l.toString(); window.location.href = "/login?" + l.toString();
}); });
if (!joinable.length) { if (!joinable.length) {
const l = new URLSearchParams("?"); const l = new URLSearchParams("?");
l.set("goback", window.location.href); l.set("goback", window.location.href);
l.set("instance", well!); l.set("instance", well!);
window.location.href = "/login?" + l.toString(); window.location.href = "/login?" + l.toString();
} }
table.append(td); table.append(td);
table.classList.add("accountSwitcher"); table.classList.add("accountSwitcher");
console.log(table); console.log(table);
document.body.append(table); document.body.append(table);
} }
document document
.getElementById("AcceptInvite")! .getElementById("AcceptInvite")!
.addEventListener("click", showAccounts); .addEventListener("click", showAccounts);
})(); })();

View file

@ -1,501 +1,501 @@
type readyjson = { type readyjson = {
op: 0; op: 0;
t: "READY"; t: "READY";
s: number; s: number;
d: { d: {
v: number; v: number;
user: mainuserjson; user: mainuserjson;
user_settings: { user_settings: {
index: number; index: number;
afk_timeout: number; afk_timeout: number;
allow_accessibility_detection: boolean; allow_accessibility_detection: boolean;
animate_emoji: boolean; animate_emoji: boolean;
animate_stickers: number; animate_stickers: number;
contact_sync_enabled: boolean; contact_sync_enabled: boolean;
convert_emoticons: boolean; convert_emoticons: boolean;
custom_status: string; custom_status: string;
default_guilds_restricted: boolean; default_guilds_restricted: boolean;
detect_platform_accounts: boolean; detect_platform_accounts: boolean;
developer_mode: boolean; developer_mode: boolean;
disable_games_tab: boolean; disable_games_tab: boolean;
enable_tts_command: boolean; enable_tts_command: boolean;
explicit_content_filter: 0; explicit_content_filter: 0;
friend_discovery_flags: 0; friend_discovery_flags: 0;
friend_source_flags: { friend_source_flags: {
all: boolean; all: boolean;
}; //might be missing things here }; //might be missing things here
gateway_connected: boolean; gateway_connected: boolean;
gif_auto_play: boolean; gif_auto_play: boolean;
guild_folders: []; //need an example of this not empty guild_folders: []; //need an example of this not empty
guild_positions: []; //need an example of this not empty guild_positions: []; //need an example of this not empty
inline_attachment_media: boolean; inline_attachment_media: boolean;
inline_embed_media: boolean; inline_embed_media: boolean;
locale: string; locale: string;
message_display_compact: boolean; message_display_compact: boolean;
native_phone_integration_enabled: boolean; native_phone_integration_enabled: boolean;
render_embeds: boolean; render_embeds: boolean;
render_reactions: boolean; render_reactions: boolean;
restricted_guilds: []; //need an example of this not empty restricted_guilds: []; //need an example of this not empty
show_current_game: boolean; show_current_game: boolean;
status: string; status: string;
stream_notifications_enabled: boolean; stream_notifications_enabled: boolean;
theme: string; theme: string;
timezone_offset: number; timezone_offset: number;
view_nsfw_guilds: boolean; view_nsfw_guilds: boolean;
}; };
guilds: guildjson[]; guilds: guildjson[];
relationships: { relationships: {
id: string; id: string;
type: 0 | 1 | 2 | 3 | 4; type: 0 | 1 | 2 | 3 | 4;
nickname: string | null; nickname: string | null;
user: userjson; user: userjson;
}[]; }[];
read_state: { read_state: {
entries: { entries: {
id: string; id: string;
channel_id: string; channel_id: string;
last_message_id: string; last_message_id: string;
last_pin_timestamp: string; last_pin_timestamp: string;
mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware
}[]; }[];
partial: boolean; partial: boolean;
version: number; version: number;
}; };
user_guild_settings: { user_guild_settings: {
entries: { entries: {
channel_overrides: unknown[]; //will have to find example channel_overrides: unknown[]; //will have to find example
message_notifications: number; message_notifications: number;
flags: number; flags: number;
hide_muted_channels: boolean; hide_muted_channels: boolean;
mobile_push: boolean; mobile_push: boolean;
mute_config: null; mute_config: null;
mute_scheduled_events: boolean; mute_scheduled_events: boolean;
muted: boolean; muted: boolean;
notify_highlights: number; notify_highlights: number;
suppress_everyone: boolean; suppress_everyone: boolean;
suppress_roles: boolean; suppress_roles: boolean;
version: number; version: number;
guild_id: string; guild_id: string;
}[]; }[];
partial: boolean; partial: boolean;
version: number; version: number;
}; };
private_channels: dirrectjson[]; private_channels: dirrectjson[];
session_id: string; session_id: string;
country_code: string; country_code: string;
users: userjson[]; users: userjson[];
merged_members: [memberjson][]; merged_members: [memberjson][];
sessions: { sessions: {
active: boolean; active: boolean;
activities: []; //will need to find example of this activities: []; //will need to find example of this
client_info: { client_info: {
version: number; version: number;
}; };
session_id: string; session_id: string;
status: string; status: string;
}[]; }[];
resume_gateway_url: string; resume_gateway_url: string;
consents: { consents: {
personalization: { personalization: {
consented: boolean; consented: boolean;
}; };
}; };
experiments: []; //not sure if I need to do this :P experiments: []; //not sure if I need to do this :P
guild_join_requests: []; //need to get examples guild_join_requests: []; //need to get examples
connected_accounts: []; //need to get examples connected_accounts: []; //need to get examples
guild_experiments: []; //need to get examples guild_experiments: []; //need to get examples
geo_ordered_rtc_regions: []; //need to get examples geo_ordered_rtc_regions: []; //need to get examples
api_code_version: number; api_code_version: number;
friend_suggestion_count: number; friend_suggestion_count: number;
analytics_token: string; analytics_token: string;
tutorial: boolean; tutorial: boolean;
session_type: string; session_type: string;
auth_session_id_hash: string; auth_session_id_hash: string;
notification_settings: { notification_settings: {
flags: number; flags: number;
}; };
}; };
}; };
type mainuserjson = userjson & { type mainuserjson = userjson & {
flags: number; flags: number;
mfa_enabled?: boolean; mfa_enabled?: boolean;
email?: string; email?: string;
phone?: string; phone?: string;
verified: boolean; verified: boolean;
nsfw_allowed: boolean; nsfw_allowed: boolean;
premium: boolean; premium: boolean;
purchased_flags: number; purchased_flags: number;
premium_usage_flags: number; premium_usage_flags: number;
disabled: boolean; disabled: boolean;
}; };
type userjson = { type userjson = {
username: string; username: string;
discriminator: string; discriminator: string;
id: string; id: string;
public_flags: number; public_flags: number;
avatar: string | null; avatar: string | null;
accent_color: number; accent_color: number;
banner?: string; banner?: string;
bio: string; bio: string;
bot: boolean; bot: boolean;
premium_since: string; premium_since: string;
premium_type: number; premium_type: number;
theme_colors: string; theme_colors: string;
pronouns: string; pronouns: string;
badge_ids: string[]; badge_ids: string[];
}; };
type memberjson = { type memberjson = {
index?: number; index?: number;
id: string; id: string;
user: userjson | null; user: userjson | null;
guild_id: string; guild_id: string;
guild: { guild: {
id: string; id: string;
} | null; } | null;
nick?: string; nick?: string;
roles: string[]; roles: string[];
joined_at: string; joined_at: string;
premium_since: string; premium_since: string;
deaf: boolean; deaf: boolean;
mute: boolean; mute: boolean;
pending: boolean; pending: boolean;
last_message_id?: boolean; //What??? last_message_id?: boolean; //What???
}; };
type emojijson = { type emojijson = {
name: string; name: string;
id?: string; id?: string;
animated?: boolean; animated?: boolean;
}; };
type guildjson = { type guildjson = {
application_command_counts: { [key: string]: number }; application_command_counts: { [key: string]: number };
channels: channeljson[]; channels: channeljson[];
data_mode: string; data_mode: string;
emojis: emojijson[]; emojis: emojijson[];
guild_scheduled_events: []; guild_scheduled_events: [];
id: string; id: string;
large: boolean; large: boolean;
lazy: boolean; lazy: boolean;
member_count: number; member_count: number;
premium_subscription_count: number; premium_subscription_count: number;
properties: { properties: {
region: string | null; region: string | null;
name: string; name: string;
description: string; description: string;
icon: string; icon: string;
splash: string; splash: string;
banner: string; banner: string;
features: string[]; features: string[];
preferred_locale: string; preferred_locale: string;
owner_id: string; owner_id: string;
application_id: string; application_id: string;
afk_channel_id: string; afk_channel_id: string;
afk_timeout: number; afk_timeout: number;
member_count: number; member_count: number;
system_channel_id: string; system_channel_id: string;
verification_level: number; verification_level: number;
explicit_content_filter: number; explicit_content_filter: number;
default_message_notifications: number; default_message_notifications: number;
mfa_level: number; mfa_level: number;
vanity_url_code: number; vanity_url_code: number;
premium_tier: number; premium_tier: number;
premium_progress_bar_enabled: boolean; premium_progress_bar_enabled: boolean;
system_channel_flags: number; system_channel_flags: number;
discovery_splash: string; discovery_splash: string;
rules_channel_id: string; rules_channel_id: string;
public_updates_channel_id: string; public_updates_channel_id: string;
max_video_channel_users: number; max_video_channel_users: number;
max_members: number; max_members: number;
nsfw_level: number; nsfw_level: number;
hub_type: null; hub_type: null;
home_header: null; home_header: null;
id: string; id: string;
latest_onboarding_question_id: string; latest_onboarding_question_id: string;
max_stage_video_channel_users: number; max_stage_video_channel_users: number;
nsfw: boolean; nsfw: boolean;
safety_alerts_channel_id: string; safety_alerts_channel_id: string;
}; };
roles: rolesjson[]; roles: rolesjson[];
stage_instances: []; stage_instances: [];
stickers: []; stickers: [];
threads: []; threads: [];
version: string; version: string;
guild_hashes: {}; guild_hashes: {};
joined_at: string; joined_at: string;
}; };
type startTypingjson = { type startTypingjson = {
d: { d: {
channel_id: string; channel_id: string;
guild_id?: string; guild_id?: string;
user_id: string; user_id: string;
timestamp: number; timestamp: number;
member?: memberjson; member?: memberjson;
}; };
}; };
type channeljson = { type channeljson = {
id: string; id: string;
created_at: string; created_at: string;
name: string; name: string;
icon: string; icon: string;
type: number; type: number;
last_message_id: string; last_message_id: string;
guild_id: string; guild_id: string;
parent_id: string; parent_id: string;
last_pin_timestamp: string; last_pin_timestamp: string;
default_auto_archive_duration: number; default_auto_archive_duration: number;
permission_overwrites: { permission_overwrites: {
id: string; id: string;
allow: string; allow: string;
deny: string; deny: string;
}[]; }[];
video_quality_mode: null; video_quality_mode: null;
nsfw: boolean; nsfw: boolean;
topic: string; topic: string;
retention_policy_id: string; retention_policy_id: string;
flags: number; flags: number;
default_thread_rate_limit_per_user: number; default_thread_rate_limit_per_user: number;
position: number; position: number;
}; };
type rolesjson = { type rolesjson = {
id: string; id: string;
guild_id: string; guild_id: string;
color: number; color: number;
hoist: boolean; hoist: boolean;
managed: boolean; managed: boolean;
mentionable: boolean; mentionable: boolean;
name: string; name: string;
permissions: string; permissions: string;
position: number; position: number;
icon: string; icon: string;
unicode_emoji: string; unicode_emoji: string;
flags: number; flags: number;
}; };
type dirrectjson = { type dirrectjson = {
id: string; id: string;
flags: number; flags: number;
last_message_id: string; last_message_id: string;
type: number; type: number;
recipients: userjson[]; recipients: userjson[];
is_spam: boolean; is_spam: boolean;
}; };
type messagejson = { type messagejson = {
id: string; id: string;
channel_id: string; channel_id: string;
guild_id: string; guild_id: string;
author: userjson; author: userjson;
member?: memberjson; member?: memberjson;
content: string; content: string;
timestamp: string; timestamp: string;
edited_timestamp: string; edited_timestamp: string;
tts: boolean; tts: boolean;
mention_everyone: boolean; mention_everyone: boolean;
mentions: []; //need examples to fix mentions: []; //need examples to fix
mention_roles: []; //need examples to fix mention_roles: []; //need examples to fix
attachments: filejson[]; attachments: filejson[];
embeds: embedjson[]; embeds: embedjson[];
reactions: { reactions: {
count: number; count: number;
emoji: emojijson; //very likely needs expanding emoji: emojijson; //very likely needs expanding
me: boolean; me: boolean;
}[]; }[];
nonce: string; nonce: string;
pinned: boolean; pinned: boolean;
type: number; type: number;
}; };
type filejson = { type filejson = {
id: string; id: string;
filename: string; filename: string;
content_type: string; content_type: string;
width?: number; width?: number;
height?: number; height?: number;
proxy_url: string | undefined; proxy_url: string | undefined;
url: string; url: string;
size: number; size: number;
}; };
type embedjson = { type embedjson = {
type: string | null; type: string | null;
color?: number; color?: number;
author: { author: {
icon_url?: string; icon_url?: string;
name?: string; name?: string;
url?: string; url?: string;
title?: string; title?: string;
}; };
title?: string; title?: string;
url?: string; url?: string;
description?: string; description?: string;
fields?: { fields?: {
name: string; name: string;
value: string; value: string;
inline: boolean; inline: boolean;
}[]; }[];
footer?: { footer?: {
icon_url?: string; icon_url?: string;
text?: string; text?: string;
thumbnail?: string; thumbnail?: string;
}; };
timestamp?: string; timestamp?: string;
thumbnail: { thumbnail: {
proxy_url: string; proxy_url: string;
url: string; url: string;
width: number; width: number;
height: number; height: number;
}; };
provider: { provider: {
name: string; name: string;
}; };
video?: { video?: {
url: string; url: string;
width?: number | null; width?: number | null;
height?: number | null; height?: number | null;
proxy_url?: string; proxy_url?: string;
}; };
invite?: { invite?: {
url: string; url: string;
code: string; code: string;
}; };
}; };
type invitejson = { type invitejson = {
code: string; code: string;
temporary: boolean; temporary: boolean;
uses: number; uses: number;
max_use: number; max_use: number;
max_age: number; max_age: number;
created_at: string; created_at: string;
expires_at: string; expires_at: string;
guild_id: string; guild_id: string;
channel_id: string; channel_id: string;
inviter_id: string; inviter_id: string;
target_user_id: string | null; target_user_id: string | null;
target_user_type: string | null; target_user_type: string | null;
vanity_url: string | null; vanity_url: string | null;
flags: number; flags: number;
guild: guildjson["properties"]; guild: guildjson["properties"];
channel: channeljson; channel: channeljson;
inviter: userjson; inviter: userjson;
}; };
type presencejson = { type presencejson = {
status: string; status: string;
since: number | null; since: number | null;
activities: any[]; //bit more complicated but not now activities: any[]; //bit more complicated but not now
afk: boolean; afk: boolean;
user?: userjson; user?: userjson;
}; };
type messageCreateJson = { type messageCreateJson = {
op: 0; op: 0;
d: { d: {
guild_id?: string; guild_id?: string;
channel_id?: string; channel_id?: string;
} & messagejson; } & messagejson;
s: number; s: number;
t: "MESSAGE_CREATE"; t: "MESSAGE_CREATE";
}; };
type wsjson = type wsjson =
| { | {
op: 0; op: 0;
d: any; d: any;
s: number; s: number;
t: t:
| "TYPING_START" | "TYPING_START"
| "USER_UPDATE" | "USER_UPDATE"
| "CHANNEL_UPDATE" | "CHANNEL_UPDATE"
| "CHANNEL_CREATE" | "CHANNEL_CREATE"
| "CHANNEL_DELETE" | "CHANNEL_DELETE"
| "GUILD_DELETE" | "GUILD_DELETE"
| "GUILD_CREATE" | "GUILD_CREATE"
| "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_ALL"
| "MESSAGE_REACTION_REMOVE_EMOJI"; | "MESSAGE_REACTION_REMOVE_EMOJI";
} }
| { | {
op: 0; op: 0;
t: "GUILD_MEMBERS_CHUNK"; t: "GUILD_MEMBERS_CHUNK";
d: memberChunk; d: memberChunk;
s: number; s: number;
} }
| { | {
op: 0; op: 0;
d: { d: {
id: string; id: string;
guild_id?: string; guild_id?: string;
channel_id: string; channel_id: string;
}; };
s: number; s: number;
t: "MESSAGE_DELETE"; t: "MESSAGE_DELETE";
} }
| { | {
op: 0; op: 0;
d: { d: {
guild_id?: string; guild_id?: string;
channel_id: string; channel_id: string;
} & messagejson; } & messagejson;
s: number; s: number;
t: "MESSAGE_UPDATE"; t: "MESSAGE_UPDATE";
} }
| messageCreateJson | messageCreateJson
| readyjson | readyjson
| { | {
op: 11; op: 11;
s: undefined; s: undefined;
d: {}; d: {};
} }
| { | {
op: 10; op: 10;
s: undefined; s: undefined;
d: { d: {
heartbeat_interval: number; heartbeat_interval: number;
}; };
} }
| { | {
op: 0; op: 0;
t: "MESSAGE_REACTION_ADD"; t: "MESSAGE_REACTION_ADD";
d: { d: {
user_id: string; user_id: string;
channel_id: string; channel_id: string;
message_id: string; message_id: string;
guild_id?: string; guild_id?: string;
emoji: emojijson; emoji: emojijson;
member?: memberjson; member?: memberjson;
}; };
s: number; s: number;
} }
| { | {
op: 0; op: 0;
t: "MESSAGE_REACTION_REMOVE"; t: "MESSAGE_REACTION_REMOVE";
d: { d: {
user_id: string; user_id: string;
channel_id: string; channel_id: string;
message_id: string; message_id: string;
guild_id: string; guild_id: string;
emoji: emojijson; emoji: emojijson;
}; };
s: 3; s: 3;
}; };
type memberChunk = { type memberChunk = {
guild_id: string; guild_id: string;
nonce: string; nonce: string;
members: memberjson[]; members: memberjson[];
presences: presencejson[]; presences: presencejson[];
chunk_index: number; chunk_index: number;
chunk_count: number; chunk_count: number;
not_found: string[]; not_found: string[];
}; };
export { export {
readyjson, readyjson,
dirrectjson, dirrectjson,
startTypingjson, startTypingjson,
channeljson, channeljson,
guildjson, guildjson,
rolesjson, rolesjson,
userjson, userjson,
memberjson, memberjson,
mainuserjson, mainuserjson,
messagejson, messagejson,
filejson, filejson,
embedjson, embedjson,
emojijson, emojijson,
presencejson, presencejson,
wsjson, wsjson,
messageCreateJson, messageCreateJson,
memberChunk, memberChunk,
invitejson, invitejson,
}; };

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,251 +6,251 @@ import { memberjson, presencejson } from "./jsontypes.js";
import { Dialog } from "./dialog.js"; import { Dialog } from "./dialog.js";
class Member extends SnowFlake { class Member extends SnowFlake {
static already = {}; static already = {};
owner: Guild; owner: Guild;
user: User; user: User;
roles: Role[] = []; roles: Role[] = [];
nick!: string; nick!: string;
[key: string]: any; [key: string]: any;
private constructor(memberjson: memberjson, owner: Guild) { private constructor(memberjson: memberjson, owner: Guild) {
super(memberjson.id); super(memberjson.id);
this.owner = owner; 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; 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); this.user = new User(memberjson.user, owner.localuser);
} else { } else {
throw new Error("Missing user object of this member"); throw new Error("Missing user object of this member");
}
for (const key of Object.keys(memberjson)) {
if (key === "guild" || key === "owner") {
continue;
}
if (key === "roles") {
for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole);
if (!role) continue;
this.roles.push(role);
}
continue;
}
(this as any)[key] = (memberjson as any)[key];
}
if (this.localuser.userMap.has(this?.id)) {
this.user = this.localuser.userMap.get(this?.id) as User;
}
this.roles.sort((a, b) => {
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
});
}
get guild() {
return this.owner;
}
get localuser() {
return this.guild.localuser;
}
get info() {
return this.owner.info;
}
static async new(
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined> {
let user: User;
if (owner.localuser.userMap.has(memberjson.id)) {
user = owner.localuser.userMap.get(memberjson.id) as User;
} else if (memberjson.user) {
user = new User(memberjson.user, owner.localuser);
} else {
throw new Error("missing user object of this member");
}
if (user.members.has(owner)) {
let memb = user.members.get(owner);
if (memb === undefined) {
memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
} else if (memb instanceof Promise) {
return await memb; //I should do something else, though for now this is "good enough"
} else {
return memb;
}
} else {
const memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
}
}
static async resolveMember(
user: User,
guild: Guild
): Promise<Member | undefined> {
const maybe = user.members.get(guild);
if (!user.members.has(guild)) {
const membpromise = guild.localuser.resolvemember(user.id, guild.id);
const promise = new Promise<Member | undefined>(async (res) => {
const membjson = await membpromise;
if (membjson === undefined) {
return res(undefined);
} else {
const member = new Member(membjson, guild);
const map = guild.localuser.presences;
member.getPresence(map.get(member.id));
map.delete(member.id);
res(member);
return member;
}
});
user.members.set(guild, promise);
}
if (maybe instanceof Promise) {
return await maybe;
} else {
return maybe;
}
}
public getPresence(presence: presencejson | undefined) {
this.user.getPresence(presence);
}
/**
* @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 }
);
}
hasRole(ID: string) {
console.log(this.roles, ID);
for (const thing of this.roles) {
if (thing.id === ID) {
return true;
}
}
return false;
}
getColor() {
for (const thing of this.roles) {
const color = thing.getColor();
if (color) {
return color;
}
}
return "";
}
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) {
return;
}
/*
if(this.error){
}
*/
html.style.color = this.getColor();
}
//this.profileclick(html);
}
profileclick(/* html: HTMLElement */) {
//to be implemented
}
get name() {
return this.nick || this.user.username;
}
kick() {
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Kick " + this.name + " from " + this.guild.properties.name],
[
"textbox",
"Reason:",
"",
function (e: Event) {
reason = (e.target as HTMLInputElement).value;
},
],
[
"button",
"",
"submit",
() => {
this.kickAPI(reason);
menu.hide();
},
],
]);
menu.show();
}
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}`, {
method: "DELETE",
headers,
});
}
ban() {
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Ban " + this.name + " from " + this.guild.properties.name],
[
"textbox",
"Reason:",
"",
function (e: Event) {
reason = (e.target as HTMLInputElement).value;
},
],
[
"button",
"",
"submit",
() => {
this.banAPI(reason);
menu.hide();
},
],
]);
menu.show();
}
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}`, {
method: "PUT",
headers,
});
}
hasPermission(name: string): boolean {
if (this.isAdmin()) {
return true;
}
for (const thing of this.roles) {
if (thing.permissions.getPermission(name)) {
return true;
}
}
return false;
}
} }
export { Member };
for (const key of Object.keys(memberjson)) {
if (key === "guild" || key === "owner") {
continue;
}
if (key === "roles") {
for (const strrole of memberjson.roles) {
const role = this.guild.roleids.get(strrole);
if (!role) continue;
this.roles.push(role);
}
continue;
}
(this as any)[key] = (memberjson as any)[key];
}
if (this.localuser.userMap.has(this?.id)) {
this.user = this.localuser.userMap.get(this?.id) as User;
}
this.roles.sort((a, b) => {
return this.guild.roles.indexOf(a) - this.guild.roles.indexOf(b);
});
}
get guild() {
return this.owner;
}
get localuser() {
return this.guild.localuser;
}
get info() {
return this.owner.info;
}
static async new(
memberjson: memberjson,
owner: Guild
): Promise<Member | undefined> {
let user: User;
if (owner.localuser.userMap.has(memberjson.id)) {
user = owner.localuser.userMap.get(memberjson.id) as User;
} else if (memberjson.user) {
user = new User(memberjson.user, owner.localuser);
} else {
throw new Error("missing user object of this member");
}
if (user.members.has(owner)) {
let memb = user.members.get(owner);
if (memb === undefined) {
memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
} else if (memb instanceof Promise) {
return await memb; //I should do something else, though for now this is "good enough"
} else {
return memb;
}
} else {
const memb = new Member(memberjson, owner);
user.members.set(owner, memb);
return memb;
}
}
static async resolveMember(
user: User,
guild: Guild
): Promise<Member | undefined> {
const maybe = user.members.get(guild);
if (!user.members.has(guild)) {
const membpromise = guild.localuser.resolvemember(user.id, guild.id);
const promise = new Promise<Member | undefined>(async (res) => {
const membjson = await membpromise;
if (membjson === undefined) {
return res(undefined);
} else {
const member = new Member(membjson, guild);
const map = guild.localuser.presences;
member.getPresence(map.get(member.id));
map.delete(member.id);
res(member);
return member;
}
});
user.members.set(guild, promise);
}
if (maybe instanceof Promise) {
return await maybe;
} else {
return maybe;
}
}
public getPresence(presence: presencejson | undefined) {
this.user.getPresence(presence);
}
/**
* @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 }
);
}
hasRole(ID: string) {
console.log(this.roles, ID);
for (const thing of this.roles) {
if (thing.id === ID) {
return true;
}
}
return false;
}
getColor() {
for (const thing of this.roles) {
const color = thing.getColor();
if (color) {
return color;
}
}
return "";
}
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) {
return;
}
/*
if(this.error){
}
*/
html.style.color = this.getColor();
}
//this.profileclick(html);
}
profileclick(/* html: HTMLElement */) {
//to be implemented
}
get name() {
return this.nick || this.user.username;
}
kick() {
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Kick " + this.name + " from " + this.guild.properties.name],
[
"textbox",
"Reason:",
"",
function (e: Event) {
reason = (e.target as HTMLInputElement).value;
},
],
[
"button",
"",
"submit",
() => {
this.kickAPI(reason);
menu.hide();
},
],
]);
menu.show();
}
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}`, {
method: "DELETE",
headers,
});
}
ban() {
let reason = "";
const menu = new Dialog([
"vdiv",
["title", "Ban " + this.name + " from " + this.guild.properties.name],
[
"textbox",
"Reason:",
"",
function (e: Event) {
reason = (e.target as HTMLInputElement).value;
},
],
[
"button",
"",
"submit",
() => {
this.banAPI(reason);
menu.hide();
},
],
]);
menu.show();
}
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}`, {
method: "PUT",
headers,
});
}
hasPermission(name: string): boolean {
if (this.isAdmin()) {
return true;
}
for (const thing of this.roles) {
if (thing.permissions.getPermission(name)) {
return true;
}
}
return false;
}
}
export { Member };

File diff suppressed because it is too large Load diff

View file

@ -1,347 +1,347 @@
class Permissions { class Permissions {
allow: bigint; allow: bigint;
deny: bigint; deny: bigint;
readonly hasDeny: boolean; readonly hasDeny: boolean;
constructor(allow: string, deny: string = "") { constructor(allow: string, deny: string = "") {
this.hasDeny = Boolean(deny); this.hasDeny = Boolean(deny);
try { try {
this.allow = BigInt(allow); this.allow = BigInt(allow);
this.deny = BigInt(deny); this.deny = BigInt(deny);
} catch { } catch {
this.allow = 0n; this.allow = 0n;
this.deny = 0n; this.deny = 0n;
console.error( console.error(
`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` `Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.`
); );
} }
} }
getPermissionbit(b: number, big: bigint): boolean { getPermissionbit(b: number, big: bigint): boolean {
return Boolean((big >> BigInt(b)) & 1n); 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); 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
} }
static map: { static map: {
[key: number | string]: [key: number | string]:
| { name: string; readableName: string; description: string } | { name: string; readableName: string; description: string }
| number; | number;
}; };
static info: { name: string; readableName: string; description: string }[]; static info: { name: string; readableName: string; description: string }[];
static makeMap() { static makeMap() {
Permissions.info = [ Permissions.info = [
//for people in the future, do not reorder these, the creation of the map realize on the order //for people in the future, do not reorder these, the creation of the map realize on the order
{ {
name: "CREATE_INSTANT_INVITE", name: "CREATE_INSTANT_INVITE",
readableName: "Create invite", readableName: "Create invite",
description: "Allows the user to create invites for the guild", description: "Allows the user to create invites for the guild",
}, },
{ {
name: "KICK_MEMBERS", name: "KICK_MEMBERS",
readableName: "Kick members", readableName: "Kick members",
description: "Allows the user to kick members from the guild", description: "Allows the user to kick members from the guild",
}, },
{ {
name: "BAN_MEMBERS", name: "BAN_MEMBERS",
readableName: "Ban members", readableName: "Ban members",
description: "Allows the user to ban members from the guild", description: "Allows the user to ban members from the guild",
}, },
{ {
name: "ADMINISTRATOR", name: "ADMINISTRATOR",
readableName: "Administrator", readableName: "Administrator",
description: description:
"Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!",
}, },
{ {
name: "MANAGE_CHANNELS", name: "MANAGE_CHANNELS",
readableName: "Manage channels", readableName: "Manage channels",
description: "Allows the user to manage and edit channels", description: "Allows the user to manage and edit channels",
}, },
{ {
name: "MANAGE_GUILD", name: "MANAGE_GUILD",
readableName: "Manage guild", readableName: "Manage guild",
description: "Allows management and editing of the guild", description: "Allows management and editing of the guild",
}, },
{ {
name: "ADD_REACTIONS", name: "ADD_REACTIONS",
readableName: "Add reactions", readableName: "Add reactions",
description: "Allows user to add reactions to messages", description: "Allows user to add reactions to messages",
}, },
{ {
name: "VIEW_AUDIT_LOG", name: "VIEW_AUDIT_LOG",
readableName: "View audit log", readableName: "View audit log",
description: "Allows the user to view the audit log", description: "Allows the user to view the audit log",
}, },
{ {
name: "PRIORITY_SPEAKER", name: "PRIORITY_SPEAKER",
readableName: "Priority speaker", readableName: "Priority speaker",
description: "Allows for using priority speaker in a voice channel", description: "Allows for using priority speaker in a voice channel",
}, },
{ {
name: "STREAM", name: "STREAM",
readableName: "Video", readableName: "Video",
description: "Allows the user to stream", description: "Allows the user to stream",
}, },
{ {
name: "VIEW_CHANNEL", name: "VIEW_CHANNEL",
readableName: "View channels", readableName: "View channels",
description: "Allows the user to view the channel", description: "Allows the user to view the channel",
}, },
{ {
name: "SEND_MESSAGES", name: "SEND_MESSAGES",
readableName: "Send messages", readableName: "Send messages",
description: "Allows user to send messages", description: "Allows user to send messages",
}, },
{ {
name: "SEND_TTS_MESSAGES", name: "SEND_TTS_MESSAGES",
readableName: "Send text-to-speech messages", readableName: "Send text-to-speech messages",
description: "Allows the user to send text-to-speech messages", description: "Allows the user to send text-to-speech messages",
}, },
{ {
name: "MANAGE_MESSAGES", name: "MANAGE_MESSAGES",
readableName: "Manage messages", readableName: "Manage messages",
description: "Allows the user to delete messages that aren't their own", description: "Allows the user to delete messages that aren't their own",
}, },
{ {
name: "EMBED_LINKS", name: "EMBED_LINKS",
readableName: "Embed links", readableName: "Embed links",
description: "Allow links sent by this user to auto-embed", description: "Allow links sent by this user to auto-embed",
}, },
{ {
name: "ATTACH_FILES", name: "ATTACH_FILES",
readableName: "Attach files", readableName: "Attach files",
description: "Allows the user to attach files", description: "Allows the user to attach files",
}, },
{ {
name: "READ_MESSAGE_HISTORY", name: "READ_MESSAGE_HISTORY",
readableName: "Read message history", readableName: "Read message history",
description: "Allows user to read the message history", description: "Allows user to read the message history",
}, },
{ {
name: "MENTION_EVERYONE", name: "MENTION_EVERYONE",
readableName: "Mention @everyone, @here and all roles", readableName: "Mention @everyone, @here and all roles",
description: "Allows the user to mention everyone", description: "Allows the user to mention everyone",
}, },
{ {
name: "USE_EXTERNAL_EMOJIS", name: "USE_EXTERNAL_EMOJIS",
readableName: "Use external emojis", readableName: "Use external emojis",
description: "Allows the user to use external emojis", description: "Allows the user to use external emojis",
}, },
{ {
name: "VIEW_GUILD_INSIGHTS", name: "VIEW_GUILD_INSIGHTS",
readableName: "View guild insights", readableName: "View guild insights",
description: "Allows the user to see guild insights", description: "Allows the user to see guild insights",
}, },
{ {
name: "CONNECT", name: "CONNECT",
readableName: "Connect", readableName: "Connect",
description: "Allows the user to connect to a voice channel", description: "Allows the user to connect to a voice channel",
}, },
{ {
name: "SPEAK", name: "SPEAK",
readableName: "Speak", readableName: "Speak",
description: "Allows the user to speak in a voice channel", description: "Allows the user to speak in a voice channel",
}, },
{ {
name: "MUTE_MEMBERS", name: "MUTE_MEMBERS",
readableName: "Mute members", readableName: "Mute members",
description: "Allows user to mute other members", description: "Allows user to mute other members",
}, },
{ {
name: "DEAFEN_MEMBERS", name: "DEAFEN_MEMBERS",
readableName: "Deafen members", readableName: "Deafen members",
description: "Allows user to deafen other members", description: "Allows user to deafen other members",
}, },
{ {
name: "MOVE_MEMBERS", name: "MOVE_MEMBERS",
readableName: "Move members", readableName: "Move members",
description: "Allows the user to move members between voice channels", description: "Allows the user to move members between voice channels",
}, },
{ {
name: "USE_VAD", name: "USE_VAD",
readableName: "Use voice activity detection", readableName: "Use voice activity detection",
description: description:
"Allows users to speak in a voice channel by simply talking", "Allows users to speak in a voice channel by simply talking",
}, },
{ {
name: "CHANGE_NICKNAME", name: "CHANGE_NICKNAME",
readableName: "Change nickname", readableName: "Change nickname",
description: "Allows the user to change their own nickname", description: "Allows the user to change their own nickname",
}, },
{ {
name: "MANAGE_NICKNAMES", name: "MANAGE_NICKNAMES",
readableName: "Manage nicknames", readableName: "Manage nicknames",
description: "Allows user to change nicknames of other members", description: "Allows user to change nicknames of other members",
}, },
{ {
name: "MANAGE_ROLES", name: "MANAGE_ROLES",
readableName: "Manage roles", readableName: "Manage roles",
description: "Allows user to edit and manage roles", description: "Allows user to edit and manage roles",
}, },
{ {
name: "MANAGE_WEBHOOKS", name: "MANAGE_WEBHOOKS",
readableName: "Manage webhooks", readableName: "Manage webhooks",
description: "Allows management and editing of webhooks", description: "Allows management and editing of webhooks",
}, },
{ {
name: "MANAGE_GUILD_EXPRESSIONS", name: "MANAGE_GUILD_EXPRESSIONS",
readableName: "Manage expressions", readableName: "Manage expressions",
description: "Allows for managing emoji, stickers, and soundboards", description: "Allows for managing emoji, stickers, and soundboards",
}, },
{ {
name: "USE_APPLICATION_COMMANDS", name: "USE_APPLICATION_COMMANDS",
readableName: "Use application commands", readableName: "Use application commands",
description: "Allows the user to use application commands", description: "Allows the user to use application commands",
}, },
{ {
name: "REQUEST_TO_SPEAK", name: "REQUEST_TO_SPEAK",
readableName: "Request to speak", readableName: "Request to speak",
description: "Allows user to request to speak in stage channel", description: "Allows user to request to speak in stage channel",
}, },
{ {
name: "MANAGE_EVENTS", name: "MANAGE_EVENTS",
readableName: "Manage events", readableName: "Manage events",
description: "Allows user to edit and manage events", description: "Allows user to edit and manage events",
}, },
{ {
name: "MANAGE_THREADS", name: "MANAGE_THREADS",
readableName: "Manage threads", readableName: "Manage threads",
description: description:
"Allows the user to delete and archive threads and view all private threads", "Allows the user to delete and archive threads and view all private threads",
}, },
{ {
name: "CREATE_PUBLIC_THREADS", name: "CREATE_PUBLIC_THREADS",
readableName: "Create public threads", readableName: "Create public threads",
description: "Allows the user to create public threads", description: "Allows the user to create public threads",
}, },
{ {
name: "CREATE_PRIVATE_THREADS", name: "CREATE_PRIVATE_THREADS",
readableName: "Create private threads", readableName: "Create private threads",
description: "Allows the user to create private threads", description: "Allows the user to create private threads",
}, },
{ {
name: "USE_EXTERNAL_STICKERS", name: "USE_EXTERNAL_STICKERS",
readableName: "Use external stickers", readableName: "Use external stickers",
description: "Allows user to use external stickers", description: "Allows user to use external stickers",
}, },
{ {
name: "SEND_MESSAGES_IN_THREADS", name: "SEND_MESSAGES_IN_THREADS",
readableName: "Send messages in threads", readableName: "Send messages in threads",
description: "Allows the user to send messages in threads", description: "Allows the user to send messages in threads",
}, },
{ {
name: "USE_EMBEDDED_ACTIVITIES", name: "USE_EMBEDDED_ACTIVITIES",
readableName: "Use activities", readableName: "Use activities",
description: "Allows the user to use embedded activities", description: "Allows the user to use embedded activities",
}, },
{ {
name: "MODERATE_MEMBERS", name: "MODERATE_MEMBERS",
readableName: "Timeout members", readableName: "Timeout members",
description: description:
"Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels", "Allows the user to time out other users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels",
}, },
{ {
name: "VIEW_CREATOR_MONETIZATION_ANALYTICS", name: "VIEW_CREATOR_MONETIZATION_ANALYTICS",
readableName: "View creator monetization analytics", readableName: "View creator monetization analytics",
description: "Allows for viewing role subscription insights", description: "Allows for viewing role subscription insights",
}, },
{ {
name: "USE_SOUNDBOARD", name: "USE_SOUNDBOARD",
readableName: "Use soundboard", readableName: "Use soundboard",
description: "Allows for using soundboard in a voice channel", description: "Allows for using soundboard in a voice channel",
}, },
{ {
name: "CREATE_GUILD_EXPRESSIONS", name: "CREATE_GUILD_EXPRESSIONS",
readableName: "Create expressions", readableName: "Create expressions",
description: description:
"Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.",
}, },
{ {
name: "CREATE_EVENTS", name: "CREATE_EVENTS",
readableName: "Create events", readableName: "Create events",
description: description:
"Allows for creating scheduled events, and editing and deleting those created by the current user.", "Allows for creating scheduled events, and editing and deleting those created by the current user.",
}, },
{ {
name: "USE_EXTERNAL_SOUNDS", name: "USE_EXTERNAL_SOUNDS",
readableName: "Use external sounds", readableName: "Use external sounds",
description: description:
"Allows the usage of custom soundboard sounds from other servers", "Allows the usage of custom soundboard sounds from other servers",
}, },
{ {
name: "SEND_VOICE_MESSAGES", name: "SEND_VOICE_MESSAGES",
readableName: "Send voice messages", readableName: "Send voice messages",
description: "Allows sending voice messages", description: "Allows sending voice messages",
}, },
{ {
name: "SEND_POLLS", name: "SEND_POLLS",
readableName: "Create polls", readableName: "Create polls",
description: "Allows sending polls", description: "Allows sending polls",
}, },
{ {
name: "USE_EXTERNAL_APPS", name: "USE_EXTERNAL_APPS",
readableName: "Use external apps", readableName: "Use external apps",
description: description:
"Allows user-installed apps to send public responses. " + "Allows user-installed apps to send public responses. " +
"When disabled, users will still be allowed to use their apps but the responses will be ephemeral. " + "When disabled, users will still be allowed to use their apps but the responses will be ephemeral. " +
"This only applies to apps not also installed to the server.", "This only applies to apps not also installed to the server.",
}, },
]; ];
Permissions.map = {}; Permissions.map = {};
let i = 0; let i = 0;
for (const thing of Permissions.info) { for (const thing of Permissions.info) {
Permissions.map[i] = thing; Permissions.map[i] = thing;
Permissions.map[thing.name] = i; Permissions.map[thing.name] = i;
i++; i++;
} }
} }
getPermission(name: string): number { getPermission(name: string): number {
if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) { if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) {
return 1; return 1;
} else if ( } else if (
this.getPermissionbit(Permissions.map[name] as number, this.deny) this.getPermissionbit(Permissions.map[name] as number, this.deny)
) { ) {
return -1; return -1;
} else { } else {
return 0; return 0;
} }
} }
hasPermission(name: string): boolean { hasPermission(name: string): boolean {
if (this.deny) { if (this.deny) {
console.warn( console.warn(
"This function may of been used in error, think about using getPermision instead" "This function may of been used in error, think about using getPermision instead"
); );
} }
if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) if (this.getPermissionbit(Permissions.map[name] as number, this.allow))
return true; return true;
if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR"); if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR");
return false; return false;
} }
setPermission(name: string, setto: number): void { setPermission(name: string, setto: number): void {
const bit = Permissions.map[name] as number; const bit = Permissions.map[name] as number;
if (!bit) { if (!bit) {
return console.error( return console.error(
"Tried to set permission to " + "Tried to set permission to " +
setto + setto +
" for " + " for " +
name + name +
" but it doesn't exist" " but it doesn't exist"
); );
} }
if (setto === 0) { if (setto === 0) {
this.deny = this.setPermissionbit(bit, false, this.deny); this.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow); 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.deny = this.setPermissionbit(bit, false, this.deny);
this.allow = this.setPermissionbit(bit, true, this.allow); 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.deny = this.setPermissionbit(bit, true, this.deny);
this.allow = this.setPermissionbit(bit, false, this.allow); this.allow = this.setPermissionbit(bit, false, this.allow);
} else { } else {
console.error("invalid number entered:" + setto); console.error("invalid number entered:" + setto);
} }
} }
} }
Permissions.makeMap(); Permissions.makeMap();
export { Permissions }; export { Permissions };

View file

@ -2,149 +2,149 @@ import { checkInstance, adduser } from "./login.js";
const registerElement = document.getElementById("register"); const registerElement = document.getElementById("register");
if (registerElement) { if (registerElement) {
registerElement.addEventListener("submit", registertry); registerElement.addEventListener("submit", registertry);
} }
async function registertry(e: Event) { async function registertry(e: Event) {
e.preventDefault(); e.preventDefault();
const elements = (e.target as HTMLFormElement) const elements = (e.target as HTMLFormElement)
.elements as HTMLFormControlsCollection; .elements as HTMLFormControlsCollection;
const email = (elements[1] as HTMLInputElement).value; const email = (elements[1] as HTMLInputElement).value;
const username = (elements[2] as HTMLInputElement).value; const username = (elements[2] as HTMLInputElement).value;
const password = (elements[3] as HTMLInputElement).value; const password = (elements[3] as HTMLInputElement).value;
const confirmPassword = (elements[4] as HTMLInputElement).value; const confirmPassword = (elements[4] as HTMLInputElement).value;
const dateofbirth = (elements[5] as HTMLInputElement).value; const dateofbirth = (elements[5] as HTMLInputElement).value;
const consent = (elements[6] as HTMLInputElement).checked; const consent = (elements[6] as HTMLInputElement).checked;
const captchaKey = (elements[7] as HTMLInputElement)?.value; const captchaKey = (elements[7] as HTMLInputElement)?.value;
if (password !== confirmPassword) { if (password !== confirmPassword) {
(document.getElementById("wrong") as HTMLElement).textContent = (document.getElementById("wrong") as HTMLElement).textContent =
"Passwords don't match"; "Passwords don't match";
return; return;
} }
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api); const apiurl = new URL(instanceInfo.api);
try { try {
const response = await fetch(apiurl + "/auth/register", { const response = await fetch(apiurl + "/auth/register", {
body: JSON.stringify({ body: JSON.stringify({
date_of_birth: dateofbirth, date_of_birth: dateofbirth,
email, email,
username, username,
password, password,
consent, consent,
captcha_key: captchaKey, captcha_key: captchaKey,
}), }),
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
}, },
method: "POST", method: "POST",
}); });
const data = await response.json(); const data = await response.json();
if (data.captcha_sitekey) { if (data.captcha_sitekey) {
const capt = document.getElementById("h-captcha"); const capt = document.getElementById("h-captcha");
if (capt && !capt.children.length) { if (capt && !capt.children.length) {
const capty = document.createElement("div"); const capty = document.createElement("div");
capty.classList.add("h-captcha"); capty.classList.add("h-captcha");
capty.setAttribute("data-sitekey", data.captcha_sitekey); capty.setAttribute("data-sitekey", data.captcha_sitekey);
const script = document.createElement("script"); const script = document.createElement("script");
script.src = "https://js.hcaptcha.com/1/api.js"; script.src = "https://js.hcaptcha.com/1/api.js";
capt.append(script); capt.append(script);
capt.append(capty); capt.append(capty);
} else { } else {
eval("hcaptcha.reset()"); eval("hcaptcha.reset()");
} }
return; return;
} }
if (!data.token) { if (!data.token) {
handleErrors(data.errors, elements); handleErrors(data.errors, elements);
} else { } else {
adduser({ adduser({
serverurls: instanceInfo, serverurls: instanceInfo,
email, email,
token: data.token, token: data.token,
}).username = username; }).username = username;
localStorage.setItem("token", data.token); localStorage.setItem("token", data.token);
const redir = new URLSearchParams(window.location.search).get("goback"); const redir = new URLSearchParams(window.location.search).get("goback");
window.location.href = redir ? redir : "/channels/@me"; window.location.href = redir ? redir : "/channels/@me";
} }
} catch (error) { } catch (error) {
console.error("Registration failed:", error); console.error("Registration failed:", error);
} }
} }
function handleErrors(errors: any, elements: HTMLFormControlsCollection) { function handleErrors(errors: any, elements: HTMLFormControlsCollection) {
if (errors.consent) { if (errors.consent) {
showError(elements[6] as HTMLElement, errors.consent._errors[0].message); showError(elements[6] as HTMLElement, errors.consent._errors[0].message);
} else if (errors.password) { } else if (errors.password) {
showError( showError(
elements[3] as HTMLElement, elements[3] as HTMLElement,
"Password: " + errors.password._errors[0].message "Password: " + errors.password._errors[0].message
); );
} else if (errors.username) { } else if (errors.username) {
showError( showError(
elements[2] as HTMLElement, elements[2] as HTMLElement,
"Username: " + errors.username._errors[0].message "Username: " + errors.username._errors[0].message
); );
} else if (errors.email) { } else if (errors.email) {
showError( showError(
elements[1] as HTMLElement, elements[1] as HTMLElement,
"Email: " + errors.email._errors[0].message "Email: " + errors.email._errors[0].message
); );
} else if (errors.date_of_birth) { } else if (errors.date_of_birth) {
showError( showError(
elements[5] as HTMLElement, elements[5] as HTMLElement,
"Date of Birth: " + errors.date_of_birth._errors[0].message "Date of Birth: " + errors.date_of_birth._errors[0].message
); );
} else { } else {
(document.getElementById("wrong") as HTMLElement).textContent = (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!; const parent = element.parentElement!;
let errorElement = parent.getElementsByClassName( let errorElement = parent.getElementsByClassName(
"suberror" "suberror"
)[0] as HTMLElement; )[0] as HTMLElement;
if (!errorElement) { if (!errorElement) {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("suberror", "suberrora"); div.classList.add("suberror", "suberrora");
parent.append(div); parent.append(div);
errorElement = div; errorElement = div;
} else { } else {
errorElement.classList.remove("suberror"); errorElement.classList.remove("suberror");
setTimeout(() => { setTimeout(() => {
errorElement.classList.add("suberror"); errorElement.classList.add("suberror");
}, 100); }, 100);
} }
errorElement.textContent = message; errorElement.textContent = message;
} }
let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null; let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null;
async function tosLogic() { async function tosLogic() {
const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}");
const apiurl = new URL(instanceInfo.api); const apiurl = new URL(instanceInfo.api);
const response = await fetch(apiurl.toString() + "/ping"); const response = await fetch(apiurl.toString() + "/ping");
const data = await response.json(); const data = await response.json();
const tosPage = data.instance.tosPage; const tosPage = data.instance.tosPage;
if (tosPage) { if (tosPage) {
document.getElementById("TOSbox")!.innerHTML = document.getElementById("TOSbox")!.innerHTML =
'I agree to the <a href="" id="TOSa">Terms of Service</a>:'; 'I agree to the <a href="" id="TOSa">Terms of Service</a>:';
TOSa = document.getElementById("TOSa") as HTMLAnchorElement; TOSa = document.getElementById("TOSa") as HTMLAnchorElement;
TOSa.href = tosPage; TOSa.href = tosPage;
} else { } else {
document.getElementById("TOSbox")!.textContent = document.getElementById("TOSbox")!.textContent =
"This instance has no Terms of Service, accept ToS anyways:"; "This instance has no Terms of Service, accept ToS anyways:";
TOSa = null; TOSa = null;
} }
console.log(tosPage); console.log(tosPage);
} }
tosLogic(); tosLogic();

View file

@ -4,177 +4,177 @@ import { Guild } from "./guild.js";
import { SnowFlake } from "./snowflake.js"; import { SnowFlake } from "./snowflake.js";
import { rolesjson } from "./jsontypes.js"; import { rolesjson } from "./jsontypes.js";
class Role extends SnowFlake { class Role extends SnowFlake {
permissions: Permissions; permissions: Permissions;
owner: Guild; owner: Guild;
color!: number; color!: number;
name!: string; name!: string;
info: Guild["info"]; info: Guild["info"];
hoist!: boolean; hoist!: boolean;
icon!: string; icon!: string;
mentionable!: boolean; mentionable!: boolean;
unicode_emoji!: string; unicode_emoji!: string;
headers: Guild["headers"]; headers: Guild["headers"];
constructor(json: rolesjson, owner: Guild) { constructor(json: rolesjson, owner: Guild) {
super(json.id); super(json.id);
this.headers = owner.headers; this.headers = owner.headers;
this.info = owner.info; this.info = owner.info;
for (const thing of Object.keys(json)) { for (const thing of Object.keys(json)) {
if (thing === "id") { if (thing === "id") {
continue; continue;
} }
(this as any)[thing] = (json as any)[thing]; (this as any)[thing] = (json as any)[thing];
} }
this.permissions = new Permissions(json.permissions); this.permissions = new Permissions(json.permissions);
this.owner = owner; this.owner = owner;
} }
get guild(): Guild { get guild(): Guild {
return this.owner; return this.owner;
} }
get localuser(): Localuser { get localuser(): Localuser {
return this.guild.localuser; return this.guild.localuser;
} }
getColor(): string | null { getColor(): string | null {
if (this.color === 0) { if (this.color === 0) {
return null; return null;
} }
return `#${this.color.toString(16)}`; return `#${this.color.toString(16)}`;
} }
} }
export { Role }; export { Role };
import { Options } from "./settings.js"; import { Options } from "./settings.js";
class PermissionToggle implements OptionsElement<number> { class PermissionToggle implements OptionsElement<number> {
readonly rolejson: { readonly rolejson: {
name: string; name: string;
readableName: string; readableName: string;
description: string; description: string;
}; };
permissions: Permissions; permissions: Permissions;
owner: Options; owner: Options;
value!: number; value!: number;
constructor( constructor(
roleJSON: PermissionToggle["rolejson"], roleJSON: PermissionToggle["rolejson"],
permissions: Permissions, permissions: Permissions,
owner: Options owner: Options
) { ) {
this.rolejson = roleJSON; this.rolejson = roleJSON;
this.permissions = permissions; this.permissions = permissions;
this.owner = owner; this.owner = owner;
} }
watchForChange() {} watchForChange() {}
generateHTML(): HTMLElement { generateHTML(): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("setting"); div.classList.add("setting");
const name = document.createElement("span"); const name = document.createElement("span");
name.textContent = this.rolejson.readableName; name.textContent = this.rolejson.readableName;
name.classList.add("settingsname"); name.classList.add("settingsname");
div.append(name); div.append(name);
div.append(this.generateCheckbox()); div.append(this.generateCheckbox());
const p = document.createElement("p"); const p = document.createElement("p");
p.textContent = this.rolejson.description; p.textContent = this.rolejson.description;
div.appendChild(p); div.appendChild(p);
return div; return div;
} }
generateCheckbox(): HTMLElement { generateCheckbox(): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("tritoggle"); div.classList.add("tritoggle");
const state = this.permissions.getPermission(this.rolejson.name); const state = this.permissions.getPermission(this.rolejson.name);
const on = document.createElement("input"); const on = document.createElement("input");
on.type = "radio"; on.type = "radio";
on.name = this.rolejson.name; on.name = this.rolejson.name;
div.append(on); div.append(on);
if (state === 1) { if (state === 1) {
on.checked = true; on.checked = true;
} }
on.onclick = (_) => { on.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 1); this.permissions.setPermission(this.rolejson.name, 1);
this.owner.changed(); this.owner.changed();
}; };
const no = document.createElement("input"); const no = document.createElement("input");
no.type = "radio"; no.type = "radio";
no.name = this.rolejson.name; no.name = this.rolejson.name;
div.append(no); div.append(no);
if (state === 0) { if (state === 0) {
no.checked = true; no.checked = true;
} }
no.onclick = (_) => { no.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, 0); this.permissions.setPermission(this.rolejson.name, 0);
this.owner.changed(); this.owner.changed();
}; };
if (this.permissions.hasDeny) { if (this.permissions.hasDeny) {
const off = document.createElement("input"); const off = document.createElement("input");
off.type = "radio"; off.type = "radio";
off.name = this.rolejson.name; off.name = this.rolejson.name;
div.append(off); div.append(off);
if (state === -1) { if (state === -1) {
off.checked = true; off.checked = true;
} }
off.onclick = (_) => { off.onclick = (_) => {
this.permissions.setPermission(this.rolejson.name, -1); this.permissions.setPermission(this.rolejson.name, -1);
this.owner.changed(); this.owner.changed();
}; };
} }
return div; return div;
} }
submit() {} submit() {}
} }
import { OptionsElement, Buttons } from "./settings.js"; import { OptionsElement, Buttons } from "./settings.js";
class RoleList extends Buttons { class RoleList extends Buttons {
readonly permissions: [Role, Permissions][]; readonly permissions: [Role, Permissions][];
permission: Permissions; permission: Permissions;
readonly guild: Guild; readonly guild: Guild;
readonly channel: boolean; readonly channel: boolean;
declare readonly buttons: [string, string][]; declare readonly buttons: [string, string][];
readonly options: Options; readonly options: Options;
onchange: Function; onchange: Function;
curid!: string; curid!: string;
constructor( constructor(
permissions: [Role, Permissions][], permissions: [Role, Permissions][],
guild: Guild, guild: Guild,
onchange: Function, onchange: Function,
channel = false channel = false
) { ) {
super("Roles"); super("Roles");
this.guild = guild; this.guild = guild;
this.permissions = permissions; this.permissions = permissions;
this.channel = channel; this.channel = channel;
this.onchange = onchange; this.onchange = onchange;
const options = new Options("", this); const options = new Options("", this);
if (channel) { if (channel) {
this.permission = new Permissions("0", "0"); this.permission = new Permissions("0", "0");
} else { } else {
this.permission = new Permissions("0"); this.permission = new Permissions("0");
} }
for (const thing of Permissions.info) { for (const thing of Permissions.info) {
options.options.push( options.options.push(
new PermissionToggle(thing, this.permission, options) new PermissionToggle(thing, this.permission, options)
); );
} }
for (const i of permissions) { for (const i of permissions) {
console.log(i); console.log(i);
this.buttons.push([i[0].name, i[0].id]); this.buttons.push([i[0].name, i[0].id]);
} }
this.options = options; this.options = options;
} }
handleString(str: string): HTMLElement { handleString(str: string): HTMLElement {
this.curid = str; this.curid = str;
const arr = this.permissions.find((_) => _[0].id === str); const arr = this.permissions.find((_) => _[0].id === str);
if (arr) { if (arr) {
const perm = arr[1]; const perm = arr[1];
this.permission.deny = perm.deny; this.permission.deny = perm.deny;
this.permission.allow = perm.allow; this.permission.allow = perm.allow;
const role = this.permissions.find((e) => e[0].id === str); const role = this.permissions.find((e) => e[0].id === str);
if (role) { if (role) {
this.options.name = role[0].name; this.options.name = role[0].name;
this.options.haschanged = false; this.options.haschanged = false;
} }
} }
return this.options.generateHTML(); return this.options.generateHTML();
} }
save() { save() {
this.onchange(this.curid, this.permission); this.onchange(this.curid, this.permission);
} }
} }
export { RoleList }; export { RoleList };

View file

@ -1,96 +1,96 @@
function deleteoldcache() { function deleteoldcache() {
caches.delete("cache"); caches.delete("cache");
console.log("this ran :P"); 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); console.log(request, response);
const cache = await caches.open("cache"); const cache = await caches.open("cache");
console.log("Grabbed"); console.log("Grabbed");
try { try {
console.log(await cache.put(request, response)); console.log(await cache.put(request, response));
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
} }
console.log("test"); console.log("test");
let lastcache: string; let lastcache: string;
self.addEventListener("activate", async () => { self.addEventListener("activate", async () => {
console.log("test2"); console.log("test2");
checkCache(); checkCache();
}); });
async function checkCache() { async function checkCache() {
if (checkedrecently) { if (checkedrecently) {
return; return;
} }
const promise = await caches.match("/getupdates"); const promise = await caches.match("/getupdates");
if (promise) { if (promise) {
lastcache = await promise.text(); lastcache = await promise.text();
} }
console.log(lastcache); console.log(lastcache);
fetch("/getupdates").then(async (data) => { fetch("/getupdates").then(async (data) => {
const text = await data.clone().text(); const text = await data.clone().text();
console.log(text, lastcache); console.log(text, lastcache);
if (lastcache !== text) { if (lastcache !== text) {
deleteoldcache(); deleteoldcache();
putInCache("/getupdates", data.clone()); putInCache("/getupdates", data.clone());
} }
checkedrecently = true; checkedrecently = true;
setTimeout((_: any) => { setTimeout((_: any) => {
checkedrecently = false; checkedrecently = false;
}, 1000 * 60 * 30); }, 1000 * 60 * 30);
}); });
} }
var checkedrecently = false; var checkedrecently = false;
function samedomain(url: string | URL) { function samedomain(url: string | URL) {
return new URL(url).origin === self.origin; return new URL(url).origin === self.origin;
} }
function isindexhtml(url: string | URL) { function isindexhtml(url: string | URL) {
console.log(url); console.log(url);
if (new URL(url).pathname.startsWith("/channels")) { if (new URL(url).pathname.startsWith("/channels")) {
return true; return true;
} }
return false; return false;
} }
async function getfile(event: { async function getfile(event: {
request: { url: URL | RequestInfo; clone: () => string | URL | Request }; request: { url: URL | RequestInfo; clone: () => string | URL | Request };
}) { }) {
checkCache(); checkCache();
if (!samedomain(event.request.url.toString())) { if (!samedomain(event.request.url.toString())) {
return await fetch(event.request.clone()); return await fetch(event.request.clone());
} }
const responseFromCache = await caches.match(event.request.url); const responseFromCache = await caches.match(event.request.url);
console.log(responseFromCache, caches); console.log(responseFromCache, caches);
if (responseFromCache) { if (responseFromCache) {
console.log("cache hit"); console.log("cache hit");
return responseFromCache; return responseFromCache;
} }
if (isindexhtml(event.request.url.toString())) { if (isindexhtml(event.request.url.toString())) {
console.log("is index.html"); console.log("is index.html");
const responseFromCache = await caches.match("/index.html"); const responseFromCache = await caches.match("/index.html");
if (responseFromCache) { if (responseFromCache) {
console.log("cache hit"); console.log("cache hit");
return responseFromCache; return responseFromCache;
} }
const responseFromNetwork = await fetch("/index.html"); const responseFromNetwork = await fetch("/index.html");
await putInCache("/index.html", responseFromNetwork.clone()); await putInCache("/index.html", responseFromNetwork.clone());
return responseFromNetwork; return responseFromNetwork;
} }
const responseFromNetwork = await fetch(event.request.clone()); const responseFromNetwork = await fetch(event.request.clone());
console.log(event.request.clone()); console.log(event.request.clone());
await putInCache(event.request.clone(), responseFromNetwork.clone()); await putInCache(event.request.clone(), responseFromNetwork.clone());
try { try {
return responseFromNetwork; return responseFromNetwork;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return e; return e;
} }
} }
self.addEventListener("fetch", (event: any) => { self.addEventListener("fetch", (event: any) => {
try { try {
event.respondWith(getfile(event)); event.respondWith(getfile(event));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}); });

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,20 @@
abstract class SnowFlake { abstract class SnowFlake {
public readonly id: string; public readonly id: string;
constructor(id: string) { constructor(id: string) {
this.id = id; this.id = id;
} }
getUnixTime(): number { getUnixTime(): number {
return SnowFlake.stringToUnixTime(this.id); return SnowFlake.stringToUnixTime(this.id);
} }
static stringToUnixTime(str: string) { static stringToUnixTime(str: string) {
try { try {
return Number((BigInt(str) >> 22n) + 1420070400000n); return Number((BigInt(str) >> 22n) + 1420070400000n);
} catch { } catch {
console.error( console.error(
`The ID is corrupted, it's ${str} when it should be some number.` `The ID is corrupted, it's ${str} when it should be some number.`
); );
return 0; return 0;
} }
} }
} }
export { SnowFlake }; export { SnowFlake };

View file

@ -7,483 +7,483 @@ import { SnowFlake } from "./snowflake.js";
import { presencejson, userjson } from "./jsontypes.js"; import { presencejson, userjson } from "./jsontypes.js";
class User extends SnowFlake { class User extends SnowFlake {
owner: Localuser; owner: Localuser;
hypotheticalpfp!: boolean; hypotheticalpfp!: boolean;
avatar!: string | null; avatar!: string | null;
username!: string; username!: string;
nickname: string | null = null; nickname: string | null = null;
relationshipType: 0 | 1 | 2 | 3 | 4 = 0; relationshipType: 0 | 1 | 2 | 3 | 4 = 0;
bio!: MarkDown; bio!: MarkDown;
discriminator!: string; discriminator!: string;
pronouns!: string; pronouns!: string;
bot!: boolean; bot!: boolean;
public_flags!: number; public_flags!: number;
accent_color!: number; accent_color!: number;
banner: string | undefined; banner: string | undefined;
hypotheticalbanner!: boolean; hypotheticalbanner!: boolean;
premium_since!: string; premium_since!: string;
premium_type!: number; premium_type!: number;
theme_colors!: string; theme_colors!: string;
badge_ids!: string[]; badge_ids!: string[];
members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> = members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> =
new WeakMap(); new WeakMap();
private status!: string; private status!: string;
resolving: false | Promise<any> = false; resolving: false | Promise<any> = false;
constructor(userjson: userjson, owner: Localuser, dontclone = false) { constructor(userjson: userjson, owner: Localuser, dontclone = false) {
super(userjson.id); super(userjson.id);
this.owner = owner; this.owner = owner;
if (!owner) { if (!owner) {
console.error("missing localuser"); console.error("missing localuser");
} }
if (dontclone) { if (dontclone) {
for (const key of Object.keys(userjson)) { for (const key of Object.keys(userjson)) {
if (key === "bio") { if (key === "bio") {
this.bio = new MarkDown(userjson[key], this.localuser); this.bio = new MarkDown(userjson[key], this.localuser);
continue; continue;
} }
if (key === "id") { if (key === "id") {
continue; continue;
} }
(this as any)[key] = (userjson as any)[key]; (this as any)[key] = (userjson as any)[key];
} }
this.hypotheticalpfp = false; this.hypotheticalpfp = false;
} else { } else {
return User.checkuser(userjson, owner); return User.checkuser(userjson, owner);
} }
} }
clone(): User { clone(): User {
return new User( return new User(
{ {
username: this.username, username: this.username,
id: this.id + "#clone", id: this.id + "#clone",
public_flags: this.public_flags, public_flags: this.public_flags,
discriminator: this.discriminator, discriminator: this.discriminator,
avatar: this.avatar, avatar: this.avatar,
accent_color: this.accent_color, accent_color: this.accent_color,
banner: this.banner, banner: this.banner,
bio: this.bio.rawString, bio: this.bio.rawString,
premium_since: this.premium_since, premium_since: this.premium_since,
premium_type: this.premium_type, premium_type: this.premium_type,
bot: this.bot, bot: this.bot,
theme_colors: this.theme_colors, theme_colors: this.theme_colors,
pronouns: this.pronouns, pronouns: this.pronouns,
badge_ids: this.badge_ids, badge_ids: this.badge_ids,
}, },
this.owner this.owner
); );
} }
public getPresence(presence: presencejson | undefined): void { public getPresence(presence: presencejson | undefined): void {
if (presence) { if (presence) {
this.setstatus(presence.status); this.setstatus(presence.status);
} else { } else {
this.setstatus("offline"); this.setstatus("offline");
} }
} }
setstatus(status: string): void { setstatus(status: string): void {
this.status = status; this.status = status;
} }
async getStatus(): Promise<string> { async getStatus(): Promise<string> {
return this.status || "offline"; return this.status || "offline";
} }
static contextmenu = new Contextmenu<User, Member | undefined>("User Menu"); static contextmenu = new Contextmenu<User, Member | undefined>("User Menu");
static setUpContextMenu(): void { static setUpContextMenu(): void {
this.contextmenu.addbutton("Copy user id", function (this: User) { this.contextmenu.addbutton("Copy user id", function (this: User) {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
}); });
this.contextmenu.addbutton("Message user", function (this: User) { this.contextmenu.addbutton("Message user", function (this: User) {
fetch(this.info.api + "/users/@me/channels", { fetch(this.info.api + "/users/@me/channels", {
method: "POST", method: "POST",
body: JSON.stringify({ recipients: [this.id] }), body: JSON.stringify({ recipients: [this.id] }),
headers: this.localuser.headers, headers: this.localuser.headers,
}) })
.then((res) => res.json()) .then((res) => res.json())
.then((json) => { .then((json) => {
this.localuser.goToChannel(json.id); this.localuser.goToChannel(json.id);
}); });
}); });
this.contextmenu.addbutton( this.contextmenu.addbutton(
"Block user", "Block user",
function (this: User) { function (this: User) {
this.block(); this.block();
}, },
null, null,
function () { function () {
return this.relationshipType !== 2; return this.relationshipType !== 2;
} }
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
"Unblock user", "Unblock user",
function (this: User) { function (this: User) {
this.unblock(); this.unblock();
}, },
null, null,
function () { function () {
return this.relationshipType === 2; return this.relationshipType === 2;
} }
); );
this.contextmenu.addbutton("Friend request", function (this: User) { this.contextmenu.addbutton("Friend request", function (this: User) {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT", method: "PUT",
headers: this.owner.headers, headers: this.owner.headers,
body: JSON.stringify({ body: JSON.stringify({
type: 1, type: 1,
}), }),
}); });
}); });
this.contextmenu.addbutton( this.contextmenu.addbutton(
"Kick member", "Kick member",
function (this: User, member: Member | undefined) { function (this: User, member: Member | undefined) {
member?.kick(); member?.kick();
}, },
null, null,
(member) => { (member) => {
if (!member) return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
if (member.id === us.id) { if (member.id === us.id) {
return false; return false;
} }
if (member.id === member.guild.properties.owner_id) { if (member.id === member.guild.properties.owner_id) {
return false; return false;
} }
return us.hasPermission("KICK_MEMBERS") || false; return us.hasPermission("KICK_MEMBERS") || false;
} }
); );
this.contextmenu.addbutton( this.contextmenu.addbutton(
"Ban member", "Ban member",
function (this: User, member: Member | undefined) { function (this: User, member: Member | undefined) {
member?.ban(); member?.ban();
}, },
null, null,
(member) => { (member) => {
if (!member) return false; if (!member) return false;
const us = member.guild.member; const us = member.guild.member;
if (member.id === us.id) { if (member.id === us.id) {
return false; return false;
} }
if (member.id === member.guild.properties.owner_id) { if (member.id === member.guild.properties.owner_id) {
return false; return false;
} }
return us.hasPermission("BAN_MEMBERS") || false; return us.hasPermission("BAN_MEMBERS") || false;
} }
); );
} }
static checkuser(user: User | userjson, owner: Localuser): User { static checkuser(user: User | userjson, owner: Localuser): User {
if (owner.userMap.has(user.id)) { if (owner.userMap.has(user.id)) {
return owner.userMap.get(user.id) as User; return owner.userMap.get(user.id) as User;
} else { } else {
const tempuser = new User(user as userjson, owner, true); const tempuser = new User(user as userjson, owner, true);
owner.userMap.set(user.id, tempuser); owner.userMap.set(user.id, tempuser);
return tempuser; return tempuser;
} }
} }
get info() { get info() {
return this.owner.info; return this.owner.info;
} }
get localuser() { get localuser() {
return this.owner; return this.owner;
} }
get name() { get name() {
return this.username; return this.username;
} }
async resolvemember(guild: Guild): Promise<Member | undefined> { async resolvemember(guild: Guild): Promise<Member | undefined> {
return await Member.resolveMember(this, guild); return await Member.resolveMember(this, guild);
} }
async getUserProfile(): Promise<any> { async getUserProfile(): Promise<any> {
return await fetch( return await fetch(
`${this.info.api}/users/${this.id.replace( `${this.info.api}/users/${this.id.replace(
"#clone", "#clone",
"" ""
)}/profile?with_mutual_guilds=true&with_mutual_friends=true`, )}/profile?with_mutual_guilds=true&with_mutual_friends=true`,
{ {
headers: this.localuser.headers, headers: this.localuser.headers,
} }
).then((res) => res.json()); ).then((res) => res.json());
} }
async getBadge(id: string): Promise<any> { async getBadge(id: string): Promise<any> {
if (this.localuser.badges.has(id)) { if (this.localuser.badges.has(id)) {
return this.localuser.badges.get(id); return this.localuser.badges.get(id);
} else { } else {
if (this.resolving) { if (this.resolving) {
await this.resolving; await this.resolving;
return this.localuser.badges.get(id); return this.localuser.badges.get(id);
} }
const prom = await this.getUserProfile(); const prom = await this.getUserProfile();
this.resolving = prom; this.resolving = prom;
const badges = prom.badges; const badges = prom.badges;
this.resolving = false; this.resolving = false;
for (const badge of badges) { for (const badge of badges) {
this.localuser.badges.set(badge.id, badge); this.localuser.badges.set(badge.id, badge);
} }
return this.localuser.badges.get(id); return this.localuser.badges.get(id);
} }
} }
buildpfp(): HTMLImageElement { buildpfp(): HTMLImageElement {
const pfp = document.createElement("img"); const pfp = document.createElement("img");
pfp.loading = "lazy"; pfp.loading = "lazy";
pfp.src = this.getpfpsrc(); pfp.src = this.getpfpsrc();
pfp.classList.add("pfp"); pfp.classList.add("pfp");
pfp.classList.add("userid:" + this.id); pfp.classList.add("userid:" + this.id);
return pfp; return pfp;
} }
async buildstatuspfp(): Promise<HTMLDivElement> { async buildstatuspfp(): Promise<HTMLDivElement> {
const div = document.createElement("div"); const div = document.createElement("div");
div.style.position = "relative"; div.style.position = "relative";
const pfp = this.buildpfp(); const pfp = this.buildpfp();
div.append(pfp); div.append(pfp);
const status = document.createElement("div"); const status = document.createElement("div");
status.classList.add("statusDiv"); status.classList.add("statusDiv");
switch (await this.getStatus()) { switch (await this.getStatus()) {
case "offline": case "offline":
status.classList.add("offlinestatus"); status.classList.add("offlinestatus");
break; break;
case "online": case "online":
default: default:
status.classList.add("onlinestatus"); status.classList.add("onlinestatus");
break; break;
} }
div.append(status); div.append(status);
return div; return div;
} }
userupdate(json: userjson): void { userupdate(json: userjson): void {
if (json.avatar !== this.avatar) { if (json.avatar !== this.avatar) {
this.changepfp(json.avatar); this.changepfp(json.avatar);
} }
} }
bind(html: HTMLElement, guild: Guild | null = null, error = true): void { bind(html: HTMLElement, guild: Guild | null = null, error = true): void {
if (guild && guild.id !== "@me") { if (guild && guild.id !== "@me") {
Member.resolveMember(this, guild) Member.resolveMember(this, guild)
.then((member) => { .then((member) => {
User.contextmenu.bindContextmenu(html, this, member); User.contextmenu.bindContextmenu(html, this, member);
if (member === undefined && error) { if (member === undefined && error) {
const errorSpan = document.createElement("span"); const errorSpan = document.createElement("span");
errorSpan.textContent = "!"; errorSpan.textContent = "!";
errorSpan.classList.add("membererror"); errorSpan.classList.add("membererror");
html.after(errorSpan); html.after(errorSpan);
return; return;
} }
if (member) { if (member) {
member.bind(html); member.bind(html);
} }
}) })
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
}); });
} }
if (guild) { if (guild) {
this.profileclick(html, guild); this.profileclick(html, guild);
} else { } else {
this.profileclick(html); this.profileclick(html);
} }
} }
static async resolve(id: string, localuser: Localuser): Promise<User> { static async resolve(id: string, localuser: Localuser): Promise<User> {
const json = await fetch( const json = await fetch(
localuser.info.api.toString() + "/users/" + id + "/profile", localuser.info.api.toString() + "/users/" + id + "/profile",
{ headers: localuser.headers } { headers: localuser.headers }
).then((res) => res.json()); ).then((res) => res.json());
return new User(json, localuser); return new User(json, localuser);
} }
changepfp(update: string | null): void { changepfp(update: string | null): void {
this.avatar = update; this.avatar = update;
this.hypotheticalpfp = false; this.hypotheticalpfp = false;
const src = this.getpfpsrc(); const src = this.getpfpsrc();
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( Array.from(document.getElementsByClassName("userid:" + this.id)).forEach(
(element) => { (element) => {
(element as HTMLImageElement).src = src; (element as HTMLImageElement).src = src;
} }
); );
} }
block(): void { block(): void {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "PUT", method: "PUT",
headers: this.owner.headers, headers: this.owner.headers,
body: JSON.stringify({ body: JSON.stringify({
type: 2, type: 2,
}), }),
}); });
this.relationshipType = 2; this.relationshipType = 2;
const channel = this.localuser.channelfocus; const channel = this.localuser.channelfocus;
if (channel) { if (channel) {
for (const message of channel.messages) { for (const message of channel.messages) {
message[1].generateMessage(); message[1].generateMessage();
} }
} }
} }
unblock(): void { unblock(): void {
fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
method: "DELETE", method: "DELETE",
headers: this.owner.headers, headers: this.owner.headers,
}); });
this.relationshipType = 0; this.relationshipType = 0;
const channel = this.localuser.channelfocus; const channel = this.localuser.channelfocus;
if (channel) { if (channel) {
for (const message of channel.messages) { for (const message of channel.messages) {
message[1].generateMessage(); message[1].generateMessage();
} }
} }
} }
getpfpsrc(): string { getpfpsrc(): string {
if (this.hypotheticalpfp && this.avatar) { if (this.hypotheticalpfp && this.avatar) {
return this.avatar; return this.avatar;
} }
if (this.avatar !== null) { if (this.avatar !== null) {
return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${
this.avatar this.avatar
}.png`; }.png`;
} else { } else {
const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n);
return `${this.info.cdn}/embed/avatars/${int}.png`; return `${this.info.cdn}/embed/avatars/${int}.png`;
} }
} }
async buildprofile( async buildprofile(
x: number, x: number,
y: number, y: number,
guild: Guild | null = null guild: Guild | null = null
): Promise<HTMLDivElement> { ): Promise<HTMLDivElement> {
if (Contextmenu.currentmenu != "") { if (Contextmenu.currentmenu != "") {
Contextmenu.currentmenu.remove(); Contextmenu.currentmenu.remove();
} }
const div = document.createElement("div"); const div = document.createElement("div");
if (this.accent_color) { if (this.accent_color) {
div.style.setProperty( div.style.setProperty(
"--accent_color", "--accent_color",
`#${this.accent_color.toString(16).padStart(6, "0")}` `#${this.accent_color.toString(16).padStart(6, "0")}`
); );
} else { } else {
div.style.setProperty("--accent_color", "transparent"); div.style.setProperty("--accent_color", "transparent");
} }
if (this.banner) { if (this.banner) {
const banner = document.createElement("img"); const banner = document.createElement("img");
let src: string; let src: string;
if (!this.hypotheticalbanner) { if (!this.hypotheticalbanner) {
src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${
this.banner this.banner
}.png`; }.png`;
} else { } else {
src = this.banner; src = this.banner;
} }
banner.src = src; banner.src = src;
banner.classList.add("banner"); banner.classList.add("banner");
div.append(banner); div.append(banner);
} }
if (x !== -1) { if (x !== -1) {
div.style.left = `${x}px`; div.style.left = `${x}px`;
div.style.top = `${y}px`; div.style.top = `${y}px`;
div.classList.add("profile", "flexttb"); div.classList.add("profile", "flexttb");
} else { } else {
this.setstatus("online"); this.setstatus("online");
div.classList.add("hypoprofile", "flexttb"); div.classList.add("hypoprofile", "flexttb");
} }
const badgediv = document.createElement("div"); const badgediv = document.createElement("div");
badgediv.classList.add("badges"); badgediv.classList.add("badges");
(async () => { (async () => {
if (!this.badge_ids) return; if (!this.badge_ids) return;
for (const id of this.badge_ids) { for (const id of this.badge_ids) {
const badgejson = await this.getBadge(id); const badgejson = await this.getBadge(id);
if (badgejson) { if (badgejson) {
const badge = document.createElement(badgejson.link ? "a" : "div"); const badge = document.createElement(badgejson.link ? "a" : "div");
badge.classList.add("badge"); badge.classList.add("badge");
const img = document.createElement("img"); const img = document.createElement("img");
img.src = badgejson.icon; img.src = badgejson.icon;
badge.append(img); badge.append(img);
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = badgejson.description; span.textContent = badgejson.description;
badge.append(span); badge.append(span);
if (badge instanceof HTMLAnchorElement) { if (badge instanceof HTMLAnchorElement) {
badge.href = badgejson.link; badge.href = badgejson.link;
} }
badgediv.append(badge); badgediv.append(badge);
} }
} }
})(); })();
const pfp = await this.buildstatuspfp(); const pfp = await this.buildstatuspfp();
div.appendChild(pfp); div.appendChild(pfp);
const userbody = document.createElement("div"); const userbody = document.createElement("div");
userbody.classList.add("infosection"); userbody.classList.add("infosection");
div.appendChild(userbody); div.appendChild(userbody);
const usernamehtml = document.createElement("h2"); const usernamehtml = document.createElement("h2");
usernamehtml.textContent = this.username; usernamehtml.textContent = this.username;
userbody.appendChild(usernamehtml); userbody.appendChild(usernamehtml);
userbody.appendChild(badgediv); userbody.appendChild(badgediv);
const discrimatorhtml = document.createElement("h3"); const discrimatorhtml = document.createElement("h3");
discrimatorhtml.classList.add("tag"); discrimatorhtml.classList.add("tag");
discrimatorhtml.textContent = `${this.username}#${this.discriminator}`; discrimatorhtml.textContent = `${this.username}#${this.discriminator}`;
userbody.appendChild(discrimatorhtml); userbody.appendChild(discrimatorhtml);
const pronounshtml = document.createElement("p"); const pronounshtml = document.createElement("p");
pronounshtml.textContent = this.pronouns; pronounshtml.textContent = this.pronouns;
pronounshtml.classList.add("pronouns"); pronounshtml.classList.add("pronouns");
userbody.appendChild(pronounshtml); userbody.appendChild(pronounshtml);
const rule = document.createElement("hr"); const rule = document.createElement("hr");
userbody.appendChild(rule); userbody.appendChild(rule);
const biohtml = this.bio.makeHTML(); const biohtml = this.bio.makeHTML();
userbody.appendChild(biohtml); userbody.appendChild(biohtml);
if (guild) { if (guild) {
Member.resolveMember(this, guild).then((member) => { Member.resolveMember(this, guild).then((member) => {
if (!member) return; if (!member) return;
const roles = document.createElement("div"); const roles = document.createElement("div");
roles.classList.add("rolesbox"); roles.classList.add("rolesbox");
for (const role of member.roles) { for (const role of member.roles) {
const roleDiv = document.createElement("div"); const roleDiv = document.createElement("div");
roleDiv.classList.add("rolediv"); roleDiv.classList.add("rolediv");
const color = document.createElement("div"); const color = document.createElement("div");
roleDiv.append(color); roleDiv.append(color);
color.style.setProperty( color.style.setProperty(
"--role-color", "--role-color",
`#${role.color.toString(16).padStart(6, "0")}` `#${role.color.toString(16).padStart(6, "0")}`
); );
color.classList.add("colorrolediv"); color.classList.add("colorrolediv");
const span = document.createElement("span"); const span = document.createElement("span");
roleDiv.append(span); roleDiv.append(span);
span.textContent = role.name; span.textContent = role.name;
roles.append(roleDiv); roles.append(roleDiv);
} }
userbody.append(roles); userbody.append(roles);
}); });
} }
if (x !== -1) { if (x !== -1) {
Contextmenu.currentmenu = div; Contextmenu.currentmenu = div;
document.body.appendChild(div); document.body.appendChild(div);
Contextmenu.keepOnScreen(div); Contextmenu.keepOnScreen(div);
} }
return div; return div;
} }
profileclick(obj: HTMLElement, guild?: Guild): void { profileclick(obj: HTMLElement, guild?: Guild): void {
obj.onclick = (e: MouseEvent) => { obj.onclick = (e: MouseEvent) => {
this.buildprofile(e.clientX, e.clientY, guild); this.buildprofile(e.clientX, e.clientY, guild);
e.stopPropagation(); e.stopPropagation();
}; };
} }
} }
User.setUpContextMenu(); User.setUpContextMenu();
export { User }; export { User };