diff --git a/emoji-packer.js b/emoji-packer.cjs similarity index 100% rename from emoji-packer.js rename to emoji-packer.cjs diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 43a3d0b..0000000 --- a/eslint.config.js +++ /dev/null @@ -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 - } - } -]; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..83a917c --- /dev/null +++ b/eslint.config.mjs @@ -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, + }, + } +); diff --git a/package-lock.json b/package-lock.json index 463e428..fa622ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "ts-to-jsdoc": "^2.2.0" }, "devDependencies": { - "@eslint/js": "^9.7.0", + "@eslint/js": "^9.10.0", "@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/parser": "^0.25.0", "@stylistic/eslint-plugin": "^2.3.0", @@ -23,7 +23,7 @@ "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", "@types/node-fetch": "^2.6.11", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-plugin-html": "^8.1.1", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-unicorn": "^55.0.0", @@ -31,8 +31,8 @@ "gulp-copy": "^5.0.0", "gulp-typescript": "^6.0.0-alpha.1", "ts-node": "^10.9.2", - "typescript": "^5.5.4", - "typescript-eslint": "^7.17.0" + "typescript": "^5.6.2", + "typescript-eslint": "^7.18.0" } }, "node_modules/@babel/code-frame": { @@ -290,10 +290,11 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", - "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "dev": true, + "license": "MIT", "engines": { "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", "integrity": "sha512-5DlvqO8bbe90cKSfFDuEblyrEnhAdgNTjWxXeUxt/XXC2OuMC8CsxzLZjtK3+0X6yM8m4xcE3fymCPwg7zdcXQ==", "dev": true, + "license": "MIT", "engines": { "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", "integrity": "sha512-dj6U1klMorS/g1QFI6j15AXknsqMDtdingSF6Sdtm1Z7WOVISfDwVyeNbb+JLUG+ATo9aECPQP5A7MHqgP6J9A==", "dev": true, + "license": "MIT", "dependencies": { "es-html-parser": "^0.0.9" }, @@ -343,13 +346,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -362,18 +366,20 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -389,6 +395,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -397,10 +404,11 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", @@ -420,7 +428,8 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", @@ -653,6 +662,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", "dev": true, + "license": "MIT", "dependencies": { "@types/eslint": "*" } @@ -774,16 +784,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", - "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/type-utils": "7.17.0", - "@typescript-eslint/utils": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -807,15 +818,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", - "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { @@ -835,12 +847,13 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -852,19 +865,21 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", - "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -875,13 +890,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", - "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.17.0", - "@typescript-eslint/utils": "7.17.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -902,12 +918,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -919,16 +936,18 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", - "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || >=20.0.0" }, @@ -938,13 +957,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", - "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/visitor-keys": "7.17.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -966,12 +986,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -983,21 +1004,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/utils": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", - "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.17.0", - "@typescript-eslint/types": "7.17.0", - "@typescript-eslint/typescript-estree": "7.17.0" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1011,12 +1034,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", - "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -1032,6 +1056,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "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", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2024,6 +2050,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -2239,16 +2266,17 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -2391,10 +2419,11 @@ } }, "node_modules/eslint/node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "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", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -4743,6 +4773,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5458,6 +5489,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5893,10 +5925,11 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5906,14 +5939,15 @@ } }, "node_modules/typescript-eslint": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.17.0.tgz", - "integrity": "sha512-spQxsQvPguduCUfyUvLItvKqK3l8KJ/kqs5Pb/URtzQ5AC53Z6us32St37rpmlt2uESG23lOFpV4UErrmy4dZQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.18.0.tgz", + "integrity": "sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "7.17.0", - "@typescript-eslint/parser": "7.17.0", - "@typescript-eslint/utils": "7.17.0" + "@typescript-eslint/eslint-plugin": "7.18.0", + "@typescript-eslint/parser": "7.18.0", + "@typescript-eslint/utils": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" diff --git a/package.json b/package.json index 2045341..b35bbea 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "ts-to-jsdoc": "^2.2.0" }, "devDependencies": { - "@eslint/js": "^9.7.0", + "@eslint/js": "^9.10.0", "@html-eslint/eslint-plugin": "^0.25.0", "@html-eslint/parser": "^0.25.0", "@stylistic/eslint-plugin": "^2.3.0", @@ -26,7 +26,7 @@ "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", "@types/node-fetch": "^2.6.11", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-plugin-html": "^8.1.1", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-unicorn": "^55.0.0", @@ -34,7 +34,7 @@ "gulp-copy": "^5.0.0", "gulp-typescript": "^6.0.0-alpha.1", "ts-node": "^10.9.2", - "typescript": "^5.5.4", - "typescript-eslint": "^7.17.0" + "typescript": "^5.6.2", + "typescript-eslint": "^7.18.0" } } diff --git a/src/index.ts b/src/index.ts index 23d492a..97be27a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,108 +13,108 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); interface Instance { - name: string; - [key: string]: any; +name: string; +[key: string]: any; } const app = express(); import instances from "./webpage/instances.json" with { type: "json" }; const instanceNames = new Map(); -for (const instance of instances) { - instanceNames.set(instance.name, instance); -} + for (const instance of instances) { + instanceNames.set(instance.name, instance); + } -app.use(compression()); + app.use(compression()); -async function updateInstances(): Promise { - try { - const response = await fetch( - "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json" - ); - const json = (await response.json()) as Instance[]; - for (const instance of json) { - if (!instanceNames.has(instance.name)) { - instances.push(instance as any); - } else { - const existingInstance = instanceNames.get(instance.name); - if (existingInstance) { - for (const key of Object.keys(instance)) { - if (!existingInstance[key]) { - existingInstance[key] = instance[key]; - } - } - } - } - } - observe(instances); - } catch (error) { - console.error("Error updating instances:", error); - } -} + async function updateInstances(): Promise { + try { + const response = await fetch( + "https://raw.githubusercontent.com/spacebarchat/spacebarchat/master/instances/instances.json" + ); + const json = (await response.json()) as Instance[]; + for (const instance of json) { + if (!instanceNames.has(instance.name)) { + instances.push(instance as any); + } else { + const existingInstance = instanceNames.get(instance.name); + if (existingInstance) { + for (const key of Object.keys(instance)) { + if (!existingInstance[key]) { + existingInstance[key] = instance[key]; + } + } + } + } + } + observe(instances); + } catch (error) { + console.error("Error updating instances:", error); + } + } -updateInstances(); + updateInstances(); -app.use("/getupdates", (_req: Request, res: Response) => { - try { - const stats = fs.statSync(path.join(__dirname, "webpage")); - res.send(stats.mtimeMs.toString()); - } catch (error) { - console.error("Error getting updates:", error); - res.status(500).send("Error getting updates"); - } -}); + app.use("/getupdates", (_req: Request, res: Response) => { + try { + const stats = fs.statSync(path.join(__dirname, "webpage")); + res.send(stats.mtimeMs.toString()); + } catch (error) { + console.error("Error getting updates:", error); + res.status(500).send("Error getting updates"); + } + }); -app.use("/services/oembed", (req: Request, res: Response) => { - inviteResponse(req, res); -}); + app.use("/services/oembed", (req: Request, res: Response) => { + inviteResponse(req, res); + }); -app.use("/uptime", (req: Request, res: Response) => { - const instanceUptime = uptime[req.query.name as string]; - res.send(instanceUptime); -}); + app.use("/uptime", (req: Request, res: Response) => { + const instanceUptime = uptime[req.query.name as string]; + res.send(instanceUptime); + }); -app.use("/", async (req: Request, res: Response) => { - const scheme = req.secure ? "https" : "http"; - const host = `${scheme}://${req.get("Host")}`; - const ref = host + req.originalUrl; + app.use("/", async (req: Request, res: Response) => { + const scheme = req.secure ? "https" : "http"; + const host = `${scheme}://${req.get("Host")}`; + const ref = host + req.originalUrl; - if (host && ref) { - const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; - res.set( - "Link", - `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` - ); - } + if (host && ref) { + const link = `${host}/services/oembed?url=${encodeURIComponent(ref)}`; + res.set( + "Link", + `<${link}>; rel="alternate"; type="application/json+oembed"; title="Jank Client oEmbed format"` + ); + } - if (req.path === "/") { - res.sendFile(path.join(__dirname, "webpage", "home.html")); - return; - } + if (req.path === "/") { + res.sendFile(path.join(__dirname, "webpage", "home.html")); + return; + } - if (req.path.startsWith("/instances.json")) { - res.json(instances); - return; - } + if (req.path.startsWith("/instances.json")) { + res.json(instances); + return; + } - if (req.path.startsWith("/invite/")) { - res.sendFile(path.join(__dirname, "webpage", "invite.html")); - return; - } + if (req.path.startsWith("/invite/")) { + res.sendFile(path.join(__dirname, "webpage", "invite.html")); + return; + } - const filePath = path.join(__dirname, "webpage", req.path); - if (fs.existsSync(filePath)) { - res.sendFile(filePath); - } else if (fs.existsSync(`${filePath}.html`)) { - res.sendFile(`${filePath}.html`); - } else { - res.sendFile(path.join(__dirname, "webpage", "index.html")); - } -}); + const filePath = path.join(__dirname, "webpage", req.path); + if (fs.existsSync(filePath)) { + res.sendFile(filePath); + } else if (fs.existsSync(`${filePath}.html`)) { + res.sendFile(`${filePath}.html`); + } else { + res.sendFile(path.join(__dirname, "webpage", "index.html")); + } + }); -const PORT = process.env.PORT || Number(process.argv[2]) || 8080; -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); -}); + const PORT = process.env.PORT || Number(process.argv[2]) || 8080; + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); -export { getApiUrls }; + export { getApiUrls }; diff --git a/src/stats.ts b/src/stats.ts index 6128fba..dde2b2c 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -8,244 +8,244 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); interface UptimeEntry { - time: number; - online: boolean; +time: number; +online: boolean; } interface UptimeObject { - [key: string]: UptimeEntry[]; +[key: string]: UptimeEntry[]; } interface Instance { - name: string; - urls?: { api: string }; - url?: string; - online?: boolean; - uptime?: { - daytime: number; - weektime: number; - alltime: number; - }; +name: string; +urls?: { api: string }; +url?: string; +online?: boolean; +uptime?: { +daytime: number; +weektime: number; +alltime: number; +}; } let uptimeObject: UptimeObject = loadUptimeObject(); export { uptimeObject as uptime }; function loadUptimeObject(): UptimeObject { - const filePath = path.join(__dirname, "..", "uptime.json"); - if (fs.existsSync(filePath)) { - try { - return JSON.parse(fs.readFileSync(filePath, "utf8")); - } catch (error) { - console.error("Error reading uptime.json:", error); - return {}; - } - } - return {}; +const filePath = path.join(__dirname, "..", "uptime.json"); +if (fs.existsSync(filePath)) { +try { +return JSON.parse(fs.readFileSync(filePath, "utf8")); +} catch (error) { +console.error("Error reading uptime.json:", error); +return {}; +} +} +return {}; } function saveUptimeObject(): void { - fs.writeFile( - `${__dirname}/uptime.json`, - JSON.stringify(uptimeObject), - (error) => { - if (error) { - console.error("Error saving uptime.json:", error); - } - } - ); +fs.writeFile( +`${__dirname}/uptime.json`, +JSON.stringify(uptimeObject), +(error) => { +if (error) { +console.error("Error saving uptime.json:", error); +} +} +); } function removeUndefinedKey(): void { - if (uptimeObject.undefined) { - delete uptimeObject.undefined; - saveUptimeObject(); - } +if (uptimeObject.undefined) { +delete uptimeObject.undefined; +saveUptimeObject(); +} } removeUndefinedKey(); export async function observe(instances: Instance[]): Promise { - const activeInstances = new Set(); - const instancePromises = instances.map((instance) => - resolveInstance(instance, activeInstances) - ); - await Promise.allSettled(instancePromises); - updateInactiveInstances(activeInstances); -} + const activeInstances = new Set(); + const instancePromises = instances.map((instance) => + resolveInstance(instance, activeInstances) + ); + await Promise.allSettled(instancePromises); + updateInactiveInstances(activeInstances); + } -async function resolveInstance( - instance: Instance, - activeInstances: Set -): Promise { - try { - calcStats(instance); - const api = await getApiUrl(instance); - if (!api) { - handleUnresolvedApi(instance); - return; - } - activeInstances.add(instance.name); - scheduleHealthCheck(instance, api); - } catch (error) { - console.error("Error resolving instance:", error); - } -} + async function resolveInstance( + instance: Instance, + activeInstances: Set + ): Promise { + try { + calcStats(instance); + const api = await getApiUrl(instance); + if (!api) { + handleUnresolvedApi(instance); + return; + } + activeInstances.add(instance.name); + scheduleHealthCheck(instance, api); + } catch (error) { + console.error("Error resolving instance:", error); + } + } -async function getApiUrl(instance: Instance): Promise { - if (instance.urls) { - return instance.urls.api; - } - if (instance.url) { - const urls = await getApiUrls(instance.url); - return urls ? urls.api : null; - } - return null; -} + async function getApiUrl(instance: Instance): Promise { + if (instance.urls) { + return instance.urls.api; + } + if (instance.url) { + const urls = await getApiUrls(instance.url); + return urls ? urls.api : null; + } + return null; + } -function handleUnresolvedApi(instance: Instance): void { - setStatus(instance, false); - console.warn(`${instance.name} does not resolve api URL`, instance); - setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30); -} + function handleUnresolvedApi(instance: Instance): void { + setStatus(instance, false); + console.warn(`${instance.name} does not resolve api URL`, instance); + setTimeout(() => resolveInstance(instance, new Set()), 1000 * 60 * 30); + } -function scheduleHealthCheck(instance: Instance, api: string): void { - const checkInterval = 1000 * 60 * 30; - const initialDelay = Math.random() * 1000 * 60 * 10; - setTimeout(() => { - checkHealth(instance, api); - setInterval(() => checkHealth(instance, api), checkInterval); - }, initialDelay); -} + function scheduleHealthCheck(instance: Instance, api: string): void { + const checkInterval = 1000 * 60 * 30; + const initialDelay = Math.random() * 1000 * 60 * 10; + setTimeout(() => { + checkHealth(instance, api); + setInterval(() => checkHealth(instance, api), checkInterval); + }, initialDelay); + } -async function checkHealth( - instance: Instance, - api: string, - tries = 0 -): Promise { - try { - const response = await fetch(`${api}ping`, { method: "HEAD" }); - if (response.ok || tries > 3) { - setStatus(instance, response.ok); - } else { - retryHealthCheck(instance, api, tries); - } - } catch (error) { - console.error("Error checking health:", error); - if (tries > 3) { - setStatus(instance, false); - } else { - retryHealthCheck(instance, api, tries); - } - } -} + async function checkHealth( + instance: Instance, + api: string, + tries = 0 + ): Promise { + try { + const response = await fetch(`${api}ping`, { method: "HEAD" }); + if (response.ok || tries > 3) { + setStatus(instance, response.ok); + } else { + retryHealthCheck(instance, api, tries); + } + } catch (error) { + console.error("Error checking health:", error); + if (tries > 3) { + setStatus(instance, false); + } else { + retryHealthCheck(instance, api, tries); + } + } + } -function retryHealthCheck( - instance: Instance, - api: string, - tries: number -): void { - setTimeout(() => checkHealth(instance, api, tries + 1), 30000); -} + function retryHealthCheck( + instance: Instance, + api: string, + tries: number + ): void { + setTimeout(() => checkHealth(instance, api, tries + 1), 30000); + } -function updateInactiveInstances(activeInstances: Set): void { - for (const key of Object.keys(uptimeObject)) { - if (!activeInstances.has(key)) { - setStatus(key, false); - } - } -} + function updateInactiveInstances(activeInstances: Set): void { + for (const key of Object.keys(uptimeObject)) { + if (!activeInstances.has(key)) { + setStatus(key, false); + } + } + } -function calcStats(instance: Instance): void { - const obj = uptimeObject[instance.name]; - if (!obj) return; + function calcStats(instance: Instance): void { + const obj = uptimeObject[instance.name]; + if (!obj) return; - const now = Date.now(); - const day = now - 1000 * 60 * 60 * 24; - const week = now - 1000 * 60 * 60 * 24 * 7; + const now = Date.now(); + const day = now - 1000 * 60 * 60 * 24; + const week = now - 1000 * 60 * 60 * 24 * 7; - let totalTimePassed = 0; - let alltime = 0; - let daytime = 0; - let weektime = 0; - let online = false; + let totalTimePassed = 0; + let alltime = 0; + let daytime = 0; + let weektime = 0; + let online = false; - for (let i = 0; i < obj.length; i++) { - const entry = obj[i]; - online = entry.online; - const stamp = entry.time; - const nextStamp = obj[i + 1]?.time || now; - const timePassed = nextStamp - stamp; + for (let i = 0; i < obj.length; i++) { + const entry = obj[i]; + online = entry.online; + const stamp = entry.time; + const nextStamp = obj[i + 1]?.time || now; + const timePassed = nextStamp - stamp; - totalTimePassed += timePassed; - alltime += Number(online) * timePassed; + totalTimePassed += timePassed; + alltime += Number(online) * timePassed; - if (stamp + timePassed > week) { - const weekTimePassed = Math.min(timePassed, nextStamp - week); - weektime += Number(online) * weekTimePassed; + if (stamp + timePassed > week) { + const weekTimePassed = Math.min(timePassed, nextStamp - week); + weektime += Number(online) * weekTimePassed; - if (stamp + timePassed > day) { - const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); - daytime += Number(online) * dayTimePassed; - } - } - } + if (stamp + timePassed > day) { + const dayTimePassed = Math.min(weekTimePassed, nextStamp - day); + daytime += Number(online) * dayTimePassed; + } + } + } - instance.online = online; - instance.uptime = calculateUptimeStats( - totalTimePassed, - alltime, - daytime, - weektime, - online - ); -} + instance.online = online; + instance.uptime = calculateUptimeStats( + totalTimePassed, + alltime, + daytime, + weektime, + online + ); + } -function calculateUptimeStats( - totalTimePassed: number, - alltime: number, - daytime: number, - weektime: number, - online: boolean -): { daytime: number; weektime: number; alltime: number } { - const dayInMs = 1000 * 60 * 60 * 24; - const weekInMs = dayInMs * 7; + function calculateUptimeStats( + totalTimePassed: number, + alltime: number, + daytime: number, + weektime: number, + online: boolean + ): { daytime: number; weektime: number; alltime: number } { + const dayInMs = 1000 * 60 * 60 * 24; + const weekInMs = dayInMs * 7; - alltime /= totalTimePassed; + alltime /= totalTimePassed; - if (totalTimePassed > dayInMs) { - daytime = daytime || (online ? dayInMs : 0); - daytime /= dayInMs; + if (totalTimePassed > dayInMs) { + daytime = daytime || (online ? dayInMs : 0); + daytime /= dayInMs; - if (totalTimePassed > weekInMs) { - weektime = weektime || (online ? weekInMs : 0); - weektime /= weekInMs; - } else { - weektime = alltime; - } - } else { - weektime = alltime; - daytime = alltime; - } + if (totalTimePassed > weekInMs) { + weektime = weektime || (online ? weekInMs : 0); + weektime /= weekInMs; + } else { + weektime = alltime; + } + } else { + weektime = alltime; + daytime = alltime; + } - return { daytime, weektime, alltime }; -} + return { daytime, weektime, alltime }; + } -function setStatus(instance: string | Instance, status: boolean): void { - const name = typeof instance === "string" ? instance : instance.name; - let obj = uptimeObject[name]; + function setStatus(instance: string | Instance, status: boolean): void { + const name = typeof instance === "string" ? instance : instance.name; + let obj = uptimeObject[name]; - if (!obj) { - obj = []; - uptimeObject[name] = obj; - } + if (!obj) { + obj = []; + uptimeObject[name] = obj; + } - if (obj.at(-1)?.online !== status) { - obj.push({ time: Date.now(), online: status }); - saveUptimeObject(); - } + if (obj.at(-1)?.online !== status) { + obj.push({ time: Date.now(), online: status }); + saveUptimeObject(); + } - if (typeof instance !== "string") { - calcStats(instance); - } -} + if (typeof instance !== "string") { + calcStats(instance); + } + } diff --git a/src/utils.ts b/src/utils.ts index 179743c..2514908 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,113 +2,113 @@ import fetch from "node-fetch"; import { Request, Response } from "express"; interface ApiUrls { - api: string; - gateway: string; - cdn: string; - wellknown: string; +api: string; +gateway: string; +cdn: string; +wellknown: string; } interface Invite { - guild: { - name: string; - description?: string; - icon?: string; - id: string; - }; - inviter?: { - username: string; - }; +guild: { +name: string; +description?: string; +icon?: string; +id: string; +}; +inviter?: { +username: string; +}; } export async function getApiUrls(url: string): Promise { - if (!url.endsWith("/")) { - url += "/"; - } - try { - const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( - (res) => res.json() as Promise - ); - const api = info.api; - const apiUrl = new URL(api); - const policies: any = await fetch( - `${api}${ - apiUrl.pathname.includes("api") ? "" : "api" - }/policies/instance/domains` - ).then((res) => res.json()); - return { - api: policies.apiEndpoint, - gateway: policies.gateway, - cdn: policies.cdn, - wellknown: url, - }; - } catch (error) { - console.error("Error fetching API URLs:", error); - return null; - } -} + if (!url.endsWith("/")) { + url += "/"; + } + try { + const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( + (res) => res.json() as Promise + ); + const api = info.api; + const apiUrl = new URL(api); + const policies: any = await fetch( + `${api}${ + apiUrl.pathname.includes("api") ? "" : "api" + }/policies/instance/domains` + ).then((res) => res.json()); + return { + api: policies.apiEndpoint, + gateway: policies.gateway, + cdn: policies.cdn, + wellknown: url, + }; + } catch (error) { + console.error("Error fetching API URLs:", error); + return null; + } + } -export async function inviteResponse( - req: Request, - res: Response -): Promise { - let url: URL; - if (URL.canParse(req.query.url as string)) { - url = new URL(req.query.url as string); - } else { - const scheme = req.secure ? "https" : "http"; - const host = `${scheme}://${req.get("Host")}`; - url = new URL(host); - } + export async function inviteResponse( + req: Request, + res: Response + ): Promise { + let url: URL; + if (URL.canParse(req.query.url as string)) { + url = new URL(req.query.url as string); + } else { + const scheme = req.secure ? "https" : "http"; + const host = `${scheme}://${req.get("Host")}`; + url = new URL(host); + } - try { - if (url.pathname.startsWith("invite")) { - throw new Error("Invalid invite URL"); - } + try { + if (url.pathname.startsWith("invite")) { + throw new Error("Invalid invite URL"); + } - const code = url.pathname.split("/")[2]; - const instance = url.searchParams.get("instance"); - if (!instance) { - throw new Error("Instance not specified"); - } - const urls = await getApiUrls(instance); - if (!urls) { - throw new Error("Failed to get API URLs"); - } + const code = url.pathname.split("/")[2]; + const instance = url.searchParams.get("instance"); + if (!instance) { + throw new Error("Instance not specified"); + } + const urls = await getApiUrls(instance); + if (!urls) { + throw new Error("Failed to get API URLs"); + } - const invite = await fetch(`${urls.api}/invites/${code}`).then( - (res) => res.json() as Promise - ); - const title = invite.guild.name; - const description = invite.inviter - ? `${invite.inviter.username} has invited you to ${invite.guild.name}${ - invite.guild.description ? `\n${invite.guild.description}` : "" - }` - : `You've been invited to ${invite.guild.name}${ - invite.guild.description ? `\n${invite.guild.description}` : "" - }`; - const thumbnail = invite.guild.icon - ? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png` - : ""; + const invite = await fetch(`${urls.api}/invites/${code}`).then( + (res) => res.json() as Promise + ); + const title = invite.guild.name; + const description = invite.inviter + ? `${invite.inviter.username} has invited you to ${invite.guild.name}${ + invite.guild.description ? `\n${invite.guild.description}` : "" + }` + : `You've been invited to ${invite.guild.name}${ + invite.guild.description ? `\n${invite.guild.description}` : "" + }`; + const thumbnail = invite.guild.icon + ? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png` + : ""; - const jsonResponse = { - type: "link", - version: "1.0", - title, - thumbnail, - description, - }; + const jsonResponse = { + type: "link", + version: "1.0", + title, + thumbnail, + description, + }; - res.json(jsonResponse); - } catch (error) { - console.error("Error processing invite response:", error); - const jsonResponse = { - type: "link", - version: "1.0", - title: "Jank Client", - thumbnail: "/logo.webp", - description: "A spacebar client that has DMs, replying and more", - url: url.toString(), - }; - res.json(jsonResponse); - } -} + res.json(jsonResponse); + } catch (error) { + console.error("Error processing invite response:", error); + const jsonResponse = { + type: "link", + version: "1.0", + title: "Jank Client", + thumbnail: "/logo.webp", + description: "A spacebar client that has DMs, replying and more", + url: url.toString(), + }; + res.json(jsonResponse); + } + } diff --git a/src/webpage/audio.ts b/src/webpage/audio.ts index dfdac5d..5652f79 100644 --- a/src/webpage/audio.ts +++ b/src/webpage/audio.ts @@ -1,164 +1,164 @@ import { getBulkInfo } from "./login.js"; class Voice { - audioCtx: AudioContext; - info: { wave: string | Function; freq: number }; - playing: boolean; - myArrayBuffer: AudioBuffer; - gainNode: GainNode; - buffer: Float32Array; - source: AudioBufferSourceNode; - constructor(wave: string | Function, freq: number, volume = 1) { - this.audioCtx = new window.AudioContext(); - this.info = { wave, freq }; - this.playing = false; - this.myArrayBuffer = this.audioCtx.createBuffer( - 1, - this.audioCtx.sampleRate, - this.audioCtx.sampleRate - ); - this.gainNode = this.audioCtx.createGain(); - this.gainNode.gain.value = volume; - this.gainNode.connect(this.audioCtx.destination); - this.buffer = this.myArrayBuffer.getChannelData(0); - this.source = this.audioCtx.createBufferSource(); - this.source.buffer = this.myArrayBuffer; - this.source.loop = true; - this.source.start(); - this.updateWave(); - } - get wave(): string | Function { - return this.info.wave; - } - get freq(): number { - return this.info.freq; - } - set wave(wave: string | Function) { - this.info.wave = wave; - this.updateWave(); - } - set freq(freq: number) { - this.info.freq = freq; - this.updateWave(); - } - updateWave(): void { - const func = this.waveFunction(); - for (let i = 0; i < this.buffer.length; i++) { - this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); - } - } - waveFunction(): Function { - if (typeof this.wave === "function") { - return this.wave; - } - switch (this.wave) { - case "sin": - return (t: number, freq: number) => { - return Math.sin(t * Math.PI * 2 * freq); - }; - case "triangle": - return (t: number, freq: number) => { - return Math.abs(((4 * t * freq) % 4) - 2) - 1; - }; - case "sawtooth": - return (t: number, freq: number) => { - return ((t * freq) % 1) * 2 - 1; - }; - case "square": - return (t: number, freq: number) => { - return (t * freq) % 2 < 1 ? 1 : -1; - }; - case "white": - return (_t: number, _freq: number) => { - return Math.random() * 2 - 1; - }; - case "noise": - return (_t: number, _freq: number) => { - return 0; - }; - } - return new Function(); - } - play(): void { - if (this.playing) { - return; - } - this.source.connect(this.gainNode); - this.playing = true; - } - stop(): void { - if (this.playing) { - this.source.disconnect(); - this.playing = false; - } - } - static noises(noise: string): void { - switch (noise) { - case "three": { - const voicy = new Voice("sin", 800); - voicy.play(); - setTimeout((_) => { - voicy.freq = 1000; - }, 50); - setTimeout((_) => { - voicy.freq = 1300; - }, 100); - setTimeout((_) => { - voicy.stop(); - }, 150); - break; - } - case "zip": { - const voicy = new Voice((t: number, freq: number) => { - return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); - }, 700); - voicy.play(); - setTimeout((_) => { - voicy.stop(); - }, 150); - break; - } - case "square": { - const voicy = new Voice("square", 600, 0.4); - voicy.play(); - setTimeout((_) => { - voicy.freq = 800; - }, 50); - setTimeout((_) => { - voicy.freq = 1000; - }, 100); - setTimeout((_) => { - voicy.stop(); - }, 150); - break; - } - case "beep": { - const voicy = new Voice("sin", 800); - voicy.play(); - setTimeout((_) => { - voicy.stop(); - }, 50); - setTimeout((_) => { - voicy.play(); - }, 100); - setTimeout((_) => { - voicy.stop(); - }, 150); - break; - } - } - } - static get sounds() { - return ["three", "zip", "square", "beep"]; - } - static setNotificationSound(sound: string) { - const userinfos = getBulkInfo(); - userinfos.preferences.notisound = sound; - localStorage.setItem("userinfos", JSON.stringify(userinfos)); - } - static getNotificationSound() { - const userinfos = getBulkInfo(); - return userinfos.preferences.notisound; - } +audioCtx: AudioContext; +info: { wave: string | Function; freq: number }; +playing: boolean; +myArrayBuffer: AudioBuffer; +gainNode: GainNode; +buffer: Float32Array; +source: AudioBufferSourceNode; +constructor(wave: string | Function, freq: number, volume = 1) { +this.audioCtx = new window.AudioContext(); +this.info = { wave, freq }; +this.playing = false; +this.myArrayBuffer = this.audioCtx.createBuffer( +1, +this.audioCtx.sampleRate, +this.audioCtx.sampleRate +); +this.gainNode = this.audioCtx.createGain(); +this.gainNode.gain.value = volume; +this.gainNode.connect(this.audioCtx.destination); +this.buffer = this.myArrayBuffer.getChannelData(0); +this.source = this.audioCtx.createBufferSource(); +this.source.buffer = this.myArrayBuffer; +this.source.loop = true; +this.source.start(); +this.updateWave(); +} +get wave(): string | Function { +return this.info.wave; +} +get freq(): number { +return this.info.freq; +} +set wave(wave: string | Function) { +this.info.wave = wave; +this.updateWave(); +} +set freq(freq: number) { +this.info.freq = freq; +this.updateWave(); +} +updateWave(): void { +const func = this.waveFunction(); +for (let i = 0; i < this.buffer.length; i++) { +this.buffer[i] = func(i / this.audioCtx.sampleRate, this.freq); +} +} +waveFunction(): Function { +if (typeof this.wave === "function") { +return this.wave; +} +switch (this.wave) { +case "sin": +return (t: number, freq: number) => { +return Math.sin(t * Math.PI * 2 * freq); +}; +case "triangle": +return (t: number, freq: number) => { +return Math.abs(((4 * t * freq) % 4) - 2) - 1; +}; +case "sawtooth": +return (t: number, freq: number) => { +return ((t * freq) % 1) * 2 - 1; +}; +case "square": +return (t: number, freq: number) => { +return (t * freq) % 2 < 1 ? 1 : -1; +}; +case "white": +return (_t: number, _freq: number) => { +return Math.random() * 2 - 1; +}; +case "noise": +return (_t: number, _freq: number) => { +return 0; +}; +} +return new Function(); +} +play(): void { +if (this.playing) { +return; +} +this.source.connect(this.gainNode); +this.playing = true; +} +stop(): void { +if (this.playing) { +this.source.disconnect(); +this.playing = false; +} +} +static noises(noise: string): void { +switch (noise) { +case "three": { +const voicy = new Voice("sin", 800); +voicy.play(); +setTimeout((_) => { +voicy.freq = 1000; +}, 50); +setTimeout((_) => { +voicy.freq = 1300; +}, 100); +setTimeout((_) => { +voicy.stop(); +}, 150); +break; +} +case "zip": { +const voicy = new Voice((t: number, freq: number) => { +return Math.sin((t + 2) ** Math.cos(t * 4) * Math.PI * 2 * freq); +}, 700); +voicy.play(); +setTimeout((_) => { +voicy.stop(); +}, 150); +break; +} +case "square": { +const voicy = new Voice("square", 600, 0.4); +voicy.play(); +setTimeout((_) => { +voicy.freq = 800; +}, 50); +setTimeout((_) => { +voicy.freq = 1000; +}, 100); +setTimeout((_) => { +voicy.stop(); +}, 150); +break; +} +case "beep": { +const voicy = new Voice("sin", 800); +voicy.play(); +setTimeout((_) => { +voicy.stop(); +}, 50); +setTimeout((_) => { +voicy.play(); +}, 100); +setTimeout((_) => { +voicy.stop(); +}, 150); +break; +} +} +} +static get sounds() { +return ["three", "zip", "square", "beep"]; +} +static setNotificationSound(sound: string) { +const userinfos = getBulkInfo(); +userinfos.preferences.notisound = sound; +localStorage.setItem("userinfos", JSON.stringify(userinfos)); +} +static getNotificationSound() { +const userinfos = getBulkInfo(); +return userinfos.preferences.notisound; +} } export { Voice }; diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 4e12262..c443aa5 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -11,1405 +11,1405 @@ import { Role, RoleList } from "./role.js"; import { InfiniteScroller } from "./infiniteScroller.js"; import { SnowFlake } from "./snowflake.js"; import { - channeljson, - embedjson, - messageCreateJson, - messagejson, - readyjson, - startTypingjson, +channeljson, +embedjson, +messageCreateJson, +messagejson, +readyjson, +startTypingjson, } from "./jsontypes.js"; import { MarkDown } from "./markdown.js"; import { Member } from "./member.js"; declare global { - interface NotificationOptions { - image?: string | null | undefined; - } +interface NotificationOptions { +image?: string | null | undefined; +} } class Channel extends SnowFlake { - editing!: Message | null; - type!: number; - owner!: Guild; - headers!: Localuser["headers"]; - name!: string; - parent_id?: string; - parent: Channel | undefined; - children!: Channel[]; - guild_id!: string; - permission_overwrites!: Map; - permission_overwritesar!: [Role, Permissions][]; - topic!: string; - nsfw!: boolean; - position: number = 0; - lastreadmessageid: string | undefined; - lastmessageid: string | undefined; - mentions!: number; - lastpin!: string; - move_id?: string; - typing!: number; - message_notifications!: number; - allthewayup!: boolean; - static contextmenu = new Contextmenu("channel menu"); - replyingto!: Message | null; - infinite!: InfiniteScroller; - idToPrev: Map = new Map(); - idToNext: Map = new Map(); - messages: Map = new Map(); - static setupcontextmenu() { - this.contextmenu.addbutton("Copy channel id", function (this: Channel) { - navigator.clipboard.writeText(this.id); - }); +editing!: Message | null; +type!: number; +owner!: Guild; +headers!: Localuser["headers"]; +name!: string; +parent_id?: string; +parent: Channel | undefined; +children!: Channel[]; +guild_id!: string; +permission_overwrites!: Map; + permission_overwritesar!: [Role, Permissions][]; + topic!: string; + nsfw!: boolean; + position: number = 0; + lastreadmessageid: string | undefined; + lastmessageid: string | undefined; + mentions!: number; + lastpin!: string; + move_id?: string; + typing!: number; + message_notifications!: number; + allthewayup!: boolean; + static contextmenu = new Contextmenu("channel menu"); + replyingto!: Message | null; + infinite!: InfiniteScroller; + idToPrev: Map = new Map(); + idToNext: Map = new Map(); + messages: Map = new Map(); + static setupcontextmenu() { + this.contextmenu.addbutton("Copy channel id", function (this: Channel) { + navigator.clipboard.writeText(this.id); + }); - this.contextmenu.addbutton("Mark as read", function (this: Channel) { - this.readbottom(); - }); + this.contextmenu.addbutton("Mark as read", function (this: Channel) { + this.readbottom(); + }); - this.contextmenu.addbutton("Settings[temp]", function (this: Channel) { - this.generateSettings(); - }); + this.contextmenu.addbutton("Settings[temp]", function (this: Channel) { + this.generateSettings(); + }); - this.contextmenu.addbutton( - "Delete channel", - function (this: Channel) { - this.deleteChannel(); - }, - null, - function () { - return this.isAdmin(); - } - ); + this.contextmenu.addbutton( + "Delete channel", + function (this: Channel) { + this.deleteChannel(); + }, + null, + function () { + return this.isAdmin(); + } + ); - this.contextmenu.addbutton( - "Edit channel", - function (this: Channel) { - this.editChannel(); - }, - null, - function () { - return this.isAdmin(); - } - ); + this.contextmenu.addbutton( + "Edit channel", + function (this: Channel) { + this.editChannel(); + }, + null, + function () { + return this.isAdmin(); + } + ); - this.contextmenu.addbutton( - "Make invite", - function (this: Channel) { - this.createInvite(); - }, - null, - function () { - return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; - } - ); - /* - this.contextmenu.addbutton("Test button",function(){ - this.localuser.ws.send(JSON.stringify({ - "op": 14, - "d": { - "guild_id": this.guild.id, - "channels": { - [this.id]: [ - [ - 0, - 99 - ] - ] - } - } - })) - },null); - /**/ - } - createInvite() { - const div = document.createElement("div"); - div.classList.add("invitediv"); - const text = document.createElement("span"); - div.append(text); - let uses = 0; - let expires = 1800; - const copycontainer = document.createElement("div"); - copycontainer.classList.add("copycontainer"); - const copy = document.createElement("span"); - copy.classList.add("copybutton", "svgtheme", "svg-copy"); - copycontainer.append(copy); - copycontainer.onclick = (_) => { - if (text.textContent) { - navigator.clipboard.writeText(text.textContent); - } - }; - div.append(copycontainer); - const update = () => { - fetch(`${this.info.api}/channels/${this.id}/invites`, { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - flags: 0, - target_type: null, - target_user_id: null, - max_age: expires + "", - max_uses: uses, - temporary: uses !== 0, - }), - }) - .then((_) => _.json()) - .then((json) => { - const params = new URLSearchParams(""); - params.set("instance", this.info.wellknown); - const encoded = params.toString(); - text.textContent = `${location.origin}/invite/${json.code}?${encoded}`; - }); - }; - update(); - new Dialog([ - "vdiv", - ["title", "Invite people"], - ["text", `to #${this.name} in ${this.guild.properties.name}`], - [ - "select", - "Expire after:", - [ - "30 Minutes", - "1 Hour", - "6 Hours", - "12 Hours", - "1 Day", - "7 Days", - "30 Days", - "Never", - ], - function (_e: Event) { - /* - let expires: number = [ - 1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0, - ][(e.srcElement as HTMLSelectElement).selectedIndex]; - */ - update(); - }, - 0, - ], - [ - "select", - "Max uses:", - [ - "No limit", - "1 use", - "5 uses", - "10 uses", - "25 uses", - "50 uses", - "100 uses", - ], - function (_e: Event) { - /* - let uses: number = [0, 1, 5, 10, 25, 50, 100][ - (e.srcElement as HTMLSelectElement).selectedIndex - ]; - */ - update(); - }, - 0, - ], - ["html", div], - ]).show(); - } - generateSettings() { - this.sortPerms(); - const settings = new Settings("Settings for " + this.name); + this.contextmenu.addbutton( + "Make invite", + function (this: Channel) { + this.createInvite(); + }, + null, + function () { + return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; + } + ); + /* + this.contextmenu.addbutton("Test button",function(){ + this.localuser.ws.send(JSON.stringify({ + "op": 14, + "d": { + "guild_id": this.guild.id, + "channels": { + [this.id]: [ + [ + 0, + 99 + ] + ] + } + } + })) + },null); + /**/ + } + createInvite() { + const div = document.createElement("div"); + div.classList.add("invitediv"); + const text = document.createElement("span"); + div.append(text); + let uses = 0; + let expires = 1800; + const copycontainer = document.createElement("div"); + copycontainer.classList.add("copycontainer"); + const copy = document.createElement("span"); + copy.classList.add("copybutton", "svgtheme", "svg-copy"); + copycontainer.append(copy); + copycontainer.onclick = (_) => { + if (text.textContent) { + navigator.clipboard.writeText(text.textContent); + } + }; + div.append(copycontainer); + const update = () => { + fetch(`${this.info.api}/channels/${this.id}/invites`, { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + flags: 0, + target_type: null, + target_user_id: null, + max_age: expires + "", + max_uses: uses, + temporary: uses !== 0, + }), + }) + .then((_) => _.json()) + .then((json) => { + const params = new URLSearchParams(""); + params.set("instance", this.info.wellknown); + const encoded = params.toString(); + text.textContent = `${location.origin}/invite/${json.code}?${encoded}`; + }); + }; + update(); + new Dialog([ + "vdiv", + ["title", "Invite people"], + ["text", `to #${this.name} in ${this.guild.properties.name}`], + [ + "select", + "Expire after:", + [ + "30 Minutes", + "1 Hour", + "6 Hours", + "12 Hours", + "1 Day", + "7 Days", + "30 Days", + "Never", + ], + function (_e: Event) { + /* + let expires: number = [ + 1800, 3600, 21600, 43200, 86400, 604800, 2592000, 0, + ][(e.srcElement as HTMLSelectElement).selectedIndex]; + */ + update(); + }, + 0, + ], + [ + "select", + "Max uses:", + [ + "No limit", + "1 use", + "5 uses", + "10 uses", + "25 uses", + "50 uses", + "100 uses", + ], + function (_e: Event) { + /* + let uses: number = [0, 1, 5, 10, 25, 50, 100][ + (e.srcElement as HTMLSelectElement).selectedIndex + ]; + */ + update(); + }, + 0, + ], + ["html", div], + ]).show(); + } + generateSettings() { + this.sortPerms(); + const settings = new Settings("Settings for " + this.name); - const s1 = settings.addButton("roles"); + const s1 = settings.addButton("roles"); - s1.options.push( - new RoleList( - this.permission_overwritesar, - this.guild, - this.updateRolePermissions.bind(this), - true - ) - ); - settings.show(); - } - sortPerms() { - this.permission_overwritesar.sort((a, b) => { - return ( - this.guild.roles.findIndex((_) => _ === a[0]) - - this.guild.roles.findIndex((_) => _ === b[0]) - ); - }); - } - setUpInfiniteScroller() { - this.infinite = new InfiniteScroller( - async (id: string, offset: number): Promise => { - if (offset === 1) { - if (this.idToPrev.has(id)) { - return this.idToPrev.get(id); - } else { - await this.grabBefore(id); - return this.idToPrev.get(id); - } - } else { - if (this.idToNext.has(id)) { - return this.idToNext.get(id); - } else if (this.lastmessage?.id !== id) { - await this.grabAfter(id); - return this.idToNext.get(id); - } else { - return; - } - } - }, - async (id: string): Promise => { - //await new Promise(_=>{setTimeout(_,Math.random()*10)}) - const messgage = this.messages.get(id); - try { - if (messgage) { - return messgage.buildhtml(); - } else { - console.error(id + " not found"); - } - } catch (e) { - console.error(e); - } - return document.createElement("div"); - }, - async (id: string) => { - const message = this.messages.get(id); - try { - if (message) { - message.deleteDiv(); - return true; - } - } catch (e) { - console.error(e); - } finally { - } - return false; - }, - this.readbottom.bind(this) - ); - } - constructor( - json: channeljson | -1, - owner: Guild, - id: string = json === -1 ? "" : json.id - ) { - super(id); - if (json === -1) { - return; - } - this.editing; - this.type = json.type; - this.owner = owner; - this.headers = this.owner.headers; - this.name = json.name; - if (json.parent_id) { - this.parent_id = json.parent_id; - } - this.parent = undefined; - this.children = []; - this.guild_id = json.guild_id; - this.permission_overwrites = new Map(); - this.permission_overwritesar = []; - for (const thing of json.permission_overwrites) { - if ( - thing.id === "1182819038095799904" || - thing.id === "1182820803700625444" - ) { - continue; - } - if (!this.permission_overwrites.has(thing.id)) { - //either a bug in the server requires this, or the API is cursed - this.permission_overwrites.set( - thing.id, - new Permissions(thing.allow, thing.deny) - ); - const permission = this.permission_overwrites.get(thing.id); - if (permission) { - const role = this.guild.roleids.get(thing.id); - if (role) { - this.permission_overwritesar.push([role, permission]); - } - } - } - } + s1.options.push( + new RoleList( + this.permission_overwritesar, + this.guild, + this.updateRolePermissions.bind(this), + true + ) + ); + settings.show(); + } + sortPerms() { + this.permission_overwritesar.sort((a, b) => { + return ( + this.guild.roles.findIndex((_) => _ === a[0]) - + this.guild.roles.findIndex((_) => _ === b[0]) + ); + }); + } + setUpInfiniteScroller() { + this.infinite = new InfiniteScroller( + async (id: string, offset: number): Promise => { + if (offset === 1) { + if (this.idToPrev.has(id)) { + return this.idToPrev.get(id); + } else { + await this.grabBefore(id); + return this.idToPrev.get(id); + } + } else { + if (this.idToNext.has(id)) { + return this.idToNext.get(id); + } else if (this.lastmessage?.id !== id) { + await this.grabAfter(id); + return this.idToNext.get(id); + } else { + return; + } + } + }, + async (id: string): Promise => { + //await new Promise(_=>{setTimeout(_,Math.random()*10)}) + const messgage = this.messages.get(id); + try { + if (messgage) { + return messgage.buildhtml(); + } else { + console.error(id + " not found"); + } + } catch (e) { + console.error(e); + } + return document.createElement("div"); + }, + async (id: string) => { + const message = this.messages.get(id); + try { + if (message) { + message.deleteDiv(); + return true; + } + } catch (e) { + console.error(e); + } finally { + } + return false; + }, + this.readbottom.bind(this) + ); + } + constructor( + json: channeljson | -1, + owner: Guild, + id: string = json === -1 ? "" : json.id + ) { + super(id); + if (json === -1) { + return; + } + this.editing; + this.type = json.type; + this.owner = owner; + this.headers = this.owner.headers; + this.name = json.name; + if (json.parent_id) { + this.parent_id = json.parent_id; + } + this.parent = undefined; + this.children = []; + this.guild_id = json.guild_id; + this.permission_overwrites = new Map(); + this.permission_overwritesar = []; + for (const thing of json.permission_overwrites) { + if ( + thing.id === "1182819038095799904" || + thing.id === "1182820803700625444" + ) { + continue; + } + if (!this.permission_overwrites.has(thing.id)) { + //either a bug in the server requires this, or the API is cursed + this.permission_overwrites.set( + thing.id, + new Permissions(thing.allow, thing.deny) + ); + const permission = this.permission_overwrites.get(thing.id); + if (permission) { + const role = this.guild.roleids.get(thing.id); + if (role) { + this.permission_overwritesar.push([role, permission]); + } + } + } + } - this.topic = json.topic; - this.nsfw = json.nsfw; - this.position = json.position; - this.lastreadmessageid = undefined; - if (json.last_message_id) { - this.lastmessageid = json.last_message_id; - } else { - this.lastmessageid = undefined; - } - this.setUpInfiniteScroller(); - this.perminfo ??= {}; - } - get perminfo() { - return this.guild.perminfo.channels[this.id]; - } - set perminfo(e) { - this.guild.perminfo.channels[this.id] = e; - } - isAdmin() { - return this.guild.isAdmin(); - } - get guild() { - return this.owner; - } - get localuser() { - return this.guild.localuser; - } - get info() { - return this.owner.info; - } - readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]) { - this.lastreadmessageid = json.last_message_id; - this.mentions = json.mention_count; - this.mentions ??= 0; - this.lastpin = json.last_pin_timestamp; - } - get hasunreads(): boolean { - if (!this.hasPermission("VIEW_CHANNEL")) { - return false; - } - return ( - !!this.lastmessageid && - (!this.lastreadmessageid || - SnowFlake.stringToUnixTime(this.lastmessageid) > - SnowFlake.stringToUnixTime(this.lastreadmessageid)) && - this.type !== 4 - ); - } - hasPermission(name: string, member = this.guild.member): boolean { - if (member.isAdmin()) { - return true; - } - for (const thing of member.roles) { - let premission = this.permission_overwrites.get(thing.id); - if (premission) { - const perm = premission.getPermission(name); - if (perm) { - return perm === 1; - } - } - if (thing.permissions.getPermission(name)) { - return true; - } - } - return false; - } - get canMessage(): boolean { - if ( - this.permission_overwritesar.length === 0 && - this.hasPermission("MANAGE_CHANNELS") - ) { - const role = this.guild.roles.find((_) => _.name === "@everyone"); - if (role) { - this.addRoleToPerms(role); - } - } - return this.hasPermission("SEND_MESSAGES"); - } - sortchildren() { - this.children.sort((a, b) => { - return a.position - b.position; - }); - } - resolveparent(_guild: Guild) { - const parentid = this.parent_id; - if (!parentid) return false; - this.parent = this.localuser.channelids.get(parentid); - this.parent ??= undefined; - if (this.parent !== undefined) { - this.parent.children.push(this); - } - return this.parent !== undefined; - } - calculateReorder() { - let position = -1; - const build: { - id: string; - position: number | undefined; - parent_id: string | undefined; - }[] = []; - for (const thing of this.children) { - const thisthing: { - id: string; - position: number | undefined; - parent_id: string | undefined; - } = { id: thing.id, position: undefined, parent_id: undefined }; - if (thing.position < position) { - thing.position = thisthing.position = position + 1; - } - position = thing.position; - if (thing.move_id && thing.move_id !== thing.parent_id) { - thing.parent_id = thing.move_id; - thisthing.parent_id = thing.parent?.id; - thing.move_id = undefined; - //console.log(this.guild.channelids[thisthing.parent_id.id]); - } - if (thisthing.position || thisthing.parent_id) { - build.push(thisthing); - } - } - return build; - } - static dragged: [Channel, HTMLDivElement] | [] = []; - html: WeakRef | undefined; - get visable() { - return this.hasPermission("VIEW_CHANNEL"); - } - createguildHTML(admin = false): HTMLDivElement { - const div = document.createElement("div"); - this.html = new WeakRef(div); - if (!this.visable) { - let quit = true; - for (const thing of this.children) { - if (thing.visable) { - quit = false; - } - } - if (quit) { - return div; - } - } - // @ts-ignore I dont wanna deal with this - div["all"] = this; - div.draggable = admin; - div.addEventListener("dragstart", (e) => { - Channel.dragged = [this, div]; - e.stopImmediatePropagation(); - }); - div.addEventListener("dragend", () => { - Channel.dragged = []; - }); - if (this.type === 4) { - this.sortchildren(); - const caps = document.createElement("div"); + this.topic = json.topic; + this.nsfw = json.nsfw; + this.position = json.position; + this.lastreadmessageid = undefined; + if (json.last_message_id) { + this.lastmessageid = json.last_message_id; + } else { + this.lastmessageid = undefined; + } + this.setUpInfiniteScroller(); + this.perminfo ??= {}; + } + get perminfo() { + return this.guild.perminfo.channels[this.id]; + } + set perminfo(e) { + this.guild.perminfo.channels[this.id] = e; + } + isAdmin() { + return this.guild.isAdmin(); + } + get guild() { + return this.owner; + } + get localuser() { + return this.guild.localuser; + } + get info() { + return this.owner.info; + } + readStateInfo(json: readyjson["d"]["read_state"]["entries"][0]) { + this.lastreadmessageid = json.last_message_id; + this.mentions = json.mention_count; + this.mentions ??= 0; + this.lastpin = json.last_pin_timestamp; + } + get hasunreads(): boolean { + if (!this.hasPermission("VIEW_CHANNEL")) { + return false; + } + return ( + !!this.lastmessageid && + (!this.lastreadmessageid || + SnowFlake.stringToUnixTime(this.lastmessageid) > + SnowFlake.stringToUnixTime(this.lastreadmessageid)) && + this.type !== 4 + ); + } + hasPermission(name: string, member = this.guild.member): boolean { + if (member.isAdmin()) { + return true; + } + for (const thing of member.roles) { + let premission = this.permission_overwrites.get(thing.id); + if (premission) { + const perm = premission.getPermission(name); + if (perm) { + return perm === 1; + } + } + if (thing.permissions.getPermission(name)) { + return true; + } + } + return false; + } + get canMessage(): boolean { + if ( + this.permission_overwritesar.length === 0 && + this.hasPermission("MANAGE_CHANNELS") + ) { + const role = this.guild.roles.find((_) => _.name === "@everyone"); + if (role) { + this.addRoleToPerms(role); + } + } + return this.hasPermission("SEND_MESSAGES"); + } + sortchildren() { + this.children.sort((a, b) => { + return a.position - b.position; + }); + } + resolveparent(_guild: Guild) { + const parentid = this.parent_id; + if (!parentid) return false; + this.parent = this.localuser.channelids.get(parentid); + this.parent ??= undefined; + if (this.parent !== undefined) { + this.parent.children.push(this); + } + return this.parent !== undefined; + } + calculateReorder() { + let position = -1; + const build: { + id: string; + position: number | undefined; + parent_id: string | undefined; + }[] = []; + for (const thing of this.children) { + const thisthing: { + id: string; + position: number | undefined; + parent_id: string | undefined; + } = { id: thing.id, position: undefined, parent_id: undefined }; + if (thing.position < position) { + thing.position = thisthing.position = position + 1; + } + position = thing.position; + if (thing.move_id && thing.move_id !== thing.parent_id) { + thing.parent_id = thing.move_id; + thisthing.parent_id = thing.parent?.id; + thing.move_id = undefined; + //console.log(this.guild.channelids[thisthing.parent_id.id]); + } + if (thisthing.position || thisthing.parent_id) { + build.push(thisthing); + } + } + return build; + } + static dragged: [Channel, HTMLDivElement] | [] = []; + html: WeakRef | undefined; + get visable() { + return this.hasPermission("VIEW_CHANNEL"); + } + createguildHTML(admin = false): HTMLDivElement { + const div = document.createElement("div"); + this.html = new WeakRef(div); + if (!this.visable) { + let quit = true; + for (const thing of this.children) { + if (thing.visable) { + quit = false; + } + } + if (quit) { + return div; + } + } + // @ts-ignore I dont wanna deal with this + div["all"] = this; + div.draggable = admin; + div.addEventListener("dragstart", (e) => { + Channel.dragged = [this, div]; + e.stopImmediatePropagation(); + }); + div.addEventListener("dragend", () => { + Channel.dragged = []; + }); + if (this.type === 4) { + this.sortchildren(); + const caps = document.createElement("div"); - const decdiv = document.createElement("div"); - const decoration = document.createElement("span"); - decoration.classList.add("svgtheme", "collapse-icon", "svg-category"); - decdiv.appendChild(decoration); + const decdiv = document.createElement("div"); + const decoration = document.createElement("span"); + decoration.classList.add("svgtheme", "collapse-icon", "svg-category"); + decdiv.appendChild(decoration); - const myhtml = document.createElement("p2"); - myhtml.textContent = this.name; - decdiv.appendChild(myhtml); - caps.appendChild(decdiv); - const childrendiv = document.createElement("div"); - if (admin) { - const addchannel = document.createElement("span"); - addchannel.textContent = "+"; - addchannel.classList.add("addchannel"); - caps.appendChild(addchannel); - addchannel.onclick = (_) => { - this.guild.createchannels(this.createChannel.bind(this)); - }; - this.coatDropDiv(decdiv, childrendiv); - } - div.appendChild(caps); - caps.classList.add("capsflex"); - decdiv.classList.add("channeleffects"); - decdiv.classList.add("channel"); + const myhtml = document.createElement("p2"); + myhtml.textContent = this.name; + decdiv.appendChild(myhtml); + caps.appendChild(decdiv); + const childrendiv = document.createElement("div"); + if (admin) { + const addchannel = document.createElement("span"); + addchannel.textContent = "+"; + addchannel.classList.add("addchannel"); + caps.appendChild(addchannel); + addchannel.onclick = (_) => { + this.guild.createchannels(this.createChannel.bind(this)); + }; + this.coatDropDiv(decdiv, childrendiv); + } + div.appendChild(caps); + caps.classList.add("capsflex"); + decdiv.classList.add("channeleffects"); + decdiv.classList.add("channel"); - Channel.contextmenu.bindContextmenu(decdiv, this, undefined); - // @ts-ignore I dont wanna deal with this - decdiv["all"] = this; + Channel.contextmenu.bindContextmenu(decdiv, this, undefined); + // @ts-ignore I dont wanna deal with this + decdiv["all"] = this; - for (const channel of this.children) { - childrendiv.appendChild(channel.createguildHTML(admin)); - } - childrendiv.classList.add("channels"); - setTimeout((_: any) => { - if (!this.perminfo.collapsed) { - childrendiv.style.height = childrendiv.scrollHeight + "px"; - } - }, 100); - div.appendChild(childrendiv); - if (this.perminfo.collapsed) { - decoration.classList.add("hiddencat"); - childrendiv.style.height = "0px"; - } - decdiv.onclick = () => { - if (childrendiv.style.height !== "0px") { - decoration.classList.add("hiddencat"); - this.perminfo.collapsed = true; - this.localuser.userinfo.updateLocal(); - childrendiv.style.height = "0px"; - } else { - decoration.classList.remove("hiddencat"); - this.perminfo.collapsed = false; - this.localuser.userinfo.updateLocal(); - childrendiv.style.height = childrendiv.scrollHeight + "px"; - } - }; - } else { - div.classList.add("channel"); - if (this.hasunreads) { - div.classList.add("cunread"); - } - Channel.contextmenu.bindContextmenu(div, this, undefined); - if (admin) { - this.coatDropDiv(div); - } - // @ts-ignore I dont wanna deal with this - div["all"] = this; - const myhtml = document.createElement("span"); - myhtml.textContent = this.name; - if (this.type === 0) { - const decoration = document.createElement("span"); - div.appendChild(decoration); - decoration.classList.add("space", "svgtheme", "svg-channel"); - } else if (this.type === 2) { - // - const decoration = document.createElement("span"); - div.appendChild(decoration); - decoration.classList.add("space", "svgtheme", "svg-voice"); - } else if (this.type === 5) { - // - const decoration = document.createElement("span"); - div.appendChild(decoration); - decoration.classList.add("space", "svgtheme", "svg-announce"); - } else { - console.log(this.type); - } - div.appendChild(myhtml); - div.onclick = (_) => { - this.getHTML(); - }; - } - return div; - } - get myhtml() { - if (this.html) { - return this.html.deref(); - } else { - return undefined; - } - } - readbottom() { - if (!this.hasunreads) { - return; - } - fetch( - this.info.api + - "/channels/" + - this.id + - "/messages/" + - this.lastmessageid + - "/ack", - { - method: "POST", - headers: this.headers, - body: JSON.stringify({}), - } - ); - this.lastreadmessageid = this.lastmessageid; - this.guild.unreads(); - if (this.myhtml) { - this.myhtml.classList.remove("cunread"); - } - } - coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false) { - div.addEventListener("dragenter", (event) => { - console.log("enter"); - event.preventDefault(); - }); + for (const channel of this.children) { + childrendiv.appendChild(channel.createguildHTML(admin)); + } + childrendiv.classList.add("channels"); + setTimeout((_: any) => { + if (!this.perminfo.collapsed) { + childrendiv.style.height = childrendiv.scrollHeight + "px"; + } + }, 100); + div.appendChild(childrendiv); + if (this.perminfo.collapsed) { + decoration.classList.add("hiddencat"); + childrendiv.style.height = "0px"; + } + decdiv.onclick = () => { + if (childrendiv.style.height !== "0px") { + decoration.classList.add("hiddencat"); + this.perminfo.collapsed = true; + this.localuser.userinfo.updateLocal(); + childrendiv.style.height = "0px"; + } else { + decoration.classList.remove("hiddencat"); + this.perminfo.collapsed = false; + this.localuser.userinfo.updateLocal(); + childrendiv.style.height = childrendiv.scrollHeight + "px"; + } + }; + } else { + div.classList.add("channel"); + if (this.hasunreads) { + div.classList.add("cunread"); + } + Channel.contextmenu.bindContextmenu(div, this, undefined); + if (admin) { + this.coatDropDiv(div); + } + // @ts-ignore I dont wanna deal with this + div["all"] = this; + const myhtml = document.createElement("span"); + myhtml.textContent = this.name; + if (this.type === 0) { + const decoration = document.createElement("span"); + div.appendChild(decoration); + decoration.classList.add("space", "svgtheme", "svg-channel"); + } else if (this.type === 2) { + // + const decoration = document.createElement("span"); + div.appendChild(decoration); + decoration.classList.add("space", "svgtheme", "svg-voice"); + } else if (this.type === 5) { + // + const decoration = document.createElement("span"); + div.appendChild(decoration); + decoration.classList.add("space", "svgtheme", "svg-announce"); + } else { + console.log(this.type); + } + div.appendChild(myhtml); + div.onclick = (_) => { + this.getHTML(); + }; + } + return div; + } + get myhtml() { + if (this.html) { + return this.html.deref(); + } else { + return undefined; + } + } + readbottom() { + if (!this.hasunreads) { + return; + } + fetch( + this.info.api + + "/channels/" + + this.id + + "/messages/" + + this.lastmessageid + + "/ack", + { + method: "POST", + headers: this.headers, + body: JSON.stringify({}), + } + ); + this.lastreadmessageid = this.lastmessageid; + this.guild.unreads(); + if (this.myhtml) { + this.myhtml.classList.remove("cunread"); + } + } + coatDropDiv(div: HTMLDivElement, container: HTMLElement | boolean = false) { + div.addEventListener("dragenter", (event) => { + console.log("enter"); + event.preventDefault(); + }); - div.addEventListener("dragover", (event) => { - event.preventDefault(); - }); + div.addEventListener("dragover", (event) => { + event.preventDefault(); + }); - div.addEventListener("drop", (event) => { - const that = Channel.dragged[0]; - if (!that) return; - event.preventDefault(); - if (container) { - that.move_id = this.id; - if (that.parent) { - that.parent.children.splice(that.parent.children.indexOf(that), 1); - } - that.parent = this; - (container as HTMLElement).prepend( - Channel.dragged[1] as HTMLDivElement - ); - this.children.unshift(that); - } else { - console.log(this, Channel.dragged); - that.move_id = this.parent_id; - if (that.parent) { - that.parent.children.splice(that.parent.children.indexOf(that), 1); - } else { - this.guild.headchannels.splice( - this.guild.headchannels.indexOf(that), - 1 - ); - } - that.parent = this.parent; - if (that.parent) { - const build: Channel[] = []; - for (let i = 0; i < that.parent.children.length; i++) { - build.push(that.parent.children[i]); - if (that.parent.children[i] === this) { - build.push(that); - } - } - that.parent.children = build; - } else { - const build: Channel[] = []; - for (let i = 0; i < this.guild.headchannels.length; i++) { - build.push(this.guild.headchannels[i]); - if (this.guild.headchannels[i] === this) { - build.push(that); - } - } - this.guild.headchannels = build; - } - if (Channel.dragged[1]) { - div.after(Channel.dragged[1]); - } - } - this.guild.calculateReorder(); - }); + div.addEventListener("drop", (event) => { + const that = Channel.dragged[0]; + if (!that) return; + event.preventDefault(); + if (container) { + that.move_id = this.id; + if (that.parent) { + that.parent.children.splice(that.parent.children.indexOf(that), 1); + } + that.parent = this; + (container as HTMLElement).prepend( + Channel.dragged[1] as HTMLDivElement + ); + this.children.unshift(that); + } else { + console.log(this, Channel.dragged); + that.move_id = this.parent_id; + if (that.parent) { + that.parent.children.splice(that.parent.children.indexOf(that), 1); + } else { + this.guild.headchannels.splice( + this.guild.headchannels.indexOf(that), + 1 + ); + } + that.parent = this.parent; + if (that.parent) { + const build: Channel[] = []; + for (let i = 0; i < that.parent.children.length; i++) { + build.push(that.parent.children[i]); + if (that.parent.children[i] === this) { + build.push(that); + } + } + that.parent.children = build; + } else { + const build: Channel[] = []; + for (let i = 0; i < this.guild.headchannels.length; i++) { + build.push(this.guild.headchannels[i]); + if (this.guild.headchannels[i] === this) { + build.push(that); + } + } + this.guild.headchannels = build; + } + if (Channel.dragged[1]) { + div.after(Channel.dragged[1]); + } + } + this.guild.calculateReorder(); + }); - return div; - } - createChannel(name: string, type: number) { - fetch(this.info.api + "/guilds/" + this.guild.id + "/channels", { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - name, - type, - parent_id: this.id, - permission_overwrites: [], - }), - }); - } - editChannel() { - let name = this.name; - let topic = this.topic; - let nsfw = this.nsfw; - const thisid = this.id; - const thistype = this.type; - const full = new Dialog([ - "hdiv", - [ - "vdiv", - [ - "textbox", - "Channel name:", - this.name, - function (this: HTMLInputElement) { - name = this.value; - }, - ], - [ - "mdbox", - "Channel topic:", - this.topic, - function (this: HTMLTextAreaElement) { - topic = this.value; - }, - ], - [ - "checkbox", - "NSFW Channel", - this.nsfw, - function (this: HTMLInputElement) { - nsfw = this.checked; - }, - ], - [ - "button", - "", - "submit", - () => { - fetch(this.info.api + "/channels/" + thisid, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - name, - type: thistype, - topic, - bitrate: 64000, - user_limit: 0, - nsfw, - flags: 0, - rate_limit_per_user: 0, - }), - }); - console.log(full); - full.hide(); - }, - ], - ], - ]); - full.show(); - console.log(full); - } - deleteChannel() { - fetch(this.info.api + "/channels/" + this.id, { - method: "DELETE", - headers: this.headers, - }); - } - setReplying(message: Message) { - if (this.replyingto?.div) { - this.replyingto.div.classList.remove("replying"); - } - this.replyingto = message; - if (!this.replyingto?.div) return; - console.log(message); - this.replyingto.div.classList.add("replying"); - this.makereplybox(); - } - makereplybox() { - const replybox = document.getElementById("replybox") as HTMLElement; - if (this.replyingto) { - replybox.innerHTML = ""; - const span = document.createElement("span"); - span.textContent = "Replying to " + this.replyingto.author.username; - const X = document.createElement("button"); - X.onclick = (_) => { - if (this.replyingto?.div) { - this.replyingto.div.classList.remove("replying"); - } - replybox.classList.add("hideReplyBox"); - this.replyingto = null; - replybox.innerHTML = ""; - }; - replybox.classList.remove("hideReplyBox"); - X.textContent = "⦻"; - X.classList.add("cancelReply"); - replybox.append(span); - replybox.append(X); - } else { - replybox.classList.add("hideReplyBox"); - } - } - async getmessage(id: string): Promise { - const message = this.messages.get(id); - if (message) { - return message; - } else { - const gety = await fetch( - this.info.api + - "/channels/" + - this.id + - "/messages?limit=1&around=" + - id, - { headers: this.headers } - ); - const json = await gety.json(); - return new Message(json[0], this); - } - } - static genid: number = 0; - async getHTML() { - const id = ++Channel.genid; - if (this.localuser.channelfocus) { - this.localuser.channelfocus.infinite.delete(); - } - if (this.guild !== this.localuser.lookingguild) { - this.guild.loadGuild(); - } - if (this.localuser.channelfocus && this.localuser.channelfocus.myhtml) { - this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); - } - if (this.myhtml) { - this.myhtml.classList.add("viewChannel"); - } - this.guild.prevchannel = this; - this.guild.perminfo.prevchannel = this.id; - this.localuser.userinfo.updateLocal(); - this.localuser.channelfocus = this; - const prom = this.infinite.delete(); - history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); + return div; + } + createChannel(name: string, type: number) { + fetch(this.info.api + "/guilds/" + this.guild.id + "/channels", { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + type, + parent_id: this.id, + permission_overwrites: [], + }), + }); + } + editChannel() { + let name = this.name; + let topic = this.topic; + let nsfw = this.nsfw; + const thisid = this.id; + const thistype = this.type; + const full = new Dialog([ + "hdiv", + [ + "vdiv", + [ + "textbox", + "Channel name:", + this.name, + function (this: HTMLInputElement) { + name = this.value; + }, + ], + [ + "mdbox", + "Channel topic:", + this.topic, + function (this: HTMLTextAreaElement) { + topic = this.value; + }, + ], + [ + "checkbox", + "NSFW Channel", + this.nsfw, + function (this: HTMLInputElement) { + nsfw = this.checked; + }, + ], + [ + "button", + "", + "submit", + () => { + fetch(this.info.api + "/channels/" + thisid, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + name, + type: thistype, + topic, + bitrate: 64000, + user_limit: 0, + nsfw, + flags: 0, + rate_limit_per_user: 0, + }), + }); + console.log(full); + full.hide(); + }, + ], + ], + ]); + full.show(); + console.log(full); + } + deleteChannel() { + fetch(this.info.api + "/channels/" + this.id, { + method: "DELETE", + headers: this.headers, + }); + } + setReplying(message: Message) { + if (this.replyingto?.div) { + this.replyingto.div.classList.remove("replying"); + } + this.replyingto = message; + if (!this.replyingto?.div) return; + console.log(message); + this.replyingto.div.classList.add("replying"); + this.makereplybox(); + } + makereplybox() { + const replybox = document.getElementById("replybox") as HTMLElement; + if (this.replyingto) { + replybox.innerHTML = ""; + const span = document.createElement("span"); + span.textContent = "Replying to " + this.replyingto.author.username; + const X = document.createElement("button"); + X.onclick = (_) => { + if (this.replyingto?.div) { + this.replyingto.div.classList.remove("replying"); + } + replybox.classList.add("hideReplyBox"); + this.replyingto = null; + replybox.innerHTML = ""; + }; + replybox.classList.remove("hideReplyBox"); + X.textContent = "⦻"; + X.classList.add("cancelReply"); + replybox.append(span); + replybox.append(X); + } else { + replybox.classList.add("hideReplyBox"); + } + } + async getmessage(id: string): Promise { + const message = this.messages.get(id); + if (message) { + return message; + } else { + const gety = await fetch( + this.info.api + + "/channels/" + + this.id + + "/messages?limit=1&around=" + + id, + { headers: this.headers } + ); + const json = await gety.json(); + return new Message(json[0], this); + } + } + static genid: number = 0; + async getHTML() { + const id = ++Channel.genid; + if (this.localuser.channelfocus) { + this.localuser.channelfocus.infinite.delete(); + } + if (this.guild !== this.localuser.lookingguild) { + this.guild.loadGuild(); + } + if (this.localuser.channelfocus && this.localuser.channelfocus.myhtml) { + this.localuser.channelfocus.myhtml.classList.remove("viewChannel"); + } + if (this.myhtml) { + this.myhtml.classList.add("viewChannel"); + } + this.guild.prevchannel = this; + this.guild.perminfo.prevchannel = this.id; + this.localuser.userinfo.updateLocal(); + this.localuser.channelfocus = this; + const prom = this.infinite.delete(); + history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); - this.localuser.pageTitle("#" + this.name); - const channelTopic = document.getElementById( - "channelTopic" - ) as HTMLSpanElement; - if (this.topic) { - channelTopic.innerHTML = new MarkDown( - this.topic, - this - ).makeHTML().innerHTML; - channelTopic.removeAttribute("hidden"); - } else channelTopic.setAttribute("hidden", ""); + this.localuser.pageTitle("#" + this.name); + const channelTopic = document.getElementById( + "channelTopic" + ) as HTMLSpanElement; + if (this.topic) { + channelTopic.innerHTML = new MarkDown( + this.topic, + this + ).makeHTML().innerHTML; + channelTopic.removeAttribute("hidden"); + } else channelTopic.setAttribute("hidden", ""); - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - Channel.regenLoadingMessages(); - loading.classList.add("loading"); - this.rendertyping(); - await this.putmessages(); - await prom; - if (id !== Channel.genid) { - return; - } - this.makereplybox(); + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + Channel.regenLoadingMessages(); + loading.classList.add("loading"); + this.rendertyping(); + await this.putmessages(); + await prom; + if (id !== Channel.genid) { + return; + } + this.makereplybox(); - await this.buildmessages(); - //loading.classList.remove("loading"); - (document.getElementById("typebox") as HTMLDivElement).contentEditable = - "" + this.canMessage; - } - typingmap: Map = new Map(); - async typingStart(typing: startTypingjson): Promise { - const memb = await Member.new(typing.d.member!, this.guild); - if (!memb) return; - if (memb.id === this.localuser.user.id) { - console.log("you is typing"); - return; - } - console.log("user is typing and you should see it"); - this.typingmap.set(memb, Date.now()); - setTimeout(this.rendertyping.bind(this), 10000); - this.rendertyping(); - } - rendertyping(): void { - const typingtext = document.getElementById("typing") as HTMLDivElement; - let build = ""; - let showing = false; - let i = 0; - const curtime = Date.now() - 5000; - for (const thing of this.typingmap.keys()) { - if ((this.typingmap.get(thing) as number) > curtime) { - if (i !== 0) { - build += ", "; - } - i++; - if (thing.nick) { - build += thing.nick; - } else { - build += thing.user.username; - } - showing = true; - } else { - this.typingmap.delete(thing); - } - } - if (i > 1) { - build += " are typing"; - } else { - build += " is typing"; - } - if (this.localuser.channelfocus === this) { - if (showing) { - typingtext.classList.remove("hidden"); - const typingtext2 = document.getElementById( - "typingtext" - ) as HTMLDivElement; - typingtext2.textContent = build; - } else { - typingtext.classList.add("hidden"); - } - } - } - static regenLoadingMessages() { - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - loading.innerHTML = ""; - for (let i = 0; i < 15; i++) { - const div = document.createElement("div"); - div.classList.add("loadingmessage"); - if (Math.random() < 0.5) { - const pfp = document.createElement("div"); - pfp.classList.add("loadingpfp"); - const username = document.createElement("div"); - username.style.width = Math.floor(Math.random() * 96 * 1.5 + 40) + "px"; - username.classList.add("loadingcontent"); - div.append(pfp, username); - } - const content = document.createElement("div"); - content.style.width = Math.floor(Math.random() * 96 * 3 + 40) + "px"; - content.style.height = Math.floor(Math.random() * 3 + 1) * 20 + "px"; - content.classList.add("loadingcontent"); - div.append(content); - loading.append(div); - } - } - lastmessage: Message | undefined; - async putmessages() { - if (this.allthewayup) { - return; - } - if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { - return; - } - const j = await fetch( - this.info.api + "/channels/" + this.id + "/messages?limit=100", - { - headers: this.headers, - } - ); + await this.buildmessages(); + //loading.classList.remove("loading"); + (document.getElementById("typebox") as HTMLDivElement).contentEditable = + "" + this.canMessage; + } + typingmap: Map = new Map(); + async typingStart(typing: startTypingjson): Promise { + const memb = await Member.new(typing.d.member!, this.guild); + if (!memb) return; + if (memb.id === this.localuser.user.id) { + console.log("you is typing"); + return; + } + console.log("user is typing and you should see it"); + this.typingmap.set(memb, Date.now()); + setTimeout(this.rendertyping.bind(this), 10000); + this.rendertyping(); + } + rendertyping(): void { + const typingtext = document.getElementById("typing") as HTMLDivElement; + let build = ""; + let showing = false; + let i = 0; + const curtime = Date.now() - 5000; + for (const thing of this.typingmap.keys()) { + if ((this.typingmap.get(thing) as number) > curtime) { + if (i !== 0) { + build += ", "; + } + i++; + if (thing.nick) { + build += thing.nick; + } else { + build += thing.user.username; + } + showing = true; + } else { + this.typingmap.delete(thing); + } + } + if (i > 1) { + build += " are typing"; + } else { + build += " is typing"; + } + if (this.localuser.channelfocus === this) { + if (showing) { + typingtext.classList.remove("hidden"); + const typingtext2 = document.getElementById( + "typingtext" + ) as HTMLDivElement; + typingtext2.textContent = build; + } else { + typingtext.classList.add("hidden"); + } + } + } + static regenLoadingMessages() { + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + loading.innerHTML = ""; + for (let i = 0; i < 15; i++) { + const div = document.createElement("div"); + div.classList.add("loadingmessage"); + if (Math.random() < 0.5) { + const pfp = document.createElement("div"); + pfp.classList.add("loadingpfp"); + const username = document.createElement("div"); + username.style.width = Math.floor(Math.random() * 96 * 1.5 + 40) + "px"; + username.classList.add("loadingcontent"); + div.append(pfp, username); + } + const content = document.createElement("div"); + content.style.width = Math.floor(Math.random() * 96 * 3 + 40) + "px"; + content.style.height = Math.floor(Math.random() * 3 + 1) * 20 + "px"; + content.classList.add("loadingcontent"); + div.append(content); + loading.append(div); + } + } + lastmessage: Message | undefined; + async putmessages() { + if (this.allthewayup) { + return; + } + if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { + return; + } + const j = await fetch( + this.info.api + "/channels/" + this.id + "/messages?limit=100", + { + headers: this.headers, + } + ); - const response = await j.json(); - if (response.length !== 100) { - this.allthewayup = true; - } - let prev: Message | undefined; - for (const thing of response) { - const message = new Message(thing, this); - if (prev) { - this.idToNext.set(message.id, prev.id); - this.idToPrev.set(prev.id, message.id); - } else { - this.lastmessage = message; - this.lastmessageid = message.id; - } - prev = message; - } - } - delChannel(json: channeljson) { - const build: Channel[] = []; - for (const thing of this.children) { - if (thing.id !== json.id) { - build.push(thing); - } - } - this.children = build; - } - async grabAfter(id: string) { - if (id === this.lastmessage?.id) { - return; - } - await fetch( - this.info.api + - "/channels/" + - this.id + - "/messages?limit=100&after=" + - id, - { - headers: this.headers, - } - ) - .then((j) => { - return j.json(); - }) - .then((response) => { - let previd: string = id; - for (const i in response) { - let messager: Message; - let willbreak = false; - if (this.messages.has(response[i].id)) { - messager = this.messages.get(response[i].id) as Message; - willbreak = true; - } else { - messager = new Message(response[i], this); - } - this.idToPrev.set(messager.id, previd); - this.idToNext.set(previd, messager.id); - previd = messager.id; - if (willbreak) { - break; - } - } - //out.buildmessages(); - }); - } - topid!: string; - async grabBefore(id: string) { - if (this.topid && id === this.topid) { - return; - } + const response = await j.json(); + if (response.length !== 100) { + this.allthewayup = true; + } + let prev: Message | undefined; + for (const thing of response) { + const message = new Message(thing, this); + if (prev) { + this.idToNext.set(message.id, prev.id); + this.idToPrev.set(prev.id, message.id); + } else { + this.lastmessage = message; + this.lastmessageid = message.id; + } + prev = message; + } + } + delChannel(json: channeljson) { + const build: Channel[] = []; + for (const thing of this.children) { + if (thing.id !== json.id) { + build.push(thing); + } + } + this.children = build; + } + async grabAfter(id: string) { + if (id === this.lastmessage?.id) { + return; + } + await fetch( + this.info.api + + "/channels/" + + this.id + + "/messages?limit=100&after=" + + id, + { + headers: this.headers, + } + ) + .then((j) => { + return j.json(); + }) + .then((response) => { + let previd: string = id; + for (const i in response) { + let messager: Message; + let willbreak = false; + if (this.messages.has(response[i].id)) { + messager = this.messages.get(response[i].id) as Message; + willbreak = true; + } else { + messager = new Message(response[i], this); + } + this.idToPrev.set(messager.id, previd); + this.idToNext.set(previd, messager.id); + previd = messager.id; + if (willbreak) { + break; + } + } + //out.buildmessages(); + }); + } + topid!: string; + async grabBefore(id: string) { + if (this.topid && id === this.topid) { + return; + } - await fetch( - this.info.api + - "/channels/" + - this.id + - "/messages?before=" + - id + - "&limit=100", - { - headers: this.headers, - } - ) - .then((j) => { - return j.json(); - }) - .then((response: messagejson[]) => { - if (response.length < 100) { - this.allthewayup = true; - if (response.length === 0) { - this.topid = id; - } - } - let previd = id; - for (const i in response) { - let messager: Message; - let willbreak = false; - if (this.messages.has(response[i].id)) { - console.log("flaky"); - messager = this.messages.get(response[i].id) as Message; - willbreak = true; - } else { - messager = new Message(response[i], this); - } + await fetch( + this.info.api + + "/channels/" + + this.id + + "/messages?before=" + + id + + "&limit=100", + { + headers: this.headers, + } + ) + .then((j) => { + return j.json(); + }) + .then((response: messagejson[]) => { + if (response.length < 100) { + this.allthewayup = true; + if (response.length === 0) { + this.topid = id; + } + } + let previd = id; + for (const i in response) { + let messager: Message; + let willbreak = false; + if (this.messages.has(response[i].id)) { + console.log("flaky"); + messager = this.messages.get(response[i].id) as Message; + willbreak = true; + } else { + messager = new Message(response[i], this); + } - this.idToNext.set(messager.id, previd); - this.idToPrev.set(previd, messager.id); - previd = messager.id; + this.idToNext.set(messager.id, previd); + this.idToPrev.set(previd, messager.id); + previd = messager.id; - if (Number(i) === response.length - 1 && response.length < 100) { - this.topid = previd; - } - if (willbreak) { - break; - } - } - }); - } - /** - * Please dont use this, its not implemented. - * @deprecated - * @todo - **/ - async grabArround(/* id: string */) { - //currently unused and no plans to use it yet - throw new Error("please don't call this, no one has implemented it :P"); - } - async buildmessages() { - this.infinitefocus = false; - this.tryfocusinfinate(); - } - infinitefocus = false; - async tryfocusinfinate() { - if (this.infinitefocus) return; - this.infinitefocus = true; - const messages = document.getElementById("channelw") as HTMLDivElement; - const messageContainers = Array.from( - messages.getElementsByClassName("messagecontainer") - ); - for (const thing of messageContainers) { - thing.remove(); - } - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - const removetitle = document.getElementById("removetitle"); - //messages.innerHTML=""; - let id: string | undefined; - if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { - id = this.lastreadmessageid; - } else if ( - this.lastreadmessageid && - (id = this.findClosest(this.lastreadmessageid)) - ) { - } else if (this.lastmessageid && this.messages.has(this.lastmessageid)) { - id = this.goBackIds(this.lastmessageid, 50); - } - if (!id) { - if (!removetitle) { - const title = document.createElement("h2"); - title.id = "removetitle"; - title.textContent = - "No messages appear to be here, be the first to say something!"; - title.classList.add("titlespace"); - messages.append(title); - } - this.infinitefocus = false; - loading.classList.remove("loading"); - return; - } else if (removetitle) { - removetitle.remove(); - } - if (this.localuser.channelfocus !== this) { - return; - } - const elements = Array.from(messages.getElementsByClassName("scroller")); - for (const elm of elements) { - elm.remove(); - console.warn("rouge element detected and removed"); - } - messages.append(await this.infinite.getDiv(id)); - this.infinite.updatestuff(); - this.infinite.watchForChange().then(async (_) => { - //await new Promise(resolve => setTimeout(resolve, 0)); - this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P - loading.classList.remove("loading"); - }); - //this.infinite.focus(id.id,false); - } - private goBackIds( - id: string, - back: number, - returnifnotexistant = true - ): string | undefined { - while (back !== 0) { - const nextid = this.idToPrev.get(id); - if (nextid) { - id = nextid; - back--; - } else { - if (returnifnotexistant) { - break; - } else { - return undefined; - } - } - } - return id; - } - private findClosest(id: string | undefined) { - if (!this.lastmessageid || !id) return; - let flake: string | undefined = this.lastmessageid; - const time = SnowFlake.stringToUnixTime(id); - let flaketime = SnowFlake.stringToUnixTime(flake); - while (flake && time < flaketime) { - flake = this.idToPrev.get(flake); + if (Number(i) === response.length - 1 && response.length < 100) { + this.topid = previd; + } + if (willbreak) { + break; + } + } + }); + } + /** + * Please dont use this, its not implemented. + * @deprecated + * @todo + **/ + async grabArround(/* id: string */) { + //currently unused and no plans to use it yet + throw new Error("please don't call this, no one has implemented it :P"); + } + async buildmessages() { + this.infinitefocus = false; + this.tryfocusinfinate(); + } + infinitefocus = false; + async tryfocusinfinate() { + if (this.infinitefocus) return; + this.infinitefocus = true; + const messages = document.getElementById("channelw") as HTMLDivElement; + const messageContainers = Array.from( + messages.getElementsByClassName("messagecontainer") + ); + for (const thing of messageContainers) { + thing.remove(); + } + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + const removetitle = document.getElementById("removetitle"); + //messages.innerHTML=""; + let id: string | undefined; + if (this.lastreadmessageid && this.messages.has(this.lastreadmessageid)) { + id = this.lastreadmessageid; + } else if ( + this.lastreadmessageid && + (id = this.findClosest(this.lastreadmessageid)) + ) { + } else if (this.lastmessageid && this.messages.has(this.lastmessageid)) { + id = this.goBackIds(this.lastmessageid, 50); + } + if (!id) { + if (!removetitle) { + const title = document.createElement("h2"); + title.id = "removetitle"; + title.textContent = + "No messages appear to be here, be the first to say something!"; + title.classList.add("titlespace"); + messages.append(title); + } + this.infinitefocus = false; + loading.classList.remove("loading"); + return; + } else if (removetitle) { + removetitle.remove(); + } + if (this.localuser.channelfocus !== this) { + return; + } + const elements = Array.from(messages.getElementsByClassName("scroller")); + for (const elm of elements) { + elm.remove(); + console.warn("rouge element detected and removed"); + } + messages.append(await this.infinite.getDiv(id)); + this.infinite.updatestuff(); + this.infinite.watchForChange().then(async (_) => { + //await new Promise(resolve => setTimeout(resolve, 0)); + this.infinite.focus(id, false); //if someone could figure out how to make this work correctly without this, that's be great :P + loading.classList.remove("loading"); + }); + //this.infinite.focus(id.id,false); + } + private goBackIds( + id: string, + back: number, + returnifnotexistant = true + ): string | undefined { + while (back !== 0) { + const nextid = this.idToPrev.get(id); + if (nextid) { + id = nextid; + back--; + } else { + if (returnifnotexistant) { + break; + } else { + return undefined; + } + } + } + return id; + } + private findClosest(id: string | undefined) { + if (!this.lastmessageid || !id) return; + let flake: string | undefined = this.lastmessageid; + const time = SnowFlake.stringToUnixTime(id); + let flaketime = SnowFlake.stringToUnixTime(flake); + while (flake && time < flaketime) { + flake = this.idToPrev.get(flake); - if (!flake) { - return; - } - flaketime = SnowFlake.stringToUnixTime(flake); - } - return flake; - } - updateChannel(json: channeljson) { - this.type = json.type; - this.name = json.name; - const parent = this.localuser.channelids.get(json.parent_id); - if (parent) { - this.parent = parent; - this.parent_id = parent.id; - } else { - this.parent = undefined; - this.parent_id = undefined; - } + if (!flake) { + return; + } + flaketime = SnowFlake.stringToUnixTime(flake); + } + return flake; + } + updateChannel(json: channeljson) { + this.type = json.type; + this.name = json.name; + const parent = this.localuser.channelids.get(json.parent_id); + if (parent) { + this.parent = parent; + this.parent_id = parent.id; + } else { + this.parent = undefined; + this.parent_id = undefined; + } - this.children = []; - this.guild_id = json.guild_id; - this.permission_overwrites = new Map(); - for (const thing of json.permission_overwrites) { - if ( - thing.id === "1182819038095799904" || - thing.id === "1182820803700625444" - ) { - continue; - } - this.permission_overwrites.set( - thing.id, - new Permissions(thing.allow, thing.deny) - ); - const permisions = this.permission_overwrites.get(thing.id); - if (permisions) { - const role = this.guild.roleids.get(thing.id); - if (role) { - this.permission_overwritesar.push([role, permisions]); - } - } - } - this.topic = json.topic; - this.nsfw = json.nsfw; - } - typingstart() { - if (this.typing > Date.now()) { - return; - } - this.typing = Date.now() + 6000; - fetch(this.info.api + "/channels/" + this.id + "/typing", { - method: "POST", - headers: this.headers, - }); - } - get notification() { - let notinumber: number | null = this.message_notifications; - if (Number(notinumber) === 3) { - notinumber = null; - } - notinumber ??= this.guild.message_notifications; - switch (Number(notinumber)) { - case 0: - return "all"; - case 1: - return "mentions"; - case 2: - return "none"; - case 3: - default: - return "default"; - } - } - async sendMessage( - content: string, - { - attachments = [], - replyingto = null, - }: { attachments: Blob[]; embeds: embedjson; replyingto: Message | null } - ) { - let replyjson: any; - if (replyingto) { - replyjson = { - guild_id: replyingto.guild.id, - channel_id: replyingto.channel.id, - message_id: replyingto.id, - }; - } - if (attachments.length === 0) { - const body = { - content, - nonce: Math.floor(Math.random() * 1000000000), - message_reference: undefined, - }; - if (replyjson) { - body.message_reference = replyjson; - } - return await fetch(this.info.api + "/channels/" + this.id + "/messages", { - method: "POST", - headers: this.headers, - body: JSON.stringify(body), - }); - } else { - const formData = new FormData(); - const body = { - content, - nonce: Math.floor(Math.random() * 1000000000), - message_reference: undefined, - }; - if (replyjson) { - body.message_reference = replyjson; - } - formData.append("payload_json", JSON.stringify(body)); - for (const i in attachments) { - formData.append("files[" + i + "]", attachments[i]); - } - return await fetch(this.info.api + "/channels/" + this.id + "/messages", { - method: "POST", - body: formData, - headers: { Authorization: this.headers.Authorization }, - }); - } - } - messageCreate(messagep: messageCreateJson): void { - if (!this.hasPermission("VIEW_CHANNEL")) { - return; - } - const messagez = new Message(messagep.d, this); - this.lastmessage = messagez; - if (this.lastmessageid) { - this.idToNext.set(this.lastmessageid, messagez.id); - this.idToPrev.set(messagez.id, this.lastmessageid); - } + this.children = []; + this.guild_id = json.guild_id; + this.permission_overwrites = new Map(); + for (const thing of json.permission_overwrites) { + if ( + thing.id === "1182819038095799904" || + thing.id === "1182820803700625444" + ) { + continue; + } + this.permission_overwrites.set( + thing.id, + new Permissions(thing.allow, thing.deny) + ); + const permisions = this.permission_overwrites.get(thing.id); + if (permisions) { + const role = this.guild.roleids.get(thing.id); + if (role) { + this.permission_overwritesar.push([role, permisions]); + } + } + } + this.topic = json.topic; + this.nsfw = json.nsfw; + } + typingstart() { + if (this.typing > Date.now()) { + return; + } + this.typing = Date.now() + 6000; + fetch(this.info.api + "/channels/" + this.id + "/typing", { + method: "POST", + headers: this.headers, + }); + } + get notification() { + let notinumber: number | null = this.message_notifications; + if (Number(notinumber) === 3) { + notinumber = null; + } + notinumber ??= this.guild.message_notifications; + switch (Number(notinumber)) { + case 0: + return "all"; + case 1: + return "mentions"; + case 2: + return "none"; + case 3: + default: + return "default"; + } + } + async sendMessage( + content: string, + { + attachments = [], + replyingto = null, + }: { attachments: Blob[]; embeds: embedjson; replyingto: Message | null } + ) { + let replyjson: any; + if (replyingto) { + replyjson = { + guild_id: replyingto.guild.id, + channel_id: replyingto.channel.id, + message_id: replyingto.id, + }; + } + if (attachments.length === 0) { + const body = { + content, + nonce: Math.floor(Math.random() * 1000000000), + message_reference: undefined, + }; + if (replyjson) { + body.message_reference = replyjson; + } + return await fetch(this.info.api + "/channels/" + this.id + "/messages", { + method: "POST", + headers: this.headers, + body: JSON.stringify(body), + }); + } else { + const formData = new FormData(); + const body = { + content, + nonce: Math.floor(Math.random() * 1000000000), + message_reference: undefined, + }; + if (replyjson) { + body.message_reference = replyjson; + } + formData.append("payload_json", JSON.stringify(body)); + for (const i in attachments) { + formData.append("files[" + i + "]", attachments[i]); + } + return await fetch(this.info.api + "/channels/" + this.id + "/messages", { + method: "POST", + body: formData, + headers: { Authorization: this.headers.Authorization }, + }); + } + } + messageCreate(messagep: messageCreateJson): void { + if (!this.hasPermission("VIEW_CHANNEL")) { + return; + } + const messagez = new Message(messagep.d, this); + this.lastmessage = messagez; + if (this.lastmessageid) { + this.idToNext.set(this.lastmessageid, messagez.id); + this.idToPrev.set(messagez.id, this.lastmessageid); + } - this.lastmessageid = messagez.id; + this.lastmessageid = messagez.id; - if (messagez.author === this.localuser.user) { - this.lastreadmessageid = messagez.id; - if (this.myhtml) { - this.myhtml.classList.remove("cunread"); - } - } else { - if (this.myhtml) { - this.myhtml.classList.add("cunread"); - } - } - this.guild.unreads(); - if (this === this.localuser.channelfocus) { - if (!this.infinitefocus) { - this.tryfocusinfinate(); - } - this.infinite.addedBottom(); - } - if (messagez.author === this.localuser.user) { - return; - } - if ( - this.localuser.lookingguild?.prevchannel === this && - document.hasFocus() - ) { - return; - } - if (this.notification === "all") { - this.notify(messagez); - } else if ( - this.notification === "mentions" && - messagez.mentionsuser(this.localuser.user) - ) { - this.notify(messagez); - } - } - notititle(message: Message): string { - return ( - message.author.username + - " > " + - this.guild.properties.name + - " > " + - this.name - ); - } - notify(message: Message, deep = 0) { - Voice.noises(Voice.getNotificationSound()); - if (!("Notification" in window)) { - } else if (Notification.permission === "granted") { - let noticontent: string | undefined | null = message.content.textContent; - if (message.embeds[0]) { - noticontent ||= message.embeds[0]?.json.title; - noticontent ||= message.content.textContent; - } - noticontent ||= "Blank Message"; - let imgurl: null | string = null; - const images = message.getimages(); - if (images.length) { - const image = images[0]; - if (image.proxy_url) { - imgurl ||= image.proxy_url; - } - imgurl ||= image.url; - } - const notification = new Notification(this.notititle(message), { - body: noticontent, - icon: message.author.getpfpsrc(), - image: imgurl, - }); - notification.addEventListener("click", (_) => { - window.focus(); - this.getHTML(); - }); - } else if (Notification.permission !== "denied") { - Notification.requestPermission().then(() => { - if (deep === 3) { - return; - } - this.notify(message, deep + 1); - }); - } - } - async addRoleToPerms(role: Role) { - await fetch( - this.info.api + "/channels/" + this.id + "/permissions/" + role.id, - { - method: "PUT", - headers: this.headers, - body: JSON.stringify({ - allow: "0", - deny: "0", - id: role.id, - type: 0, - }), - } - ); - const perm = new Permissions("0", "0"); - this.permission_overwrites.set(role.id, perm); - this.permission_overwritesar.push([role, perm]); - } - async updateRolePermissions(id: string, perms: Permissions) { - const permission = this.permission_overwrites.get(id); - if (permission) { - permission.allow = perms.allow; - permission.deny = perms.deny; - await fetch( - this.info.api + "/channels/" + this.id + "/permissions/" + id, - { - method: "PUT", - headers: this.headers, - body: JSON.stringify({ - allow: permission.allow.toString(), - deny: permission.deny.toString(), - id, - type: 0, - }), - } - ); - } - } -} -Channel.setupcontextmenu(); -export { Channel }; + if (messagez.author === this.localuser.user) { + this.lastreadmessageid = messagez.id; + if (this.myhtml) { + this.myhtml.classList.remove("cunread"); + } + } else { + if (this.myhtml) { + this.myhtml.classList.add("cunread"); + } + } + this.guild.unreads(); + if (this === this.localuser.channelfocus) { + if (!this.infinitefocus) { + this.tryfocusinfinate(); + } + this.infinite.addedBottom(); + } + if (messagez.author === this.localuser.user) { + return; + } + if ( + this.localuser.lookingguild?.prevchannel === this && + document.hasFocus() + ) { + return; + } + if (this.notification === "all") { + this.notify(messagez); + } else if ( + this.notification === "mentions" && + messagez.mentionsuser(this.localuser.user) + ) { + this.notify(messagez); + } + } + notititle(message: Message): string { + return ( + message.author.username + + " > " + + this.guild.properties.name + + " > " + + this.name + ); + } + notify(message: Message, deep = 0) { + Voice.noises(Voice.getNotificationSound()); + if (!("Notification" in window)) { + } else if (Notification.permission === "granted") { + let noticontent: string | undefined | null = message.content.textContent; + if (message.embeds[0]) { + noticontent ||= message.embeds[0]?.json.title; + noticontent ||= message.content.textContent; + } + noticontent ||= "Blank Message"; + let imgurl: null | string = null; + const images = message.getimages(); + if (images.length) { + const image = images[0]; + if (image.proxy_url) { + imgurl ||= image.proxy_url; + } + imgurl ||= image.url; + } + const notification = new Notification(this.notititle(message), { + body: noticontent, + icon: message.author.getpfpsrc(), + image: imgurl, + }); + notification.addEventListener("click", (_) => { + window.focus(); + this.getHTML(); + }); + } else if (Notification.permission !== "denied") { + Notification.requestPermission().then(() => { + if (deep === 3) { + return; + } + this.notify(message, deep + 1); + }); + } + } + async addRoleToPerms(role: Role) { + await fetch( + this.info.api + "/channels/" + this.id + "/permissions/" + role.id, + { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: "0", + deny: "0", + id: role.id, + type: 0, + }), + } + ); + const perm = new Permissions("0", "0"); + this.permission_overwrites.set(role.id, perm); + this.permission_overwritesar.push([role, perm]); + } + async updateRolePermissions(id: string, perms: Permissions) { + const permission = this.permission_overwrites.get(id); + if (permission) { + permission.allow = perms.allow; + permission.deny = perms.deny; + await fetch( + this.info.api + "/channels/" + this.id + "/permissions/" + id, + { + method: "PUT", + headers: this.headers, + body: JSON.stringify({ + allow: permission.allow.toString(), + deny: permission.deny.toString(), + id, + type: 0, + }), + } + ); + } + } + } + Channel.setupcontextmenu(); + export { Channel }; diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index fe28d2e..72c20fe 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -1,107 +1,107 @@ class Contextmenu { - static currentmenu: HTMLElement | ""; - name: string; - buttons: [ - string, - (this: x, arg: y, e: MouseEvent) => void, - string | null, - (this: x, arg: y) => boolean, - (this: x, arg: y) => boolean, - string - ][]; - div!: HTMLDivElement; - static setup() { - Contextmenu.currentmenu = ""; - document.addEventListener("click", (event) => { - if (Contextmenu.currentmenu === "") { - return; - } - if (!Contextmenu.currentmenu.contains(event.target as Node)) { - Contextmenu.currentmenu.remove(); - Contextmenu.currentmenu = ""; - } - }); - } - constructor(name: string) { - this.name = name; - this.buttons = []; - } - addbutton( - text: string, - onclick: (this: x, arg: y, e: MouseEvent) => void, - img: null | string = null, - shown: (this: x, arg: y) => boolean = (_) => true, - enabled: (this: x, arg: y) => boolean = (_) => true - ) { - this.buttons.push([text, onclick, img, shown, enabled, "button"]); - return {}; - } - addsubmenu( - text: string, - onclick: (this: x, arg: y, e: MouseEvent) => void, - img = null, - shown: (this: x, arg: y) => boolean = (_) => true, - enabled: (this: x, arg: y) => boolean = (_) => true - ) { - this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); - return {}; - } - private makemenu(x: number, y: number, addinfo: x, other: y) { - const div = document.createElement("div"); - div.classList.add("contextmenu", "flexttb"); + static currentmenu: HTMLElement | ""; + name: string; + buttons: [ + string, + (this: x, arg: y, e: MouseEvent) => void, + string | null, + (this: x, arg: y) => boolean, + (this: x, arg: y) => boolean, + string + ][]; + div!: HTMLDivElement; + static setup() { + Contextmenu.currentmenu = ""; + document.addEventListener("click", (event) => { + if (Contextmenu.currentmenu === "") { + return; + } + if (!Contextmenu.currentmenu.contains(event.target as Node)) { + Contextmenu.currentmenu.remove(); + Contextmenu.currentmenu = ""; + } + }); + } + constructor(name: string) { + this.name = name; + this.buttons = []; + } + addbutton( + text: string, + onclick: (this: x, arg: y, e: MouseEvent) => void, + img: null | string = null, + shown: (this: x, arg: y) => boolean = (_) => true, + enabled: (this: x, arg: y) => boolean = (_) => true + ) { + this.buttons.push([text, onclick, img, shown, enabled, "button"]); + return {}; + } + addsubmenu( + text: string, + onclick: (this: x, arg: y, e: MouseEvent) => void, + img = null, + shown: (this: x, arg: y) => boolean = (_) => true, + enabled: (this: x, arg: y) => boolean = (_) => true + ) { + this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); + return {}; + } + private makemenu(x: number, y: number, addinfo: x, other: y) { + const div = document.createElement("div"); + div.classList.add("contextmenu", "flexttb"); - let visibleButtons = 0; - for (const thing of this.buttons) { - if (!thing[3].bind(addinfo).call(addinfo, other)) continue; - visibleButtons++; + let visibleButtons = 0; + for (const thing of this.buttons) { + if (!thing[3].bind(addinfo).call(addinfo, other)) continue; + visibleButtons++; - const intext = document.createElement("button"); - intext.disabled = !thing[4].bind(addinfo).call(addinfo, other); - intext.classList.add("contextbutton"); - intext.textContent = thing[0]; - console.log(thing); - if (thing[5] === "button" || thing[5] === "submenu") { - intext.onclick = thing[1].bind(addinfo, other); - } + const intext = document.createElement("button"); + intext.disabled = !thing[4].bind(addinfo).call(addinfo, other); + intext.classList.add("contextbutton"); + intext.textContent = thing[0]; + console.log(thing); + if (thing[5] === "button" || thing[5] === "submenu") { + intext.onclick = thing[1].bind(addinfo, other); + } - div.appendChild(intext); - } - if (visibleButtons == 0) return; + div.appendChild(intext); + } + if (visibleButtons == 0) return; - if (Contextmenu.currentmenu != "") { - Contextmenu.currentmenu.remove(); - } - div.style.top = y + "px"; - div.style.left = x + "px"; - document.body.appendChild(div); - Contextmenu.keepOnScreen(div); - console.log(div); - Contextmenu.currentmenu = div; - return this.div; - } - bindContextmenu(obj: HTMLElement, addinfo: x, other: y) { - const func = (event: MouseEvent) => { - event.preventDefault(); - event.stopImmediatePropagation(); - this.makemenu(event.clientX, event.clientY, addinfo, other); - }; - obj.addEventListener("contextmenu", func); - return func; - } - static keepOnScreen(obj: HTMLElement) { - const html = document.documentElement.getBoundingClientRect(); - const docheight = html.height; - const docwidth = html.width; - const box = obj.getBoundingClientRect(); - console.log(box, docheight, docwidth); - if (box.right > docwidth) { - console.log("test"); - obj.style.left = docwidth - box.width + "px"; - } - if (box.bottom > docheight) { - obj.style.top = docheight - box.height + "px"; - } - } -} -Contextmenu.setup(); -export { Contextmenu }; + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } + div.style.top = y + "px"; + div.style.left = x + "px"; + document.body.appendChild(div); + Contextmenu.keepOnScreen(div); + console.log(div); + Contextmenu.currentmenu = div; + return this.div; + } + bindContextmenu(obj: HTMLElement, addinfo: x, other: y) { + const func = (event: MouseEvent) => { + event.preventDefault(); + event.stopImmediatePropagation(); + this.makemenu(event.clientX, event.clientY, addinfo, other); + }; + obj.addEventListener("contextmenu", func); + return func; + } + static keepOnScreen(obj: HTMLElement) { + const html = document.documentElement.getBoundingClientRect(); + const docheight = html.height; + const docwidth = html.width; + const box = obj.getBoundingClientRect(); + console.log(box, docheight, docwidth); + if (box.right > docwidth) { + console.log("test"); + obj.style.left = docwidth - box.width + "px"; + } + if (box.bottom > docheight) { + obj.style.top = docheight - box.height + "px"; + } + } + } + Contextmenu.setup(); + export { Contextmenu }; diff --git a/src/webpage/dialog.ts b/src/webpage/dialog.ts index ca15cf1..9dd69c1 100644 --- a/src/webpage/dialog.ts +++ b/src/webpage/dialog.ts @@ -1,273 +1,273 @@ type dialogjson = - | ["hdiv", ...dialogjson[]] - | ["vdiv", ...dialogjson[]] - | ["img", string, [number, number] | undefined | ["fit"]] - | ["checkbox", string, boolean, (this: HTMLInputElement, e: Event) => unknown] - | ["button", string, string, (this: HTMLButtonElement, e: Event) => unknown] - | ["mdbox", string, string, (this: HTMLTextAreaElement, e: Event) => unknown] - | ["textbox", string, string, (this: HTMLInputElement, e: Event) => unknown] - | ["fileupload", string, (this: HTMLInputElement, e: Event) => unknown] - | ["text", string] - | ["title", string] - | ["radio", string, string[], (this: unknown, e: string) => unknown, number] - | ["html", HTMLElement] - | [ - "select", - string, - string[], - (this: HTMLSelectElement, e: Event) => unknown, - number - ] - | ["tabs", [string, dialogjson][]]; +| ["hdiv", ...dialogjson[]] +| ["vdiv", ...dialogjson[]] +| ["img", string, [number, number] | undefined | ["fit"]] +| ["checkbox", string, boolean, (this: HTMLInputElement, e: Event) => unknown] +| ["button", string, string, (this: HTMLButtonElement, e: Event) => unknown] +| ["mdbox", string, string, (this: HTMLTextAreaElement, e: Event) => unknown] +| ["textbox", string, string, (this: HTMLInputElement, e: Event) => unknown] +| ["fileupload", string, (this: HTMLInputElement, e: Event) => unknown] +| ["text", string] +| ["title", string] +| ["radio", string, string[], (this: unknown, e: string) => unknown, number] +| ["html", HTMLElement] +| [ +"select", +string, +string[], +(this: HTMLSelectElement, e: Event) => unknown, +number +] +| ["tabs", [string, dialogjson][]]; class Dialog { - layout: dialogjson; - onclose: Function; - onopen: Function; - html: HTMLDivElement; - background!: HTMLDivElement; - constructor( - layout: dialogjson, - onclose = (_: any) => {}, - onopen = (_: any) => {} - ) { - this.layout = layout; - this.onclose = onclose; - this.onopen = onopen; - const div = document.createElement("div"); - div.appendChild(this.tohtml(layout)); - this.html = div; - this.html.classList.add("centeritem"); - if (!(layout[0] === "img")) { - this.html.classList.add("nonimagecenter"); - } - } - tohtml(array: dialogjson): HTMLElement { - switch (array[0]) { - case "img": - const img = document.createElement("img"); - img.src = array[1]; - if (array[2] != undefined) { - if (array[2].length === 2) { - img.width = array[2][0]; - img.height = array[2][1]; - } else if (array[2][0] === "fit") { - img.classList.add("imgfit"); - } - } - return img; - case "hdiv": - const hdiv = document.createElement("div"); - hdiv.classList.add("flexltr"); +layout: dialogjson; +onclose: Function; +onopen: Function; +html: HTMLDivElement; +background!: HTMLDivElement; +constructor( +layout: dialogjson, +onclose = (_: any) => {}, +onopen = (_: any) => {} +) { +this.layout = layout; +this.onclose = onclose; +this.onopen = onopen; +const div = document.createElement("div"); +div.appendChild(this.tohtml(layout)); +this.html = div; +this.html.classList.add("centeritem"); +if (!(layout[0] === "img")) { +this.html.classList.add("nonimagecenter"); +} +} +tohtml(array: dialogjson): HTMLElement { +switch (array[0]) { +case "img": +const img = document.createElement("img"); +img.src = array[1]; +if (array[2] != undefined) { +if (array[2].length === 2) { +img.width = array[2][0]; +img.height = array[2][1]; +} else if (array[2][0] === "fit") { +img.classList.add("imgfit"); +} +} +return img; +case "hdiv": +const hdiv = document.createElement("div"); +hdiv.classList.add("flexltr"); - for (const thing of array) { - if (thing === "hdiv") { - continue; - } - hdiv.appendChild(this.tohtml(thing)); - } - return hdiv; - case "vdiv": - const vdiv = document.createElement("div"); - vdiv.classList.add("flexttb"); - for (const thing of array) { - if (thing === "vdiv") { - continue; - } - vdiv.appendChild(this.tohtml(thing)); - } - return vdiv; - case "checkbox": { - const div = document.createElement("div"); - const checkbox = document.createElement("input"); - div.appendChild(checkbox); - const label = document.createElement("span"); - checkbox.checked = array[2]; - label.textContent = array[1]; - div.appendChild(label); - checkbox.addEventListener("change", array[3]); - checkbox.type = "checkbox"; - return div; - } - case "button": { - const div = document.createElement("div"); - const input = document.createElement("button"); +for (const thing of array) { +if (thing === "hdiv") { +continue; +} +hdiv.appendChild(this.tohtml(thing)); +} +return hdiv; +case "vdiv": +const vdiv = document.createElement("div"); +vdiv.classList.add("flexttb"); +for (const thing of array) { +if (thing === "vdiv") { +continue; +} +vdiv.appendChild(this.tohtml(thing)); +} +return vdiv; +case "checkbox": { +const div = document.createElement("div"); +const checkbox = document.createElement("input"); +div.appendChild(checkbox); +const label = document.createElement("span"); +checkbox.checked = array[2]; +label.textContent = array[1]; +div.appendChild(label); +checkbox.addEventListener("change", array[3]); +checkbox.type = "checkbox"; +return div; +} +case "button": { +const div = document.createElement("div"); +const input = document.createElement("button"); - const label = document.createElement("span"); - input.textContent = array[2]; - label.textContent = array[1]; - div.appendChild(label); - div.appendChild(input); - input.addEventListener("click", array[3]); - return div; - } - case "mdbox": { - const div = document.createElement("div"); - const input = document.createElement("textarea"); - input.value = array[2]; - const label = document.createElement("span"); - label.textContent = array[1]; - input.addEventListener("input", array[3]); - div.appendChild(label); - div.appendChild(document.createElement("br")); - div.appendChild(input); - return div; - } - case "textbox": { - const div = document.createElement("div"); - const input = document.createElement("input"); - input.value = array[2]; - input.type = "text"; - const label = document.createElement("span"); - label.textContent = array[1]; - console.log(array[3]); - input.addEventListener("input", array[3]); - div.appendChild(label); - div.appendChild(input); - return div; - } - case "fileupload": { - const div = document.createElement("div"); - const input = document.createElement("input"); - input.type = "file"; - const label = document.createElement("span"); - label.textContent = array[1]; - div.appendChild(label); - div.appendChild(input); - input.addEventListener("change", array[2]); - console.log(array); - return div; - } - case "text": { - const span = document.createElement("span"); - span.textContent = array[1]; - return span; - } - case "title": { - const span = document.createElement("span"); - span.classList.add("title"); - span.textContent = array[1]; - return span; - } - case "radio": { - const div = document.createElement("div"); - const fieldset = document.createElement("fieldset"); - fieldset.addEventListener("change", () => { - let i = -1; - for (const thing of Array.from(fieldset.children)) { - i++; - if (i === 0) { - continue; - } - const checkbox = thing.children[0].children[0] as HTMLInputElement; - if (checkbox.checked) { - array[3](checkbox.value); - } - } - }); - const legend = document.createElement("legend"); - legend.textContent = array[1]; - fieldset.appendChild(legend); - let i = 0; - for (const thing of array[2]) { - const div = document.createElement("div"); - const input = document.createElement("input"); - input.classList.add("radio"); - input.type = "radio"; - input.name = array[1]; - input.value = thing; - if (i === array[4]) { - input.checked = true; - } - const label = document.createElement("label"); +const label = document.createElement("span"); +input.textContent = array[2]; +label.textContent = array[1]; +div.appendChild(label); +div.appendChild(input); +input.addEventListener("click", array[3]); +return div; +} +case "mdbox": { +const div = document.createElement("div"); +const input = document.createElement("textarea"); +input.value = array[2]; +const label = document.createElement("span"); +label.textContent = array[1]; +input.addEventListener("input", array[3]); +div.appendChild(label); +div.appendChild(document.createElement("br")); +div.appendChild(input); +return div; +} +case "textbox": { +const div = document.createElement("div"); +const input = document.createElement("input"); +input.value = array[2]; +input.type = "text"; +const label = document.createElement("span"); +label.textContent = array[1]; +console.log(array[3]); +input.addEventListener("input", array[3]); +div.appendChild(label); +div.appendChild(input); +return div; +} +case "fileupload": { +const div = document.createElement("div"); +const input = document.createElement("input"); +input.type = "file"; +const label = document.createElement("span"); +label.textContent = array[1]; +div.appendChild(label); +div.appendChild(input); +input.addEventListener("change", array[2]); +console.log(array); +return div; +} +case "text": { +const span = document.createElement("span"); +span.textContent = array[1]; +return span; +} +case "title": { +const span = document.createElement("span"); +span.classList.add("title"); +span.textContent = array[1]; +return span; +} +case "radio": { +const div = document.createElement("div"); +const fieldset = document.createElement("fieldset"); +fieldset.addEventListener("change", () => { +let i = -1; +for (const thing of Array.from(fieldset.children)) { +i++; +if (i === 0) { +continue; +} +const checkbox = thing.children[0].children[0] as HTMLInputElement; +if (checkbox.checked) { +array[3](checkbox.value); +} +} +}); +const legend = document.createElement("legend"); +legend.textContent = array[1]; +fieldset.appendChild(legend); +let i = 0; +for (const thing of array[2]) { +const div = document.createElement("div"); +const input = document.createElement("input"); +input.classList.add("radio"); +input.type = "radio"; +input.name = array[1]; +input.value = thing; +if (i === array[4]) { +input.checked = true; +} +const label = document.createElement("label"); - label.appendChild(input); - const span = document.createElement("span"); - span.textContent = thing; - label.appendChild(span); - div.appendChild(label); - fieldset.appendChild(div); - i++; - } - div.appendChild(fieldset); - return div; - } - case "html": - return array[1]; +label.appendChild(input); +const span = document.createElement("span"); +span.textContent = thing; +label.appendChild(span); +div.appendChild(label); +fieldset.appendChild(div); +i++; +} +div.appendChild(fieldset); +return div; +} +case "html": +return array[1]; - case "select": { - const div = document.createElement("div"); - const label = document.createElement("label"); - const select = document.createElement("select"); +case "select": { +const div = document.createElement("div"); +const label = document.createElement("label"); +const select = document.createElement("select"); - label.textContent = array[1]; - div.append(label); - div.appendChild(select); - for (const thing of array[2]) { - const option = document.createElement("option"); - option.textContent = thing; - select.appendChild(option); - } - select.selectedIndex = array[4]; - select.addEventListener("change", array[3]); - return div; - } - case "tabs": { - const table = document.createElement("div"); - table.classList.add("flexttb"); - const tabs = document.createElement("div"); - tabs.classList.add("flexltr"); - tabs.classList.add("tabbed-head"); - table.appendChild(tabs); - const content = document.createElement("div"); - content.classList.add("tabbed-content"); - table.appendChild(content); +label.textContent = array[1]; +div.append(label); +div.appendChild(select); +for (const thing of array[2]) { +const option = document.createElement("option"); +option.textContent = thing; +select.appendChild(option); +} +select.selectedIndex = array[4]; +select.addEventListener("change", array[3]); +return div; +} +case "tabs": { +const table = document.createElement("div"); +table.classList.add("flexttb"); +const tabs = document.createElement("div"); +tabs.classList.add("flexltr"); +tabs.classList.add("tabbed-head"); +table.appendChild(tabs); +const content = document.createElement("div"); +content.classList.add("tabbed-content"); +table.appendChild(content); - let shown: HTMLElement | undefined; - for (const thing of array[1]) { - const button = document.createElement("button"); - button.textContent = thing[0]; - tabs.appendChild(button); +let shown: HTMLElement | undefined; +for (const thing of array[1]) { +const button = document.createElement("button"); +button.textContent = thing[0]; +tabs.appendChild(button); - const html = this.tohtml(thing[1]); - content.append(html); - if (!shown) { - shown = html; - } else { - html.style.display = "none"; - } - button.addEventListener("click", (_) => { - if (shown) { - shown.style.display = "none"; - } - html.style.display = ""; - shown = html; - }); - } - return table; - } - default: - console.error( - "can't find element:" + array[0], - " full element:", - array - ); - return document.createElement("span"); - } - } - show() { - this.onopen(); - console.log("fullscreen"); - this.background = document.createElement("div"); - this.background.classList.add("background"); - document.body.appendChild(this.background); - document.body.appendChild(this.html); - this.background.onclick = (_) => { - this.hide(); - }; - } - hide() { - document.body.removeChild(this.background); - document.body.removeChild(this.html); - } +const html = this.tohtml(thing[1]); +content.append(html); +if (!shown) { +shown = html; +} else { +html.style.display = "none"; +} +button.addEventListener("click", (_) => { +if (shown) { +shown.style.display = "none"; +} +html.style.display = ""; +shown = html; +}); +} +return table; +} +default: +console.error( +"can't find element:" + array[0], +" full element:", +array +); +return document.createElement("span"); +} +} +show() { +this.onopen(); +console.log("fullscreen"); +this.background = document.createElement("div"); +this.background.classList.add("background"); +document.body.appendChild(this.background); +document.body.appendChild(this.html); +this.background.onclick = (_) => { +this.hide(); +}; +} +hide() { +document.body.removeChild(this.background); +document.body.removeChild(this.html); +} } export { Dialog }; diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 131143e..3ac7313 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -4,75 +4,75 @@ import { Message } from "./message.js"; import { Localuser } from "./localuser.js"; import { User } from "./user.js"; import { - channeljson, - dirrectjson, - memberjson, - messagejson, +channeljson, +dirrectjson, +memberjson, +messagejson, } from "./jsontypes.js"; import { Permissions } from "./permissions.js"; import { SnowFlake } from "./snowflake.js"; import { Contextmenu } from "./contextmenu.js"; class Direct extends Guild { - declare channelids: { [key: string]: Group }; - getUnixTime(): number { - throw new Error("Do not call this for Direct, it does not make sense"); - } - constructor(json: dirrectjson[], owner: Localuser) { - super(-1, owner, null); - this.message_notifications = 0; - this.owner = owner; - if (!this.localuser) { - console.error("Owner was not included, please fix"); - } - this.headers = this.localuser.headers; - this.channels = []; - this.channelids = {}; - // @ts-ignore - this.properties = {}; - this.roles = []; - this.roleids = new Map(); - this.prevchannel = undefined; - this.properties.name = "Direct Messages"; - for (const thing of json) { - const temp = new Group(thing, this); - this.channels.push(temp); - this.channelids[temp.id] = temp; - } - this.headchannels = this.channels; - } - createChannelpac(json: any) { - const thischannel = new Group(json, this); - this.channelids[thischannel.id] = thischannel; - this.channels.push(thischannel); - this.sortchannels(); - this.printServers(); - return thischannel; - } - delChannel(json: channeljson) { - const channel = this.channelids[json.id]; - super.delChannel(json); - if (channel) { - channel.del(); - } - } - giveMember(_member: memberjson) { - console.error("not a real guild, can't give member object"); - } - getRole(/* ID: string */) { - return null; - } - hasRole(/* r: string */) { - return false; - } - isAdmin() { - return false; - } - unreaddms() { - for (const thing of this.channels) { - (thing as Group).unreads(); - } - } +declare channelids: { [key: string]: Group }; +getUnixTime(): number { +throw new Error("Do not call this for Direct, it does not make sense"); +} +constructor(json: dirrectjson[], owner: Localuser) { +super(-1, owner, null); +this.message_notifications = 0; +this.owner = owner; +if (!this.localuser) { +console.error("Owner was not included, please fix"); +} +this.headers = this.localuser.headers; +this.channels = []; +this.channelids = {}; +// @ts-ignore +this.properties = {}; +this.roles = []; +this.roleids = new Map(); +this.prevchannel = undefined; +this.properties.name = "Direct Messages"; +for (const thing of json) { +const temp = new Group(thing, this); +this.channels.push(temp); +this.channelids[temp.id] = temp; +} +this.headchannels = this.channels; +} +createChannelpac(json: any) { +const thischannel = new Group(json, this); +this.channelids[thischannel.id] = thischannel; +this.channels.push(thischannel); +this.sortchannels(); +this.printServers(); +return thischannel; +} +delChannel(json: channeljson) { +const channel = this.channelids[json.id]; +super.delChannel(json); +if (channel) { +channel.del(); +} +} +giveMember(_member: memberjson) { +console.error("not a real guild, can't give member object"); +} +getRole(/* ID: string */) { +return null; +} +hasRole(/* r: string */) { +return false; +} +isAdmin() { +return false; +} +unreaddms() { +for (const thing of this.channels) { +(thing as Group).unreads(); +} +} } const dmPermissions = new Permissions("0"); @@ -100,207 +100,207 @@ dmPermissions.setPermission("USE_VAD", 1); // @ts-ignore class Group extends Channel { - user: User; - static contextmenu = new Contextmenu("channel menu"); - static setupcontextmenu() { - this.contextmenu.addbutton("Copy DM id", function (this: Group) { - navigator.clipboard.writeText(this.id); - }); +user: User; +static contextmenu = new Contextmenu("channel menu"); + static setupcontextmenu() { + this.contextmenu.addbutton("Copy DM id", function (this: Group) { + navigator.clipboard.writeText(this.id); + }); - this.contextmenu.addbutton("Mark as read", function (this: Group) { - this.readbottom(); - }); + this.contextmenu.addbutton("Mark as read", function (this: Group) { + this.readbottom(); + }); - this.contextmenu.addbutton("Close DM", function (this: Group) { - this.deleteChannel(); - }); + this.contextmenu.addbutton("Close DM", function (this: Group) { + this.deleteChannel(); + }); - this.contextmenu.addbutton("Copy user ID", function () { - navigator.clipboard.writeText(this.user.id); - }); - } - constructor(json: dirrectjson, owner: Direct) { - super(-1, owner, json.id); - this.owner = owner; - this.headers = this.guild.headers; - this.name = json.recipients[0]?.username; - if (json.recipients[0]) { - this.user = new User(json.recipients[0], this.localuser); - } else { - this.user = this.localuser.user; - } - this.name ??= this.localuser.user.username; - this.parent_id!; - this.parent!; - this.children = []; - this.guild_id = "@me"; - this.permission_overwrites = new Map(); - this.lastmessageid = json.last_message_id; - this.mentions = 0; - this.setUpInfiniteScroller(); - this.updatePosition(); - } - updatePosition() { - if (this.lastmessageid) { - this.position = SnowFlake.stringToUnixTime(this.lastmessageid); - } else { - this.position = 0; - } - this.position = -Math.max(this.position, this.getUnixTime()); - } - createguildHTML() { - const div = document.createElement("div"); - Group.contextmenu.bindContextmenu(div, this, undefined); - this.html = new WeakRef(div); - div.classList.add("channeleffects"); - const myhtml = document.createElement("span"); - myhtml.textContent = this.name; - div.appendChild(this.user.buildpfp()); - div.appendChild(myhtml); - (div as any)["myinfo"] = this; - div.onclick = (_) => { - this.getHTML(); - }; + this.contextmenu.addbutton("Copy user ID", function () { + navigator.clipboard.writeText(this.user.id); + }); + } + constructor(json: dirrectjson, owner: Direct) { + super(-1, owner, json.id); + this.owner = owner; + this.headers = this.guild.headers; + this.name = json.recipients[0]?.username; + if (json.recipients[0]) { + this.user = new User(json.recipients[0], this.localuser); + } else { + this.user = this.localuser.user; + } + this.name ??= this.localuser.user.username; + this.parent_id!; + this.parent!; + this.children = []; + this.guild_id = "@me"; + this.permission_overwrites = new Map(); + this.lastmessageid = json.last_message_id; + this.mentions = 0; + this.setUpInfiniteScroller(); + this.updatePosition(); + } + updatePosition() { + if (this.lastmessageid) { + this.position = SnowFlake.stringToUnixTime(this.lastmessageid); + } else { + this.position = 0; + } + this.position = -Math.max(this.position, this.getUnixTime()); + } + createguildHTML() { + const div = document.createElement("div"); + Group.contextmenu.bindContextmenu(div, this, undefined); + this.html = new WeakRef(div); + div.classList.add("channeleffects"); + const myhtml = document.createElement("span"); + myhtml.textContent = this.name; + div.appendChild(this.user.buildpfp()); + div.appendChild(myhtml); + (div as any)["myinfo"] = this; + div.onclick = (_) => { + this.getHTML(); + }; - return div; - } - async getHTML() { - const id = ++Channel.genid; - if (this.localuser.channelfocus) { - this.localuser.channelfocus.infinite.delete(); - } - if (this.guild !== this.localuser.lookingguild) { - this.guild.loadGuild(); - } - this.guild.prevchannel = this; - this.localuser.channelfocus = this; - const prom = this.infinite.delete(); - history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); - this.localuser.pageTitle("@" + this.name); - (document.getElementById("channelTopic") as HTMLElement).setAttribute( - "hidden", - "" - ); + return div; + } + async getHTML() { + const id = ++Channel.genid; + if (this.localuser.channelfocus) { + this.localuser.channelfocus.infinite.delete(); + } + if (this.guild !== this.localuser.lookingguild) { + this.guild.loadGuild(); + } + this.guild.prevchannel = this; + this.localuser.channelfocus = this; + const prom = this.infinite.delete(); + history.pushState(null, "", "/channels/" + this.guild_id + "/" + this.id); + this.localuser.pageTitle("@" + this.name); + (document.getElementById("channelTopic") as HTMLElement).setAttribute( + "hidden", + "" + ); - const loading = document.getElementById("loadingdiv") as HTMLDivElement; - Channel.regenLoadingMessages(); - loading.classList.add("loading"); - this.rendertyping(); - await this.putmessages(); - await prom; - if (id !== Channel.genid) { - return; - } - this.buildmessages(); - (document.getElementById("typebox") as HTMLDivElement).contentEditable = - "" + true; - } - messageCreate(messagep: { d: messagejson }) { - const messagez = new Message(messagep.d, this); - if (this.lastmessageid) { - this.idToNext.set(this.lastmessageid, messagez.id); - this.idToPrev.set(messagez.id, this.lastmessageid); - } - this.lastmessageid = messagez.id; - if (messagez.author === this.localuser.user) { - this.lastreadmessageid = messagez.id; - if (this.myhtml) { - this.myhtml.classList.remove("cunread"); - } - } else { - if (this.myhtml) { - this.myhtml.classList.add("cunread"); - } - } - this.unreads(); - this.updatePosition(); - this.infinite.addedBottom(); - this.guild.sortchannels(); - if (this.myhtml) { - const parrent = this.myhtml.parentElement as HTMLElement; - parrent.prepend(this.myhtml); - } - if (this === this.localuser.channelfocus) { - if (!this.infinitefocus) { - this.tryfocusinfinate(); - } - this.infinite.addedBottom(); - } - this.unreads(); - if (messagez.author === this.localuser.user) { - return; - } - if ( - this.localuser.lookingguild?.prevchannel === this && - document.hasFocus() - ) { - return; - } - if (this.notification === "all") { - this.notify(messagez); - } else if ( - this.notification === "mentions" && - messagez.mentionsuser(this.localuser.user) - ) { - this.notify(messagez); - } - } - notititle(message: Message) { - return message.author.username; - } - readbottom() { - super.readbottom(); - this.unreads(); - } - all: WeakRef = new WeakRef(document.createElement("div")); - noti: WeakRef = new WeakRef(document.createElement("div")); - del() { - const all = this.all.deref(); - if (all) { - all.remove(); - } - if (this.myhtml) { - this.myhtml.remove(); - } - } - unreads() { - const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime - const current = this.all.deref(); - if (this.hasunreads) { - { - const noti = this.noti.deref(); - if (noti) { - noti.textContent = this.mentions + ""; - return; - } - } - const div = document.createElement("div"); - div.classList.add("servernoti"); - const noti = document.createElement("div"); - noti.classList.add("unread", "notiunread", "pinged"); - noti.textContent = "" + this.mentions; - this.noti = new WeakRef(noti); - div.append(noti); - const buildpfp = this.user.buildpfp(); - this.all = new WeakRef(div); - buildpfp.classList.add("mentioned"); - div.append(buildpfp); - sentdms.append(div); - div.onclick = (_) => { - this.guild.loadGuild(); - this.getHTML(); - }; - } else if (current) { - current.remove(); - } else { - } - } - isAdmin(): boolean { - return false; - } - hasPermission(name: string): boolean { - return dmPermissions.hasPermission(name); - } -} -export { Direct, Group }; -Group.setupcontextmenu(); + const loading = document.getElementById("loadingdiv") as HTMLDivElement; + Channel.regenLoadingMessages(); + loading.classList.add("loading"); + this.rendertyping(); + await this.putmessages(); + await prom; + if (id !== Channel.genid) { + return; + } + this.buildmessages(); + (document.getElementById("typebox") as HTMLDivElement).contentEditable = + "" + true; + } + messageCreate(messagep: { d: messagejson }) { + const messagez = new Message(messagep.d, this); + if (this.lastmessageid) { + this.idToNext.set(this.lastmessageid, messagez.id); + this.idToPrev.set(messagez.id, this.lastmessageid); + } + this.lastmessageid = messagez.id; + if (messagez.author === this.localuser.user) { + this.lastreadmessageid = messagez.id; + if (this.myhtml) { + this.myhtml.classList.remove("cunread"); + } + } else { + if (this.myhtml) { + this.myhtml.classList.add("cunread"); + } + } + this.unreads(); + this.updatePosition(); + this.infinite.addedBottom(); + this.guild.sortchannels(); + if (this.myhtml) { + const parrent = this.myhtml.parentElement as HTMLElement; + parrent.prepend(this.myhtml); + } + if (this === this.localuser.channelfocus) { + if (!this.infinitefocus) { + this.tryfocusinfinate(); + } + this.infinite.addedBottom(); + } + this.unreads(); + if (messagez.author === this.localuser.user) { + return; + } + if ( + this.localuser.lookingguild?.prevchannel === this && + document.hasFocus() + ) { + return; + } + if (this.notification === "all") { + this.notify(messagez); + } else if ( + this.notification === "mentions" && + messagez.mentionsuser(this.localuser.user) + ) { + this.notify(messagez); + } + } + notititle(message: Message) { + return message.author.username; + } + readbottom() { + super.readbottom(); + this.unreads(); + } + all: WeakRef = new WeakRef(document.createElement("div")); + noti: WeakRef = new WeakRef(document.createElement("div")); + del() { + const all = this.all.deref(); + if (all) { + all.remove(); + } + if (this.myhtml) { + this.myhtml.remove(); + } + } + unreads() { + const sentdms = document.getElementById("sentdms") as HTMLDivElement; //Need to change sometime + const current = this.all.deref(); + if (this.hasunreads) { + { + const noti = this.noti.deref(); + if (noti) { + noti.textContent = this.mentions + ""; + return; + } + } + const div = document.createElement("div"); + div.classList.add("servernoti"); + const noti = document.createElement("div"); + noti.classList.add("unread", "notiunread", "pinged"); + noti.textContent = "" + this.mentions; + this.noti = new WeakRef(noti); + div.append(noti); + const buildpfp = this.user.buildpfp(); + this.all = new WeakRef(div); + buildpfp.classList.add("mentioned"); + div.append(buildpfp); + sentdms.append(div); + div.onclick = (_) => { + this.guild.loadGuild(); + this.getHTML(); + }; + } else if (current) { + current.remove(); + } else { + } + } + isAdmin(): boolean { + return false; + } + hasPermission(name: string): boolean { + return dmPermissions.hasPermission(name); + } + } + export { Direct, Group }; + Group.setupcontextmenu(); diff --git a/src/webpage/embed.ts b/src/webpage/embed.ts index 772a835..fcf8feb 100644 --- a/src/webpage/embed.ts +++ b/src/webpage/embed.ts @@ -6,406 +6,406 @@ import { getapiurls, getInstances } from "./login.js"; import { Guild } from "./guild.js"; class Embed { - type: string; - owner: Message; - json: embedjson; - constructor(json: embedjson, owner: Message) { - this.type = this.getType(json); - this.owner = owner; - this.json = json; - } - getType(json: embedjson) { - const instances = getInstances(); - if ( - instances && - json.type === "link" && - json.url && - URL.canParse(json.url) - ) { - const Url = new URL(json.url); - for (const instance of instances) { - if (instance.url && URL.canParse(instance.url)) { - const IUrl = new URL(instance.url); - const params = new URLSearchParams(Url.search); - let host: string; - if (params.has("instance")) { - const url = params.get("instance") as string; - if (URL.canParse(url)) { - host = new URL(url).host; - } else { - host = Url.host; - } - } else { - host = Url.host; - } - if (IUrl.host === host) { - const code = - Url.pathname.split("/")[Url.pathname.split("/").length - 1]; - json.invite = { - url: instance.url, - code, - }; - return "invite"; - } - } - } - } - return json.type || "rich"; - } - generateHTML() { - switch (this.type) { - case "rich": - return this.generateRich(); - case "image": - return this.generateImage(); - case "invite": - return this.generateInvite(); - case "link": - return this.generateLink(); - case "video": - case "article": - return this.generateArticle(); - default: - console.warn( - `unsupported embed type ${this.type}, please add support dev :3`, - this.json - ); - return document.createElement("div"); //prevent errors by giving blank div - } - } - get message() { - return this.owner; - } - get channel() { - return this.message.channel; - } - get guild() { - return this.channel.guild; - } - get localuser() { - return this.guild.localuser; - } - generateRich() { - const div = document.createElement("div"); - if (this.json.color) { - div.style.backgroundColor = "#" + this.json.color.toString(16); - } - div.classList.add("embed-color"); +type: string; +owner: Message; +json: embedjson; +constructor(json: embedjson, owner: Message) { +this.type = this.getType(json); +this.owner = owner; +this.json = json; +} +getType(json: embedjson) { +const instances = getInstances(); +if ( +instances && +json.type === "link" && +json.url && +URL.canParse(json.url) +) { +const Url = new URL(json.url); +for (const instance of instances) { +if (instance.url && URL.canParse(instance.url)) { +const IUrl = new URL(instance.url); +const params = new URLSearchParams(Url.search); +let host: string; +if (params.has("instance")) { +const url = params.get("instance") as string; +if (URL.canParse(url)) { +host = new URL(url).host; +} else { +host = Url.host; +} +} else { +host = Url.host; +} +if (IUrl.host === host) { +const code = +Url.pathname.split("/")[Url.pathname.split("/").length - 1]; +json.invite = { +url: instance.url, +code, +}; +return "invite"; +} +} +} +} +return json.type || "rich"; +} +generateHTML() { +switch (this.type) { +case "rich": +return this.generateRich(); +case "image": +return this.generateImage(); +case "invite": +return this.generateInvite(); +case "link": +return this.generateLink(); +case "video": +case "article": +return this.generateArticle(); +default: +console.warn( +`unsupported embed type ${this.type}, please add support dev :3`, +this.json +); +return document.createElement("div"); //prevent errors by giving blank div +} +} +get message() { +return this.owner; +} +get channel() { +return this.message.channel; +} +get guild() { +return this.channel.guild; +} +get localuser() { +return this.guild.localuser; +} +generateRich() { +const div = document.createElement("div"); +if (this.json.color) { +div.style.backgroundColor = "#" + this.json.color.toString(16); +} +div.classList.add("embed-color"); - const embed = document.createElement("div"); - embed.classList.add("embed"); - div.append(embed); +const embed = document.createElement("div"); +embed.classList.add("embed"); +div.append(embed); - if (this.json.author) { - const authorline = document.createElement("div"); - if (this.json.author.icon_url) { - const img = document.createElement("img"); - img.classList.add("embedimg"); - img.src = this.json.author.icon_url; - authorline.append(img); - } - const a = document.createElement("a"); - a.textContent = this.json.author.name as string; - if (this.json.author.url) { - MarkDown.safeLink(a, this.json.author.url); - } - a.classList.add("username"); - authorline.append(a); - embed.append(authorline); - } - if (this.json.title) { - const title = document.createElement("a"); - title.append(new MarkDown(this.json.title, this.channel).makeHTML()); - if (this.json.url) { - MarkDown.safeLink(title, this.json.url); - } - title.classList.add("embedtitle"); - embed.append(title); - } - if (this.json.description) { - const p = document.createElement("p"); - p.append(new MarkDown(this.json.description, this.channel).makeHTML()); - embed.append(p); - } +if (this.json.author) { +const authorline = document.createElement("div"); +if (this.json.author.icon_url) { +const img = document.createElement("img"); +img.classList.add("embedimg"); +img.src = this.json.author.icon_url; +authorline.append(img); +} +const a = document.createElement("a"); +a.textContent = this.json.author.name as string; +if (this.json.author.url) { +MarkDown.safeLink(a, this.json.author.url); +} +a.classList.add("username"); +authorline.append(a); +embed.append(authorline); +} +if (this.json.title) { +const title = document.createElement("a"); +title.append(new MarkDown(this.json.title, this.channel).makeHTML()); +if (this.json.url) { +MarkDown.safeLink(title, this.json.url); +} +title.classList.add("embedtitle"); +embed.append(title); +} +if (this.json.description) { +const p = document.createElement("p"); +p.append(new MarkDown(this.json.description, this.channel).makeHTML()); +embed.append(p); +} - embed.append(document.createElement("br")); - if (this.json.fields) { - for (const thing of this.json.fields) { - const div = document.createElement("div"); - const b = document.createElement("b"); - b.textContent = thing.name; - div.append(b); - const p = document.createElement("p"); - p.append(new MarkDown(thing.value, this.channel).makeHTML()); - p.classList.add("embedp"); - div.append(p); +embed.append(document.createElement("br")); +if (this.json.fields) { +for (const thing of this.json.fields) { +const div = document.createElement("div"); +const b = document.createElement("b"); +b.textContent = thing.name; +div.append(b); +const p = document.createElement("p"); +p.append(new MarkDown(thing.value, this.channel).makeHTML()); +p.classList.add("embedp"); +div.append(p); - if (thing.inline) { - div.classList.add("inline"); - } - embed.append(div); - } - } - if (this.json.footer || this.json.timestamp) { - const footer = document.createElement("div"); - if (this.json?.footer?.icon_url) { - const img = document.createElement("img"); - img.src = this.json.footer.icon_url; - img.classList.add("embedicon"); - footer.append(img); - } - if (this.json?.footer?.text) { - const span = document.createElement("span"); - span.textContent = this.json.footer.text; - span.classList.add("spaceright"); - footer.append(span); - } - if (this.json?.footer && this.json?.timestamp) { - const span = document.createElement("span"); - span.textContent = "•"; - span.classList.add("spaceright"); - footer.append(span); - } - if (this.json?.timestamp) { - const span = document.createElement("span"); - span.textContent = new Date(this.json.timestamp).toLocaleString(); - footer.append(span); - } - embed.append(footer); - } - return div; - } - generateImage() { - const img = document.createElement("img"); - img.classList.add("messageimg"); - img.onclick = function () { - const full = new Dialog(["img", img.src, ["fit"]]); - full.show(); - }; - img.src = this.json.thumbnail.proxy_url; - if (this.json.thumbnail.width) { - let scale = 1; - const max = 96 * 3; - scale = Math.max(scale, this.json.thumbnail.width / max); - scale = Math.max(scale, this.json.thumbnail.height / max); - this.json.thumbnail.width /= scale; - this.json.thumbnail.height /= scale; - } - img.style.width = this.json.thumbnail.width + "px"; - img.style.height = this.json.thumbnail.height + "px"; - console.log(this.json, "Image fix"); - return img; - } - generateLink() { - const table = document.createElement("table"); - table.classList.add("embed", "linkembed"); - const trtop = document.createElement("tr"); - table.append(trtop); - if (this.json.url && this.json.title) { - const td = document.createElement("td"); - const a = document.createElement("a"); - MarkDown.safeLink(a, this.json.url); - a.textContent = this.json.title; - td.append(a); - trtop.append(td); - } - { - const td = document.createElement("td"); - const img = document.createElement("img"); - if (this.json.thumbnail) { - img.classList.add("embedimg"); - img.onclick = function () { - const full = new Dialog(["img", img.src, ["fit"]]); - full.show(); - }; - img.src = this.json.thumbnail.proxy_url; - td.append(img); - } - trtop.append(td); - } - const bottomtr = document.createElement("tr"); - const td = document.createElement("td"); - if (this.json.description) { - const span = document.createElement("span"); - span.textContent = this.json.description; - td.append(span); - } - bottomtr.append(td); - table.append(bottomtr); - return table; - } - invcache: [invitejson, { cdn: string; api: string }] | undefined; - generateInvite() { - if (this.invcache && (!this.json.invite || !this.localuser)) { - return this.generateLink(); - } - const div = document.createElement("div"); - div.classList.add("embed", "inviteEmbed", "flexttb"); - const json1 = this.json.invite; - (async () => { - let json: invitejson; - let info: { cdn: string; api: string }; - if (!this.invcache) { - if (!json1) { - div.append(this.generateLink()); - return; - } - const tempinfo = await getapiurls(json1.url); +if (thing.inline) { +div.classList.add("inline"); +} +embed.append(div); +} +} +if (this.json.footer || this.json.timestamp) { +const footer = document.createElement("div"); +if (this.json?.footer?.icon_url) { +const img = document.createElement("img"); +img.src = this.json.footer.icon_url; +img.classList.add("embedicon"); +footer.append(img); +} +if (this.json?.footer?.text) { +const span = document.createElement("span"); +span.textContent = this.json.footer.text; +span.classList.add("spaceright"); +footer.append(span); +} +if (this.json?.footer && this.json?.timestamp) { +const span = document.createElement("span"); +span.textContent = "•"; +span.classList.add("spaceright"); +footer.append(span); +} +if (this.json?.timestamp) { +const span = document.createElement("span"); +span.textContent = new Date(this.json.timestamp).toLocaleString(); +footer.append(span); +} +embed.append(footer); +} +return div; +} +generateImage() { +const img = document.createElement("img"); +img.classList.add("messageimg"); +img.onclick = function () { +const full = new Dialog(["img", img.src, ["fit"]]); +full.show(); +}; +img.src = this.json.thumbnail.proxy_url; +if (this.json.thumbnail.width) { +let scale = 1; +const max = 96 * 3; +scale = Math.max(scale, this.json.thumbnail.width / max); +scale = Math.max(scale, this.json.thumbnail.height / max); +this.json.thumbnail.width /= scale; +this.json.thumbnail.height /= scale; +} +img.style.width = this.json.thumbnail.width + "px"; +img.style.height = this.json.thumbnail.height + "px"; +console.log(this.json, "Image fix"); +return img; +} +generateLink() { +const table = document.createElement("table"); +table.classList.add("embed", "linkembed"); +const trtop = document.createElement("tr"); +table.append(trtop); +if (this.json.url && this.json.title) { +const td = document.createElement("td"); +const a = document.createElement("a"); +MarkDown.safeLink(a, this.json.url); +a.textContent = this.json.title; +td.append(a); +trtop.append(td); +} +{ +const td = document.createElement("td"); +const img = document.createElement("img"); +if (this.json.thumbnail) { +img.classList.add("embedimg"); +img.onclick = function () { +const full = new Dialog(["img", img.src, ["fit"]]); +full.show(); +}; +img.src = this.json.thumbnail.proxy_url; +td.append(img); +} +trtop.append(td); +} +const bottomtr = document.createElement("tr"); +const td = document.createElement("td"); +if (this.json.description) { +const span = document.createElement("span"); +span.textContent = this.json.description; +td.append(span); +} +bottomtr.append(td); +table.append(bottomtr); +return table; +} +invcache: [invitejson, { cdn: string; api: string }] | undefined; +generateInvite() { +if (this.invcache && (!this.json.invite || !this.localuser)) { +return this.generateLink(); +} +const div = document.createElement("div"); +div.classList.add("embed", "inviteEmbed", "flexttb"); +const json1 = this.json.invite; +(async () => { +let json: invitejson; +let info: { cdn: string; api: string }; +if (!this.invcache) { +if (!json1) { +div.append(this.generateLink()); +return; +} +const tempinfo = await getapiurls(json1.url); - if (!tempinfo) { - div.append(this.generateLink()); - return; - } - info = tempinfo; - const res = await fetch(info.api + "/invites/" + json1.code); - if (!res.ok) { - div.append(this.generateLink()); - } - json = (await res.json()) as invitejson; - this.invcache = [json, info]; - } else { - [json, info] = this.invcache; - } - if (!json) { - div.append(this.generateLink()); - return; - } - if (json.guild.banner) { - const banner = document.createElement("img"); - banner.src = - this.localuser.info.cdn + - "/icons/" + - json.guild.id + - "/" + - json.guild.banner + - ".png?size=256"; - banner.classList.add("banner"); - div.append(banner); - } - const guild: invitejson["guild"] & { info?: { cdn: string } } = - json.guild; - guild.info = info; - const icon = Guild.generateGuildIcon( - guild as invitejson["guild"] & { info: { cdn: string } } - ); - const iconrow = document.createElement("div"); - iconrow.classList.add("flexltr", "flexstart"); - iconrow.append(icon); - { - const guildinfo = document.createElement("div"); - guildinfo.classList.add("flexttb", "invguildinfo"); - const name = document.createElement("b"); - name.textContent = guild.name; - guildinfo.append(name); +if (!tempinfo) { +div.append(this.generateLink()); +return; +} +info = tempinfo; +const res = await fetch(info.api + "/invites/" + json1.code); +if (!res.ok) { +div.append(this.generateLink()); +} +json = (await res.json()) as invitejson; +this.invcache = [json, info]; +} else { +[json, info] = this.invcache; +} +if (!json) { +div.append(this.generateLink()); +return; +} +if (json.guild.banner) { +const banner = document.createElement("img"); +banner.src = +this.localuser.info.cdn + +"/icons/" + +json.guild.id + +"/" + +json.guild.banner + +".png?size=256"; +banner.classList.add("banner"); +div.append(banner); +} +const guild: invitejson["guild"] & { info?: { cdn: string } } = +json.guild; +guild.info = info; +const icon = Guild.generateGuildIcon( +guild as invitejson["guild"] & { info: { cdn: string } } +); +const iconrow = document.createElement("div"); +iconrow.classList.add("flexltr", "flexstart"); +iconrow.append(icon); +{ +const guildinfo = document.createElement("div"); +guildinfo.classList.add("flexttb", "invguildinfo"); +const name = document.createElement("b"); +name.textContent = guild.name; +guildinfo.append(name); - const members = document.createElement("span"); - members.innerText = - "#" + json.channel.name + " • Members: " + guild.member_count; - guildinfo.append(members); - members.classList.add("subtext"); - iconrow.append(guildinfo); - } +const members = document.createElement("span"); +members.innerText = +"#" + json.channel.name + " • Members: " + guild.member_count; +guildinfo.append(members); +members.classList.add("subtext"); +iconrow.append(guildinfo); +} - div.append(iconrow); - const h2 = document.createElement("h2"); - h2.textContent = `You've been invited by ${json.inviter.username}`; - div.append(h2); - const button = document.createElement("button"); - button.textContent = "Accept"; - if (this.localuser.info.api.startsWith(info.api)) { - if (this.localuser.guildids.has(guild.id)) { - button.textContent = "Already joined"; - button.disabled = true; - } - } - button.classList.add("acceptinvbutton"); - div.append(button); - button.onclick = (_) => { - if (this.localuser.info.api.startsWith(info.api)) { - fetch(this.localuser.info.api + "/invites/" + json.code, { - method: "POST", - headers: this.localuser.headers, - }) - .then((r) => r.json()) - .then((_) => { - if (_.message) { - alert(_.message); - } - }); - } else { - if (this.json.invite) { - const params = new URLSearchParams(""); - params.set("instance", this.json.invite.url); - const encoded = params.toString(); - const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`; - window.open(url, "_blank"); - } - } - }; - })(); - return div; - } - generateArticle() { - const colordiv = document.createElement("div"); - colordiv.style.backgroundColor = "#000000"; - colordiv.classList.add("embed-color"); +div.append(iconrow); +const h2 = document.createElement("h2"); +h2.textContent = `You've been invited by ${json.inviter.username}`; +div.append(h2); +const button = document.createElement("button"); +button.textContent = "Accept"; +if (this.localuser.info.api.startsWith(info.api)) { +if (this.localuser.guildids.has(guild.id)) { +button.textContent = "Already joined"; +button.disabled = true; +} +} +button.classList.add("acceptinvbutton"); +div.append(button); +button.onclick = (_) => { +if (this.localuser.info.api.startsWith(info.api)) { +fetch(this.localuser.info.api + "/invites/" + json.code, { +method: "POST", +headers: this.localuser.headers, +}) +.then((r) => r.json()) +.then((_) => { +if (_.message) { +alert(_.message); +} +}); +} else { +if (this.json.invite) { +const params = new URLSearchParams(""); +params.set("instance", this.json.invite.url); +const encoded = params.toString(); +const url = `${location.origin}/invite/${this.json.invite.code}?${encoded}`; +window.open(url, "_blank"); +} +} +}; +})(); +return div; +} +generateArticle() { +const colordiv = document.createElement("div"); +colordiv.style.backgroundColor = "#000000"; +colordiv.classList.add("embed-color"); - const div = document.createElement("div"); - div.classList.add("embed"); - if (this.json.provider) { - const provider = document.createElement("p"); - provider.classList.add("provider"); - provider.textContent = this.json.provider.name; - div.append(provider); - } - const a = document.createElement("a"); - if (this.json.url && this.json.url) { - MarkDown.safeLink(a, this.json.url); - a.textContent = this.json.url; - div.append(a); - } - if (this.json.description) { - const description = document.createElement("p"); - description.textContent = this.json.description; - div.append(description); - } - if (this.json.thumbnail) { - const img = document.createElement("img"); - if (this.json.thumbnail.width && this.json.thumbnail.width) { - let scale = 1; - const inch = 96; - scale = Math.max(scale, this.json.thumbnail.width / inch / 4); - scale = Math.max(scale, this.json.thumbnail.height / inch / 3); - this.json.thumbnail.width /= scale; - this.json.thumbnail.height /= scale; - img.style.width = this.json.thumbnail.width + "px"; - img.style.height = this.json.thumbnail.height + "px"; - } - img.classList.add("bigembedimg"); - if (this.json.video) { - img.onclick = async () => { - if (this.json.video) { - img.remove(); - const iframe = document.createElement("iframe"); - iframe.src = this.json.video.url + "?autoplay=1"; - if (this.json.thumbnail.width && this.json.thumbnail.width) { - iframe.style.width = this.json.thumbnail.width + "px"; - iframe.style.height = this.json.thumbnail.height + "px"; - } - div.append(iframe); - } - }; - } else { - img.onclick = async () => { - const full = new Dialog(["img", img.src, ["fit"]]); - full.show(); - }; - } - img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url; - div.append(img); - } - colordiv.append(div); - return colordiv; - } +const div = document.createElement("div"); +div.classList.add("embed"); +if (this.json.provider) { +const provider = document.createElement("p"); +provider.classList.add("provider"); +provider.textContent = this.json.provider.name; +div.append(provider); +} +const a = document.createElement("a"); +if (this.json.url && this.json.url) { +MarkDown.safeLink(a, this.json.url); +a.textContent = this.json.url; +div.append(a); +} +if (this.json.description) { +const description = document.createElement("p"); +description.textContent = this.json.description; +div.append(description); +} +if (this.json.thumbnail) { +const img = document.createElement("img"); +if (this.json.thumbnail.width && this.json.thumbnail.width) { +let scale = 1; +const inch = 96; +scale = Math.max(scale, this.json.thumbnail.width / inch / 4); +scale = Math.max(scale, this.json.thumbnail.height / inch / 3); +this.json.thumbnail.width /= scale; +this.json.thumbnail.height /= scale; +img.style.width = this.json.thumbnail.width + "px"; +img.style.height = this.json.thumbnail.height + "px"; +} +img.classList.add("bigembedimg"); +if (this.json.video) { +img.onclick = async () => { +if (this.json.video) { +img.remove(); +const iframe = document.createElement("iframe"); +iframe.src = this.json.video.url + "?autoplay=1"; +if (this.json.thumbnail.width && this.json.thumbnail.width) { +iframe.style.width = this.json.thumbnail.width + "px"; +iframe.style.height = this.json.thumbnail.height + "px"; +} +div.append(iframe); +} +}; +} else { +img.onclick = async () => { +const full = new Dialog(["img", img.src, ["fit"]]); +full.show(); +}; +} +img.src = this.json.thumbnail.proxy_url || this.json.thumbnail.url; +div.append(img); +} +colordiv.append(div); +return colordiv; +} } export { Embed }; diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts index 2e61a93..c5d6ce8 100644 --- a/src/webpage/emoji.ts +++ b/src/webpage/emoji.ts @@ -3,257 +3,257 @@ import { Guild } from "./guild.js"; import { Localuser } from "./localuser.js"; class Emoji { - static emojis: { - name: string; - emojis: { - name: string; - emoji: string; - }[]; - }[]; - name: string; - id: string; - animated: boolean; - owner: Guild | Localuser; - get guild() { - if (this.owner instanceof Guild) { - 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 { - let res: (r: Emoji | string) => void; - const promise: Promise = 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; - } +static emojis: { +name: string; +emojis: { +name: string; +emoji: string; +}[]; +}[]; +name: string; +id: string; +animated: boolean; +owner: Guild | Localuser; +get guild() { +if (this.owner instanceof Guild) { +return this.owner; } -Emoji.grabEmoji(); -export { Emoji }; +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 { + let res: (r: Emoji | string) => void; + const promise: Promise = 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 }; diff --git a/src/webpage/file.ts b/src/webpage/file.ts index e0f2311..570d620 100644 --- a/src/webpage/file.ts +++ b/src/webpage/file.ts @@ -3,150 +3,150 @@ import { Dialog } from "./dialog.js"; import { filejson } from "./jsontypes.js"; class File { - owner: Message | null; - id: string; - filename: string; - content_type: string; - width: number | undefined; - height: number | undefined; - proxy_url: string | undefined; - url: string; - size: number; - constructor(fileJSON: filejson, owner: Message | null) { - this.owner = owner; - this.id = fileJSON.id; - this.filename = fileJSON.filename; - this.content_type = fileJSON.content_type; - this.width = fileJSON.width; - this.height = fileJSON.height; - this.url = fileJSON.url; - this.proxy_url = fileJSON.proxy_url; - this.content_type = fileJSON.content_type; - this.size = fileJSON.size; - } - getHTML(temp: boolean = false): HTMLElement { - const src = this.proxy_url || this.url; - if (this.width && this.height) { - let scale = 1; - const max = 96 * 3; - scale = Math.max(scale, this.width / max); - scale = Math.max(scale, this.height / max); - this.width /= scale; - this.height /= scale; - } - if (this.content_type.startsWith("image/")) { - const div = document.createElement("div"); - const img = document.createElement("img"); - img.classList.add("messageimg"); - div.classList.add("messageimgdiv"); - img.onclick = function () { - const full = new Dialog(["img", img.src, ["fit"]]); - full.show(); - }; - img.src = src; - div.append(img); - if (this.width) { - div.style.width = this.width + "px"; - div.style.height = this.height + "px"; - } - console.log(img); - console.log(this.width, this.height); - return div; - } else if (this.content_type.startsWith("video/")) { - const video = document.createElement("video"); - const source = document.createElement("source"); - source.src = src; - video.append(source); - source.type = this.content_type; - video.controls = !temp; - if (this.width && this.height) { - video.width = this.width; - video.height = this.height; - } - return video; - } else if (this.content_type.startsWith("audio/")) { - const audio = document.createElement("audio"); - const source = document.createElement("source"); - source.src = src; - audio.append(source); - source.type = this.content_type; - audio.controls = !temp; - return audio; - } else { - return this.createunknown(); - } - } - upHTML(files: Blob[], file: globalThis.File): HTMLElement { - const div = document.createElement("div"); - const contained = this.getHTML(true); - div.classList.add("containedFile"); - div.append(contained); - const controls = document.createElement("div"); - const garbage = document.createElement("button"); - garbage.textContent = "🗑"; - garbage.onclick = (_) => { - div.remove(); - files.splice(files.indexOf(file), 1); - }; - controls.classList.add("controls"); - div.append(controls); - controls.append(garbage); - return div; - } - static initFromBlob(file: globalThis.File) { - return new File( - { - filename: file.name, - size: file.size, - id: "null", - content_type: file.type, - width: undefined, - height: undefined, - url: URL.createObjectURL(file), - proxy_url: undefined, - }, - null - ); - } - createunknown(): HTMLElement { - console.log("🗎"); - const src = this.proxy_url || this.url; - const div = document.createElement("table"); - div.classList.add("unknownfile"); - const nametr = document.createElement("tr"); - div.append(nametr); - const fileicon = document.createElement("td"); - nametr.append(fileicon); - fileicon.append("🗎"); - fileicon.classList.add("fileicon"); - fileicon.rowSpan = 2; - const nametd = document.createElement("td"); - if (src) { - const a = document.createElement("a"); - a.href = src; - a.textContent = this.filename; - nametd.append(a); - } else { - nametd.textContent = this.filename; - } +owner: Message | null; +id: string; +filename: string; +content_type: string; +width: number | undefined; +height: number | undefined; +proxy_url: string | undefined; +url: string; +size: number; +constructor(fileJSON: filejson, owner: Message | null) { +this.owner = owner; +this.id = fileJSON.id; +this.filename = fileJSON.filename; +this.content_type = fileJSON.content_type; +this.width = fileJSON.width; +this.height = fileJSON.height; +this.url = fileJSON.url; +this.proxy_url = fileJSON.proxy_url; +this.content_type = fileJSON.content_type; +this.size = fileJSON.size; +} +getHTML(temp: boolean = false): HTMLElement { +const src = this.proxy_url || this.url; +if (this.width && this.height) { +let scale = 1; +const max = 96 * 3; +scale = Math.max(scale, this.width / max); +scale = Math.max(scale, this.height / max); +this.width /= scale; +this.height /= scale; +} +if (this.content_type.startsWith("image/")) { +const div = document.createElement("div"); +const img = document.createElement("img"); +img.classList.add("messageimg"); +div.classList.add("messageimgdiv"); +img.onclick = function () { +const full = new Dialog(["img", img.src, ["fit"]]); +full.show(); +}; +img.src = src; +div.append(img); +if (this.width) { +div.style.width = this.width + "px"; +div.style.height = this.height + "px"; +} +console.log(img); +console.log(this.width, this.height); +return div; +} else if (this.content_type.startsWith("video/")) { +const video = document.createElement("video"); +const source = document.createElement("source"); +source.src = src; +video.append(source); +source.type = this.content_type; +video.controls = !temp; +if (this.width && this.height) { +video.width = this.width; +video.height = this.height; +} +return video; +} else if (this.content_type.startsWith("audio/")) { +const audio = document.createElement("audio"); +const source = document.createElement("source"); +source.src = src; +audio.append(source); +source.type = this.content_type; +audio.controls = !temp; +return audio; +} else { +return this.createunknown(); +} +} +upHTML(files: Blob[], file: globalThis.File): HTMLElement { +const div = document.createElement("div"); +const contained = this.getHTML(true); +div.classList.add("containedFile"); +div.append(contained); +const controls = document.createElement("div"); +const garbage = document.createElement("button"); +garbage.textContent = "🗑"; +garbage.onclick = (_) => { +div.remove(); +files.splice(files.indexOf(file), 1); +}; +controls.classList.add("controls"); +div.append(controls); +controls.append(garbage); +return div; +} +static initFromBlob(file: globalThis.File) { +return new File( +{ +filename: file.name, +size: file.size, +id: "null", +content_type: file.type, +width: undefined, +height: undefined, +url: URL.createObjectURL(file), +proxy_url: undefined, +}, +null +); +} +createunknown(): HTMLElement { +console.log("🗎"); +const src = this.proxy_url || this.url; +const div = document.createElement("table"); +div.classList.add("unknownfile"); +const nametr = document.createElement("tr"); +div.append(nametr); +const fileicon = document.createElement("td"); +nametr.append(fileicon); +fileicon.append("🗎"); +fileicon.classList.add("fileicon"); +fileicon.rowSpan = 2; +const nametd = document.createElement("td"); +if (src) { +const a = document.createElement("a"); +a.href = src; +a.textContent = this.filename; +nametd.append(a); +} else { +nametd.textContent = this.filename; +} - nametd.classList.add("filename"); - nametr.append(nametd); - const sizetr = document.createElement("tr"); - const size = document.createElement("td"); - sizetr.append(size); - size.textContent = "Size:" + File.filesizehuman(this.size); - size.classList.add("filesize"); - div.appendChild(sizetr); - return div; - } - static filesizehuman(fsize: number) { - const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); - return ( - Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + - " " + - ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] - ); - } +nametd.classList.add("filename"); +nametr.append(nametd); +const sizetr = document.createElement("tr"); +const size = document.createElement("td"); +sizetr.append(size); +size.textContent = "Size:" + File.filesizehuman(this.size); +size.classList.add("filesize"); +div.appendChild(sizetr); +return div; +} +static filesizehuman(fsize: number) { +const i = fsize == 0 ? 0 : Math.floor(Math.log(fsize) / Math.log(1024)); +return ( +Number((fsize / Math.pow(1024, i)).toFixed(2)) * 1 + +" " + +["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"][i] +); +} } export { File }; diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index 3c2ec7a..a1351da 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -8,717 +8,717 @@ import { Settings } from "./settings.js"; import { Permissions } from "./permissions.js"; import { SnowFlake } from "./snowflake.js"; import { - channeljson, - guildjson, - emojijson, - memberjson, - invitejson, +channeljson, +guildjson, +emojijson, +memberjson, +invitejson, } from "./jsontypes.js"; import { User } from "./user.js"; class Guild extends SnowFlake { - owner!: Localuser; - headers!: Localuser["headers"]; - channels!: Channel[]; - properties!: guildjson["properties"]; - member_count!: number; - roles!: Role[]; - roleids!: Map; - prevchannel: Channel | undefined; - banner!: string; - message_notifications!: number; - headchannels!: Channel[]; - position!: number; - parent_id!: string; - member!: Member; - html!: HTMLElement; - emojis!: emojijson[]; - large!: boolean; - static contextmenu = new Contextmenu("guild menu"); - static setupcontextmenu() { - Guild.contextmenu.addbutton("Copy Guild id", function (this: Guild) { - navigator.clipboard.writeText(this.id); - }); +owner!: Localuser; +headers!: Localuser["headers"]; +channels!: Channel[]; +properties!: guildjson["properties"]; +member_count!: number; +roles!: Role[]; +roleids!: Map; + prevchannel: Channel | undefined; + banner!: string; + message_notifications!: number; + headchannels!: Channel[]; + position!: number; + parent_id!: string; + member!: Member; + html!: HTMLElement; + emojis!: emojijson[]; + large!: boolean; + static contextmenu = new Contextmenu("guild menu"); + static setupcontextmenu() { + Guild.contextmenu.addbutton("Copy Guild id", function (this: Guild) { + navigator.clipboard.writeText(this.id); + }); - Guild.contextmenu.addbutton("Mark as read", function (this: Guild) { - this.markAsRead(); - }); + Guild.contextmenu.addbutton("Mark as read", function (this: Guild) { + this.markAsRead(); + }); - Guild.contextmenu.addbutton("Notifications", function (this: Guild) { - this.setnotifcation(); - }); + Guild.contextmenu.addbutton("Notifications", function (this: Guild) { + this.setnotifcation(); + }); - Guild.contextmenu.addbutton( - "Leave guild", - function (this: Guild) { - this.confirmleave(); - }, - null, - function (_) { - return this.properties.owner_id !== this.member.user.id; - } - ); + Guild.contextmenu.addbutton( + "Leave guild", + function (this: Guild) { + this.confirmleave(); + }, + null, + function (_) { + return this.properties.owner_id !== this.member.user.id; + } + ); - Guild.contextmenu.addbutton( - "Delete guild", - function (this: Guild) { - this.confirmDelete(); - }, - null, - function (_) { - return this.properties.owner_id === this.member.user.id; - } - ); + Guild.contextmenu.addbutton( + "Delete guild", + function (this: Guild) { + this.confirmDelete(); + }, + null, + function (_) { + return this.properties.owner_id === this.member.user.id; + } + ); - Guild.contextmenu.addbutton( - "Create invite", - function (this: Guild) {}, - null, - (_) => true, - (_) => false - ); - Guild.contextmenu.addbutton("Settings", function (this: Guild) { - this.generateSettings(); - }); - /* -----things left for later----- - guild.contextmenu.addbutton("Leave Guild",function(){ - console.log(this) - this.deleteChannel(); - },null,_=>{return thisuser.isAdmin()}) + Guild.contextmenu.addbutton( + "Create invite", + function (this: Guild) {}, + null, + (_) => true, + (_) => false + ); + Guild.contextmenu.addbutton("Settings", function (this: Guild) { + this.generateSettings(); + }); + /* -----things left for later----- + guild.contextmenu.addbutton("Leave Guild",function(){ + console.log(this) + this.deleteChannel(); + },null,_=>{return thisuser.isAdmin()}) - guild.contextmenu.addbutton("Mute Guild",function(){ - editchannelf(this); - },null,_=>{return thisuser.isAdmin()}) - */ - } - generateSettings() { - const settings = new Settings("Settings for " + this.properties.name); - { - const overview = settings.addButton("Overview"); - const form = overview.addForm("", (_) => {}, { - headers: this.headers, - traditionalSubmit: true, - fetchURL: this.info.api + "/guilds/" + this.id, - method: "PATCH", - }); - form.addTextInput("Name:", "name", { initText: this.properties.name }); - form.addMDInput("Description:", "description", { - initText: this.properties.description, - }); - form.addFileInput("Banner:", "banner", { clear: true }); - form.addFileInput("Icon:", "icon", { clear: true }); - let region = this.properties.region; - if (!region) { - region = ""; - } - form.addTextInput("Region:", "region", { initText: region }); - } - const s1 = settings.addButton("roles"); - const permlist: [Role, Permissions][] = []; - for (const thing of this.roles) { - permlist.push([thing, thing.permissions]); - } - s1.options.push( - new RoleList(permlist, this, this.updateRolePermissions.bind(this)) - ); - settings.show(); - } - constructor( - json: guildjson | -1, - owner: Localuser, - member: memberjson | User | null - ) { - if (json === -1 || member === null) { - super("@me"); - return; - } - if (json.stickers.length) { - console.log(json.stickers, ":3"); - } - super(json.id); - this.large = json.large; - this.member_count = json.member_count; - this.emojis = json.emojis; - this.owner = owner; - this.headers = this.owner.headers; - this.channels = []; - this.properties = json.properties; - this.roles = []; - this.roleids = new Map(); + guild.contextmenu.addbutton("Mute Guild",function(){ + editchannelf(this); + },null,_=>{return thisuser.isAdmin()}) + */ + } + generateSettings() { + const settings = new Settings("Settings for " + this.properties.name); + { + const overview = settings.addButton("Overview"); + const form = overview.addForm("", (_) => {}, { + headers: this.headers, + traditionalSubmit: true, + fetchURL: this.info.api + "/guilds/" + this.id, + method: "PATCH", + }); + form.addTextInput("Name:", "name", { initText: this.properties.name }); + form.addMDInput("Description:", "description", { + initText: this.properties.description, + }); + form.addFileInput("Banner:", "banner", { clear: true }); + form.addFileInput("Icon:", "icon", { clear: true }); + let region = this.properties.region; + if (!region) { + region = ""; + } + form.addTextInput("Region:", "region", { initText: region }); + } + const s1 = settings.addButton("roles"); + const permlist: [Role, Permissions][] = []; + for (const thing of this.roles) { + permlist.push([thing, thing.permissions]); + } + s1.options.push( + new RoleList(permlist, this, this.updateRolePermissions.bind(this)) + ); + settings.show(); + } + constructor( + json: guildjson | -1, + owner: Localuser, + member: memberjson | User | null + ) { + if (json === -1 || member === null) { + super("@me"); + return; + } + if (json.stickers.length) { + console.log(json.stickers, ":3"); + } + super(json.id); + this.large = json.large; + this.member_count = json.member_count; + this.emojis = json.emojis; + this.owner = owner; + this.headers = this.owner.headers; + this.channels = []; + this.properties = json.properties; + this.roles = []; + this.roleids = new Map(); - this.message_notifications = 0; - for (const roley of json.roles) { - const roleh = new Role(roley, this); - this.roles.push(roleh); - this.roleids.set(roleh.id, roleh); - } - if (member instanceof User) { - Member.resolveMember(member, this).then((_) => { - if (_) { - this.member = _; - } else { - console.error("Member was unable to resolve"); - } - }); - } else { - Member.new(member, this).then((_) => { - if (_) { - this.member = _; - } - }); - } - this.perminfo ??= { channels: {} }; - for (const thing of json.channels) { - const temp = new Channel(thing, this); - this.channels.push(temp); - this.localuser.channelids.set(temp.id, temp); - } - this.headchannels = []; - for (const thing of this.channels) { - const parent = thing.resolveparent(this); - if (!parent) { - this.headchannels.push(thing); - } - } - this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel); - } - get perminfo() { - return this.localuser.perminfo.guilds[this.id]; - } - set perminfo(e) { - this.localuser.perminfo.guilds[this.id] = e; - } - notisetting(settings: { - channel_overrides?: unknown[]; - message_notifications: any; - flags?: number; - hide_muted_channels?: boolean; - mobile_push?: boolean; - mute_config?: null; - mute_scheduled_events?: boolean; - muted?: boolean; - notify_highlights?: number; - suppress_everyone?: boolean; - suppress_roles?: boolean; - version?: number; - guild_id?: string; - }) { - this.message_notifications = settings.message_notifications; - } - setnotifcation() { - let noti = this.message_notifications; - const notiselect = new Dialog([ - "vdiv", - [ - "radio", - "select notifications type", - ["all", "only mentions", "none"], - function (e: string /* "all" | "only mentions" | "none" */) { - noti = ["all", "only mentions", "none"].indexOf(e); - }, - noti, - ], - [ - "button", - "", - "submit", - (_: any) => { - // - fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - message_notifications: noti, - }), - }); - this.message_notifications = noti; - }, - ], - ]); - notiselect.show(); - } - confirmleave() { - const full = new Dialog([ - "vdiv", - ["title", "Are you sure you want to leave?"], - [ - "hdiv", - [ - "button", - "", - "Yes, I'm sure", - (_: any) => { - this.leave().then((_) => { - full.hide(); - }); - }, - ], - [ - "button", - "", - "Nevermind", - (_: any) => { - full.hide(); - }, - ], - ], - ]); - full.show(); - } - async leave() { - return fetch(this.info.api + "/users/@me/guilds/" + this.id, { - method: "DELETE", - headers: this.headers, - }); - } - printServers() { - let build = ""; - for (const thing of this.headchannels) { - build += thing.name + ":" + thing.position + "\n"; - for (const thingy of thing.children) { - build += " " + thingy.name + ":" + thingy.position + "\n"; - } - } - console.log(build); - } - calculateReorder() { - let position = -1; - const build: { - id: string; - position: number | undefined; - parent_id: string | undefined; - }[] = []; - for (const thing of this.headchannels) { - const thisthing: { - id: string; - position: number | undefined; - parent_id: string | undefined; - } = { id: thing.id, position: undefined, parent_id: undefined }; - if (thing.position <= position) { - thing.position = thisthing.position = position + 1; - } - position = thing.position; - console.log(position); - if (thing.move_id && thing.move_id !== thing.parent_id) { - thing.parent_id = thing.move_id; - thisthing.parent_id = thing.parent?.id; - thing.move_id = undefined; - } - if (thisthing.position || thisthing.parent_id) { - build.push(thisthing); - } - if (thing.children.length > 0) { - const things = thing.calculateReorder(); - for (const thing of things) { - build.push(thing); - } - } - } - console.log(build); - this.printServers(); - if (build.length === 0) { - return; - } - const serverbug = false; - if (serverbug) { - for (const thing of build) { - console.log(build, thing); - fetch(this.info.api + "/guilds/" + this.id + "/channels", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify([thing]), - }); - } - } else { - fetch(this.info.api + "/guilds/" + this.id + "/channels", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(build), - }); - } - } - get localuser() { - return this.owner; - } - get info() { - return this.owner.info; - } - sortchannels() { - this.headchannels.sort((a, b) => { - return a.position - b.position; - }); - } - static generateGuildIcon( - guild: Guild | (invitejson["guild"] & { info: { cdn: string } }) - ) { - const divy = document.createElement("div"); - divy.classList.add("servernoti"); + this.message_notifications = 0; + for (const roley of json.roles) { + const roleh = new Role(roley, this); + this.roles.push(roleh); + this.roleids.set(roleh.id, roleh); + } + if (member instanceof User) { + Member.resolveMember(member, this).then((_) => { + if (_) { + this.member = _; + } else { + console.error("Member was unable to resolve"); + } + }); + } else { + Member.new(member, this).then((_) => { + if (_) { + this.member = _; + } + }); + } + this.perminfo ??= { channels: {} }; + for (const thing of json.channels) { + const temp = new Channel(thing, this); + this.channels.push(temp); + this.localuser.channelids.set(temp.id, temp); + } + this.headchannels = []; + for (const thing of this.channels) { + const parent = thing.resolveparent(this); + if (!parent) { + this.headchannels.push(thing); + } + } + this.prevchannel = this.localuser.channelids.get(this.perminfo.prevchannel); + } + get perminfo() { + return this.localuser.perminfo.guilds[this.id]; + } + set perminfo(e) { + this.localuser.perminfo.guilds[this.id] = e; + } + notisetting(settings: { + channel_overrides?: unknown[]; + message_notifications: any; + flags?: number; + hide_muted_channels?: boolean; + mobile_push?: boolean; + mute_config?: null; + mute_scheduled_events?: boolean; + muted?: boolean; + notify_highlights?: number; + suppress_everyone?: boolean; + suppress_roles?: boolean; + version?: number; + guild_id?: string; + }) { + this.message_notifications = settings.message_notifications; + } + setnotifcation() { + let noti = this.message_notifications; + const notiselect = new Dialog([ + "vdiv", + [ + "radio", + "select notifications type", + ["all", "only mentions", "none"], + function (e: string /* "all" | "only mentions" | "none" */) { + noti = ["all", "only mentions", "none"].indexOf(e); + }, + noti, + ], + [ + "button", + "", + "submit", + (_: any) => { + // + fetch(this.info.api + `/users/@me/guilds/${this.id}/settings/`, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + message_notifications: noti, + }), + }); + this.message_notifications = noti; + }, + ], + ]); + notiselect.show(); + } + confirmleave() { + const full = new Dialog([ + "vdiv", + ["title", "Are you sure you want to leave?"], + [ + "hdiv", + [ + "button", + "", + "Yes, I'm sure", + (_: any) => { + this.leave().then((_) => { + full.hide(); + }); + }, + ], + [ + "button", + "", + "Nevermind", + (_: any) => { + full.hide(); + }, + ], + ], + ]); + full.show(); + } + async leave() { + return fetch(this.info.api + "/users/@me/guilds/" + this.id, { + method: "DELETE", + headers: this.headers, + }); + } + printServers() { + let build = ""; + for (const thing of this.headchannels) { + build += thing.name + ":" + thing.position + "\n"; + for (const thingy of thing.children) { + build += " " + thingy.name + ":" + thingy.position + "\n"; + } + } + console.log(build); + } + calculateReorder() { + let position = -1; + const build: { + id: string; + position: number | undefined; + parent_id: string | undefined; + }[] = []; + for (const thing of this.headchannels) { + const thisthing: { + id: string; + position: number | undefined; + parent_id: string | undefined; + } = { id: thing.id, position: undefined, parent_id: undefined }; + if (thing.position <= position) { + thing.position = thisthing.position = position + 1; + } + position = thing.position; + console.log(position); + if (thing.move_id && thing.move_id !== thing.parent_id) { + thing.parent_id = thing.move_id; + thisthing.parent_id = thing.parent?.id; + thing.move_id = undefined; + } + if (thisthing.position || thisthing.parent_id) { + build.push(thisthing); + } + if (thing.children.length > 0) { + const things = thing.calculateReorder(); + for (const thing of things) { + build.push(thing); + } + } + } + console.log(build); + this.printServers(); + if (build.length === 0) { + return; + } + const serverbug = false; + if (serverbug) { + for (const thing of build) { + console.log(build, thing); + fetch(this.info.api + "/guilds/" + this.id + "/channels", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify([thing]), + }); + } + } else { + fetch(this.info.api + "/guilds/" + this.id + "/channels", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(build), + }); + } + } + get localuser() { + return this.owner; + } + get info() { + return this.owner.info; + } + sortchannels() { + this.headchannels.sort((a, b) => { + return a.position - b.position; + }); + } + static generateGuildIcon( + guild: Guild | (invitejson["guild"] & { info: { cdn: string } }) + ) { + const divy = document.createElement("div"); + divy.classList.add("servernoti"); - const noti = document.createElement("div"); - noti.classList.add("unread"); - divy.append(noti); - if (guild instanceof Guild) { - guild.localuser.guildhtml.set(guild.id, divy); - } - let icon: string | null; - if (guild instanceof Guild) { - icon = guild.properties.icon; - } else { - icon = guild.icon; - } - if (icon !== null) { - const img = document.createElement("img"); - img.classList.add("pfp", "servericon"); - img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; - divy.appendChild(img); - if (guild instanceof Guild) { - img.onclick = () => { - console.log(guild.loadGuild); - guild.loadGuild(); - guild.loadChannel(); - }; - Guild.contextmenu.bindContextmenu(img, guild, undefined); - } - } else { - const div = document.createElement("div"); - let name: string; - if (guild instanceof Guild) { - name = guild.properties.name; - } else { - name = guild.name; - } - const build = name - .replace(/'s /g, " ") - .replace(/\w+/g, (word) => word[0]) - .replace(/\s/g, ""); - div.textContent = build; - div.classList.add("blankserver", "servericon"); - divy.appendChild(div); - if (guild instanceof Guild) { - div.onclick = () => { - guild.loadGuild(); - guild.loadChannel(); - }; - Guild.contextmenu.bindContextmenu(div, guild, undefined); - } - } - return divy; - } - generateGuildIcon() { - return Guild.generateGuildIcon(this); - } - confirmDelete() { - let confirmname = ""; - const full = new Dialog([ - "vdiv", - [ - "title", - "Are you sure you want to delete " + this.properties.name + "?", - ], - [ - "textbox", - "Name of server:", - "", - function (this: HTMLInputElement) { - confirmname = this.value; - }, - ], - [ - "hdiv", - [ - "button", - "", - "Yes, I'm sure", - (_: any) => { - console.log(confirmname); - if (confirmname !== this.properties.name) { - return; - } - this.delete().then((_) => { - full.hide(); - }); - }, - ], - [ - "button", - "", - "Nevermind", - (_: any) => { - full.hide(); - }, - ], - ], - ]); - full.show(); - } - async delete() { - return fetch(this.info.api + "/guilds/" + this.id + "/delete", { - method: "POST", - headers: this.headers, - }); - } - unreads(html?: HTMLElement | undefined) { - if (html) { - this.html = html; - } else { - html = this.html; - } - let read = true; - for (const thing of this.channels) { - if (thing.hasunreads) { - console.log(thing); - read = false; - break; - } - } - if (!html) { - return; - } - if (read) { - html.children[0].classList.remove("notiunread"); - } else { - html.children[0].classList.add("notiunread"); - } - } - getHTML() { - //this.printServers(); - this.sortchannels(); - this.printServers(); - const build = document.createElement("div"); + const noti = document.createElement("div"); + noti.classList.add("unread"); + divy.append(noti); + if (guild instanceof Guild) { + guild.localuser.guildhtml.set(guild.id, divy); + } + let icon: string | null; + if (guild instanceof Guild) { + icon = guild.properties.icon; + } else { + icon = guild.icon; + } + if (icon !== null) { + const img = document.createElement("img"); + img.classList.add("pfp", "servericon"); + img.src = guild.info.cdn + "/icons/" + guild.id + "/" + icon + ".png"; + divy.appendChild(img); + if (guild instanceof Guild) { + img.onclick = () => { + console.log(guild.loadGuild); + guild.loadGuild(); + guild.loadChannel(); + }; + Guild.contextmenu.bindContextmenu(img, guild, undefined); + } + } else { + const div = document.createElement("div"); + let name: string; + if (guild instanceof Guild) { + name = guild.properties.name; + } else { + name = guild.name; + } + const build = name + .replace(/'s /g, " ") + .replace(/\w+/g, (word) => word[0]) + .replace(/\s/g, ""); + div.textContent = build; + div.classList.add("blankserver", "servericon"); + divy.appendChild(div); + if (guild instanceof Guild) { + div.onclick = () => { + guild.loadGuild(); + guild.loadChannel(); + }; + Guild.contextmenu.bindContextmenu(div, guild, undefined); + } + } + return divy; + } + generateGuildIcon() { + return Guild.generateGuildIcon(this); + } + confirmDelete() { + let confirmname = ""; + const full = new Dialog([ + "vdiv", + [ + "title", + "Are you sure you want to delete " + this.properties.name + "?", + ], + [ + "textbox", + "Name of server:", + "", + function (this: HTMLInputElement) { + confirmname = this.value; + }, + ], + [ + "hdiv", + [ + "button", + "", + "Yes, I'm sure", + (_: any) => { + console.log(confirmname); + if (confirmname !== this.properties.name) { + return; + } + this.delete().then((_) => { + full.hide(); + }); + }, + ], + [ + "button", + "", + "Nevermind", + (_: any) => { + full.hide(); + }, + ], + ], + ]); + full.show(); + } + async delete() { + return fetch(this.info.api + "/guilds/" + this.id + "/delete", { + method: "POST", + headers: this.headers, + }); + } + unreads(html?: HTMLElement | undefined) { + if (html) { + this.html = html; + } else { + html = this.html; + } + let read = true; + for (const thing of this.channels) { + if (thing.hasunreads) { + console.log(thing); + read = false; + break; + } + } + if (!html) { + return; + } + if (read) { + html.children[0].classList.remove("notiunread"); + } else { + html.children[0].classList.add("notiunread"); + } + } + getHTML() { + //this.printServers(); + this.sortchannels(); + this.printServers(); + const build = document.createElement("div"); - for (const thing of this.headchannels) { - build.appendChild(thing.createguildHTML(this.isAdmin())); - } - return build; - } - isAdmin() { - return this.member.isAdmin(); - } - async markAsRead() { - const build: { - read_states: { - channel_id: string; - message_id: string | null | undefined; - read_state_type: number; - }[]; - } = { read_states: [] }; - for (const thing of this.channels) { - if (thing.hasunreads) { - build.read_states.push({ - channel_id: thing.id, - message_id: thing.lastmessageid, - read_state_type: 0, - }); - thing.lastreadmessageid = thing.lastmessageid; - if (!thing.myhtml) continue; - thing.myhtml.classList.remove("cunread"); - } - } - this.unreads(); - fetch(this.info.api + "/read-states/ack-bulk", { - method: "POST", - headers: this.headers, - body: JSON.stringify(build), - }); - } - hasRole(r: Role | string) { - console.log("this should run"); - if (r instanceof Role) { - r = r.id; - } - return this.member.hasRole(r); - } - loadChannel(ID?: string | undefined) { - if (ID) { - const channel = this.localuser.channelids.get(ID); - if (channel) { - channel.getHTML(); - return; - } - } - if (this.prevchannel) { - console.log(this.prevchannel); - this.prevchannel.getHTML(); - return; - } - for (const thing of this.channels) { - if (thing.children.length === 0) { - thing.getHTML(); - return; - } - } - } - loadGuild() { - this.localuser.loadGuild(this.id); - } - updateChannel(json: channeljson) { - const channel = this.localuser.channelids.get(json.id); - if (channel) { - channel.updateChannel(json); - this.headchannels = []; - for (const thing of this.channels) { - thing.children = []; - } - this.headchannels = []; - for (const thing of this.channels) { - const parent = thing.resolveparent(this); - if (!parent) { - this.headchannels.push(thing); - } - } - this.printServers(); - } - } - createChannelpac(json: channeljson) { - const thischannel = new Channel(json, this); - this.localuser.channelids.set(json.id, thischannel); - this.channels.push(thischannel); - thischannel.resolveparent(this); - if (!thischannel.parent) { - this.headchannels.push(thischannel); - } - this.calculateReorder(); - this.printServers(); - return thischannel; - } - createchannels(func = this.createChannel) { - let name = ""; - let category = 0; - const channelselect = new Dialog([ - "vdiv", - [ - "radio", - "select channel type", - ["voice", "text", "announcement"], - function (radio: string) { - console.log(radio); - category = - { text: 0, voice: 2, announcement: 5, category: 4 }[radio] || 0; - }, - 1, - ], - [ - "textbox", - "Name of channel", - "", - function (this: HTMLInputElement) { - name = this.value; - }, - ], - [ - "button", - "", - "submit", - function () { - console.log(name, category); - func(name, category); - channelselect.hide(); - }, - ], - ]); - channelselect.show(); - } - createcategory() { - let name = ""; - const category = 4; - const channelselect = new Dialog([ - "vdiv", - [ - "textbox", - "Name of category", - "", - function (this: HTMLInputElement) { - name = this.value; - }, - ], - [ - "button", - "", - "submit", - () => { - console.log(name, category); - this.createChannel(name, category); - channelselect.hide(); - }, - ], - ]); - channelselect.show(); - } - delChannel(json: channeljson) { - const channel = this.localuser.channelids.get(json.id); - this.localuser.channelids.delete(json.id); - if (!channel) return; - this.channels.splice(this.channels.indexOf(channel), 1); - const indexy = this.headchannels.indexOf(channel); - if (indexy !== -1) { - this.headchannels.splice(indexy, 1); - } + for (const thing of this.headchannels) { + build.appendChild(thing.createguildHTML(this.isAdmin())); + } + return build; + } + isAdmin() { + return this.member.isAdmin(); + } + async markAsRead() { + const build: { + read_states: { + channel_id: string; + message_id: string | null | undefined; + read_state_type: number; + }[]; + } = { read_states: [] }; + for (const thing of this.channels) { + if (thing.hasunreads) { + build.read_states.push({ + channel_id: thing.id, + message_id: thing.lastmessageid, + read_state_type: 0, + }); + thing.lastreadmessageid = thing.lastmessageid; + if (!thing.myhtml) continue; + thing.myhtml.classList.remove("cunread"); + } + } + this.unreads(); + fetch(this.info.api + "/read-states/ack-bulk", { + method: "POST", + headers: this.headers, + body: JSON.stringify(build), + }); + } + hasRole(r: Role | string) { + console.log("this should run"); + if (r instanceof Role) { + r = r.id; + } + return this.member.hasRole(r); + } + loadChannel(ID?: string | undefined) { + if (ID) { + const channel = this.localuser.channelids.get(ID); + if (channel) { + channel.getHTML(); + return; + } + } + if (this.prevchannel) { + console.log(this.prevchannel); + this.prevchannel.getHTML(); + return; + } + for (const thing of this.channels) { + if (thing.children.length === 0) { + thing.getHTML(); + return; + } + } + } + loadGuild() { + this.localuser.loadGuild(this.id); + } + updateChannel(json: channeljson) { + const channel = this.localuser.channelids.get(json.id); + if (channel) { + channel.updateChannel(json); + this.headchannels = []; + for (const thing of this.channels) { + thing.children = []; + } + this.headchannels = []; + for (const thing of this.channels) { + const parent = thing.resolveparent(this); + if (!parent) { + this.headchannels.push(thing); + } + } + this.printServers(); + } + } + createChannelpac(json: channeljson) { + const thischannel = new Channel(json, this); + this.localuser.channelids.set(json.id, thischannel); + this.channels.push(thischannel); + thischannel.resolveparent(this); + if (!thischannel.parent) { + this.headchannels.push(thischannel); + } + this.calculateReorder(); + this.printServers(); + return thischannel; + } + createchannels(func = this.createChannel) { + let name = ""; + let category = 0; + const channelselect = new Dialog([ + "vdiv", + [ + "radio", + "select channel type", + ["voice", "text", "announcement"], + function (radio: string) { + console.log(radio); + category = + { text: 0, voice: 2, announcement: 5, category: 4 }[radio] || 0; + }, + 1, + ], + [ + "textbox", + "Name of channel", + "", + function (this: HTMLInputElement) { + name = this.value; + }, + ], + [ + "button", + "", + "submit", + function () { + console.log(name, category); + func(name, category); + channelselect.hide(); + }, + ], + ]); + channelselect.show(); + } + createcategory() { + let name = ""; + const category = 4; + const channelselect = new Dialog([ + "vdiv", + [ + "textbox", + "Name of category", + "", + function (this: HTMLInputElement) { + name = this.value; + }, + ], + [ + "button", + "", + "submit", + () => { + console.log(name, category); + this.createChannel(name, category); + channelselect.hide(); + }, + ], + ]); + channelselect.show(); + } + delChannel(json: channeljson) { + const channel = this.localuser.channelids.get(json.id); + this.localuser.channelids.delete(json.id); + if (!channel) return; + this.channels.splice(this.channels.indexOf(channel), 1); + const indexy = this.headchannels.indexOf(channel); + if (indexy !== -1) { + this.headchannels.splice(indexy, 1); + } - /* - const build=[]; - for(const thing of this.channels){ - console.log(thing.id); - if(thing!==channel){ - build.push(thing) - }else{ - console.log("fail"); - if(thing.parent){ - thing.parent.delChannel(json); - } - } - } - this.channels=build; - */ - this.printServers(); - } - createChannel(name: string, type: number) { - fetch(this.info.api + "/guilds/" + this.id + "/channels", { - method: "POST", - headers: this.headers, - body: JSON.stringify({ name, type }), - }); - } - async createRole(name: string) { - const fetched = await fetch( - this.info.api + "/guilds/" + this.id + "roles", - { - method: "POST", - headers: this.headers, - body: JSON.stringify({ - name, - color: 0, - permissions: "0", - }), - } - ); - const json = await fetched.json(); - const role = new Role(json, this); - this.roleids.set(role.id, role); - this.roles.push(role); - return role; - } - async updateRolePermissions(id: string, perms: Permissions) { - const role = this.roleids.get(id); - if (!role) { - return; - } - role.permissions.allow = perms.allow; - role.permissions.deny = perms.deny; + /* + const build=[]; + for(const thing of this.channels){ + console.log(thing.id); + if(thing!==channel){ + build.push(thing) + }else{ + console.log("fail"); + if(thing.parent){ + thing.parent.delChannel(json); + } + } + } + this.channels=build; + */ + this.printServers(); + } + createChannel(name: string, type: number) { + fetch(this.info.api + "/guilds/" + this.id + "/channels", { + method: "POST", + headers: this.headers, + body: JSON.stringify({ name, type }), + }); + } + async createRole(name: string) { + const fetched = await fetch( + this.info.api + "/guilds/" + this.id + "roles", + { + method: "POST", + headers: this.headers, + body: JSON.stringify({ + name, + color: 0, + permissions: "0", + }), + } + ); + const json = await fetched.json(); + const role = new Role(json, this); + this.roleids.set(role.id, role); + this.roles.push(role); + return role; + } + async updateRolePermissions(id: string, perms: Permissions) { + const role = this.roleids.get(id); + if (!role) { + return; + } + role.permissions.allow = perms.allow; + role.permissions.deny = perms.deny; - await fetch(this.info.api + "/guilds/" + this.id + "/roles/" + role.id, { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - color: role.color, - hoist: role.hoist, - icon: role.icon, - mentionable: role.mentionable, - name: role.name, - permissions: role.permissions.allow.toString(), - unicode_emoji: role.unicode_emoji, - }), - }); - } -} -Guild.setupcontextmenu(); -export { Guild }; + await fetch(this.info.api + "/guilds/" + this.id + "/roles/" + role.id, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + color: role.color, + hoist: role.hoist, + icon: role.icon, + mentionable: role.mentionable, + name: role.name, + permissions: role.permissions.allow.toString(), + unicode_emoji: role.unicode_emoji, + }), + }); + } + } + Guild.setupcontextmenu(); + export { Guild }; diff --git a/src/webpage/home.ts b/src/webpage/home.ts index 7301cd9..a60402b 100644 --- a/src/webpage/home.ts +++ b/src/webpage/home.ts @@ -3,87 +3,87 @@ console.log(mobile); const serverbox = document.getElementById("instancebox") as HTMLDivElement; fetch("/instances.json") - .then((_) => _.json()) - .then( - ( - json: { - name: string; - description?: string; - descriptionLong?: string; - image?: string; - url?: string; - display?: boolean; - online?: boolean; - uptime: { alltime: number; daytime: number; weektime: number }; - urls: { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login?: string; - }; - }[] - ) => { - console.warn(json); - for (const instance of json) { - if (instance.display === false) { - continue; - } - const div = document.createElement("div"); - div.classList.add("flexltr", "instance"); - if (instance.image) { - const img = document.createElement("img"); - img.src = instance.image; - div.append(img); - } - const statbox = document.createElement("div"); - statbox.classList.add("flexttb"); +.then((_) => _.json()) +.then( +( +json: { +name: string; +description?: string; +descriptionLong?: string; +image?: string; +url?: string; +display?: boolean; +online?: boolean; +uptime: { alltime: number; daytime: number; weektime: number }; +urls: { +wellknown: string; +api: string; +cdn: string; +gateway: string; +login?: string; +}; +}[] +) => { +console.warn(json); +for (const instance of json) { +if (instance.display === false) { +continue; +} +const div = document.createElement("div"); +div.classList.add("flexltr", "instance"); +if (instance.image) { +const img = document.createElement("img"); +img.src = instance.image; +div.append(img); +} +const statbox = document.createElement("div"); +statbox.classList.add("flexttb"); - { - const textbox = document.createElement("div"); - textbox.classList.add("flexttb", "instatancetextbox"); - const title = document.createElement("h2"); - title.innerText = instance.name; - if (instance.online !== undefined) { - const status = document.createElement("span"); - status.innerText = instance.online ? "Online" : "Offline"; - status.classList.add("instanceStatus"); - title.append(status); - } - textbox.append(title); - if (instance.description || instance.descriptionLong) { - const p = document.createElement("p"); - if (instance.descriptionLong) { - p.innerText = instance.descriptionLong; - } else if (instance.description) { - p.innerText = instance.description; - } - textbox.append(p); - } - statbox.append(textbox); - } - if (instance.uptime) { - const stats = document.createElement("div"); - stats.classList.add("flexltr"); - const span = document.createElement("span"); - span.innerText = `Uptime: All time: ${Math.round( - instance.uptime.alltime * 100 - )}% This week: ${Math.round( - instance.uptime.weektime * 100 - )}% Today: ${Math.round(instance.uptime.daytime * 100)}%`; - stats.append(span); - statbox.append(stats); - } - div.append(statbox); - div.onclick = (_) => { - if (instance.online) { - window.location.href = - "/register.html?instance=" + encodeURI(instance.name); - } else { - alert("Instance is offline, can't connect"); - } - }; - serverbox.append(div); - } - } - ); +{ +const textbox = document.createElement("div"); +textbox.classList.add("flexttb", "instatancetextbox"); +const title = document.createElement("h2"); +title.innerText = instance.name; +if (instance.online !== undefined) { +const status = document.createElement("span"); +status.innerText = instance.online ? "Online" : "Offline"; +status.classList.add("instanceStatus"); +title.append(status); +} +textbox.append(title); +if (instance.description || instance.descriptionLong) { +const p = document.createElement("p"); +if (instance.descriptionLong) { +p.innerText = instance.descriptionLong; +} else if (instance.description) { +p.innerText = instance.description; +} +textbox.append(p); +} +statbox.append(textbox); +} +if (instance.uptime) { +const stats = document.createElement("div"); +stats.classList.add("flexltr"); +const span = document.createElement("span"); +span.innerText = `Uptime: All time: ${Math.round( +instance.uptime.alltime * 100 +)}% This week: ${Math.round( +instance.uptime.weektime * 100 +)}% Today: ${Math.round(instance.uptime.daytime * 100)}%`; +stats.append(span); +statbox.append(stats); +} +div.append(statbox); +div.onclick = (_) => { +if (instance.online) { +window.location.href = +"/register.html?instance=" + encodeURI(instance.name); +} else { +alert("Instance is offline, can't connect"); +} +}; +serverbox.append(div); +} +} +); diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 18f2d40..d902d3b 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -6,254 +6,254 @@ import { Message } from "./message.js"; import { File } from "./file.js"; (async () => { - async function waitForLoad(): Promise { - return new Promise((resolve) => { - document.addEventListener("DOMContentLoaded", (_) => resolve()); - }); - } +async function waitForLoad(): Promise { + return new Promise((resolve) => { + document.addEventListener("DOMContentLoaded", (_) => resolve()); + }); + } - await waitForLoad(); + await waitForLoad(); - const users = getBulkUsers(); - if (!users.currentuser) { - window.location.href = "/login.html"; - return; - } + const users = getBulkUsers(); + if (!users.currentuser) { + window.location.href = "/login.html"; + return; + } - function showAccountSwitcher(): void { - const table = document.createElement("div"); - table.classList.add("accountSwitcher"); + function showAccountSwitcher(): void { + const table = document.createElement("div"); + table.classList.add("accountSwitcher"); - for (const user of Object.values(users.users)) { - const specialUser = user as Specialuser; - const userInfo = document.createElement("div"); - userInfo.classList.add("flexltr", "switchtable"); + for (const user of Object.values(users.users)) { + const specialUser = user as Specialuser; + const userInfo = document.createElement("div"); + userInfo.classList.add("flexltr", "switchtable"); - const pfp = document.createElement("img"); - pfp.src = specialUser.pfpsrc; - pfp.classList.add("pfp"); - userInfo.append(pfp); + const pfp = document.createElement("img"); + pfp.src = specialUser.pfpsrc; + pfp.classList.add("pfp"); + userInfo.append(pfp); - const userDiv = document.createElement("div"); - userDiv.classList.add("userinfo"); - userDiv.textContent = specialUser.username; - userDiv.append(document.createElement("br")); + const userDiv = document.createElement("div"); + userDiv.classList.add("userinfo"); + userDiv.textContent = specialUser.username; + userDiv.append(document.createElement("br")); - const span = document.createElement("span"); - span.textContent = specialUser.serverurls.wellknown - .replace("https://", "") - .replace("http://", ""); - span.classList.add("serverURL"); - userDiv.append(span); + const span = document.createElement("span"); + span.textContent = specialUser.serverurls.wellknown + .replace("https://", "") + .replace("http://", ""); + span.classList.add("serverURL"); + userDiv.append(span); - userInfo.append(userDiv); - table.append(userInfo); + userInfo.append(userDiv); + table.append(userInfo); - userInfo.addEventListener("click", () => { - thisUser.unload(); - thisUser.swapped = true; - const loading = document.getElementById("loading") as HTMLDivElement; - loading.classList.remove("doneloading"); - loading.classList.add("loading"); + userInfo.addEventListener("click", () => { + thisUser.unload(); + thisUser.swapped = true; + const loading = document.getElementById("loading") as HTMLDivElement; + loading.classList.remove("doneloading"); + loading.classList.add("loading"); - thisUser = new Localuser(specialUser); - users.currentuser = specialUser.uid; - localStorage.setItem("userinfos", JSON.stringify(users)); + thisUser = new Localuser(specialUser); + users.currentuser = specialUser.uid; + localStorage.setItem("userinfos", JSON.stringify(users)); - thisUser.initwebsocket().then(() => { - thisUser.loaduser(); - thisUser.init(); - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); + thisUser.initwebsocket().then(() => { + thisUser.loaduser(); + thisUser.init(); + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); - userInfo.remove(); - }); - } + userInfo.remove(); + }); + } - const switchAccountDiv = document.createElement("div"); - switchAccountDiv.classList.add("switchtable"); - switchAccountDiv.textContent = "Switch accounts ⇌"; - switchAccountDiv.addEventListener("click", () => { - window.location.href = "/login.html"; - }); - table.append(switchAccountDiv); + const switchAccountDiv = document.createElement("div"); + switchAccountDiv.classList.add("switchtable"); + switchAccountDiv.textContent = "Switch accounts ⇌"; + switchAccountDiv.addEventListener("click", () => { + window.location.href = "/login.html"; + }); + table.append(switchAccountDiv); - if (Contextmenu.currentmenu) { - Contextmenu.currentmenu.remove(); - } - Contextmenu.currentmenu = table; - document.body.append(table); - } + if (Contextmenu.currentmenu) { + Contextmenu.currentmenu.remove(); + } + Contextmenu.currentmenu = table; + document.body.append(table); + } - const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; - userInfoElement.addEventListener("click", (event) => { - event.stopImmediatePropagation(); - showAccountSwitcher(); - }); + const userInfoElement = document.getElementById("userinfo") as HTMLDivElement; + userInfoElement.addEventListener("click", (event) => { + event.stopImmediatePropagation(); + showAccountSwitcher(); + }); - const switchAccountsElement = document.getElementById( - "switchaccounts" - ) as HTMLDivElement; - switchAccountsElement.addEventListener("click", (event) => { - event.stopImmediatePropagation(); - showAccountSwitcher(); - }); + const switchAccountsElement = document.getElementById( + "switchaccounts" + ) as HTMLDivElement; + switchAccountsElement.addEventListener("click", (event) => { + event.stopImmediatePropagation(); + showAccountSwitcher(); + }); - let thisUser: Localuser; - try { - console.log(users.users, users.currentuser); - thisUser = new Localuser(users.users[users.currentuser]); - thisUser.initwebsocket().then(() => { - thisUser.loaduser(); - thisUser.init(); - const loading = document.getElementById("loading") as HTMLDivElement; - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); - } catch (e) { - console.error(e); - (document.getElementById("load-desc") as HTMLSpanElement).textContent = - "Account unable to start"; - thisUser = new Localuser(-1); - } + let thisUser: Localuser; + try { + console.log(users.users, users.currentuser); + thisUser = new Localuser(users.users[users.currentuser]); + thisUser.initwebsocket().then(() => { + thisUser.loaduser(); + thisUser.init(); + const loading = document.getElementById("loading") as HTMLDivElement; + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + } catch (e) { + console.error(e); + (document.getElementById("load-desc") as HTMLSpanElement).textContent = + "Account unable to start"; + thisUser = new Localuser(-1); + } - const menu = new Contextmenu("create rightclick"); - menu.addbutton( - "Create channel", - () => { - if (thisUser.lookingguild) { - thisUser.lookingguild.createchannels(); - } - }, - null, - () => thisUser.isAdmin() - ); + const menu = new Contextmenu("create rightclick"); + menu.addbutton( + "Create channel", + () => { + if (thisUser.lookingguild) { + thisUser.lookingguild.createchannels(); + } + }, + null, + () => thisUser.isAdmin() + ); - menu.addbutton( - "Create category", - () => { - if (thisUser.lookingguild) { - thisUser.lookingguild.createcategory(); - } - }, - null, - () => thisUser.isAdmin() - ); + menu.addbutton( + "Create category", + () => { + if (thisUser.lookingguild) { + thisUser.lookingguild.createcategory(); + } + }, + null, + () => thisUser.isAdmin() + ); - menu.bindContextmenu( - document.getElementById("channels") as HTMLDivElement, - 0, - 0 - ); + menu.bindContextmenu( + document.getElementById("channels") as HTMLDivElement, + 0, + 0 + ); - const pasteImageElement = document.getElementById( - "pasteimage" - ) as HTMLDivElement; - let replyingTo: Message | null = null; + const pasteImageElement = document.getElementById( + "pasteimage" + ) as HTMLDivElement; + let replyingTo: Message | null = null; - async function handleEnter(event: KeyboardEvent): Promise { - const channel = thisUser.channelfocus; - if (!channel) return; + async function handleEnter(event: KeyboardEvent): Promise { + const channel = thisUser.channelfocus; + if (!channel) return; - channel.typingstart(); + channel.typingstart(); - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); - if (channel.editing) { - channel.editing.edit(markdown.rawString); - channel.editing = null; - } else { - replyingTo = thisUser.channelfocus - ? thisUser.channelfocus.replyingto - : null; - if (replyingTo?.div) { - replyingTo.div.classList.remove("replying"); - } - if (thisUser.channelfocus) { - thisUser.channelfocus.replyingto = null; - } - channel.sendMessage(markdown.rawString, { - attachments: images, - // @ts-ignore This is valid according to the API - embeds: [], // Add an empty array for the embeds property - replyingto: replyingTo, - }); - if (thisUser.channelfocus) { - thisUser.channelfocus.makereplybox(); - } - } + if (channel.editing) { + channel.editing.edit(markdown.rawString); + channel.editing = null; + } else { + replyingTo = thisUser.channelfocus + ? thisUser.channelfocus.replyingto + : null; + if (replyingTo?.div) { + replyingTo.div.classList.remove("replying"); + } + if (thisUser.channelfocus) { + thisUser.channelfocus.replyingto = null; + } + channel.sendMessage(markdown.rawString, { + attachments: images, + // @ts-ignore This is valid according to the API + embeds: [], // Add an empty array for the embeds property + replyingto: replyingTo, + }); + if (thisUser.channelfocus) { + thisUser.channelfocus.makereplybox(); + } + } - while (images.length) { - images.pop(); - pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); - } + while (images.length) { + images.pop(); + pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); + } - typebox.innerHTML = ""; - } - } + typebox.innerHTML = ""; + } + } - interface CustomHTMLDivElement extends HTMLDivElement { - markdown: MarkDown; - } + interface CustomHTMLDivElement extends HTMLDivElement { + markdown: MarkDown; + } - const typebox = document.getElementById("typebox") as CustomHTMLDivElement; - const markdown = new MarkDown("", thisUser); - typebox.markdown = markdown; - typebox.addEventListener("keyup", handleEnter); - typebox.addEventListener("keydown", (event) => { - if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); - }); - markdown.giveBox(typebox); + const typebox = document.getElementById("typebox") as CustomHTMLDivElement; + const markdown = new MarkDown("", thisUser); + typebox.markdown = markdown; + typebox.addEventListener("keyup", handleEnter); + typebox.addEventListener("keydown", (event) => { + if (event.key === "Enter" && !event.shiftKey) event.preventDefault(); + }); + markdown.giveBox(typebox); - const images: Blob[] = []; - const imagesHtml: HTMLElement[] = []; + const images: Blob[] = []; + const imagesHtml: HTMLElement[] = []; - document.addEventListener("paste", async (e: ClipboardEvent) => { - if (!e.clipboardData) return; + document.addEventListener("paste", async (e: ClipboardEvent) => { + if (!e.clipboardData) return; - for (const file of Array.from(e.clipboardData.files)) { - const fileInstance = File.initFromBlob(file); - e.preventDefault(); - const html = fileInstance.upHTML(images, file); - pasteImageElement.appendChild(html); - images.push(file); - imagesHtml.push(html); - } - }); + for (const file of Array.from(e.clipboardData.files)) { + const fileInstance = File.initFromBlob(file); + e.preventDefault(); + const html = fileInstance.upHTML(images, file); + pasteImageElement.appendChild(html); + images.push(file); + imagesHtml.push(html); + } + }); - setTheme(); + setTheme(); - function userSettings(): void { - thisUser.showusersettings(); - } + function userSettings(): void { + thisUser.showusersettings(); + } - (document.getElementById("settings") as HTMLImageElement).onclick = - userSettings; + (document.getElementById("settings") as HTMLImageElement).onclick = + userSettings; - if (mobile) { - const channelWrapper = document.getElementById( - "channelw" - ) as HTMLDivElement; - channelWrapper.onclick = () => { - ( - document.getElementById("channels")!.parentNode as HTMLElement - ).classList.add("collapse"); - document.getElementById("servertd")!.classList.add("collapse"); - document.getElementById("servers")!.classList.add("collapse"); - }; + if (mobile) { + const channelWrapper = document.getElementById( + "channelw" + ) as HTMLDivElement; + channelWrapper.onclick = () => { + ( + document.getElementById("channels")!.parentNode as HTMLElement + ).classList.add("collapse"); + document.getElementById("servertd")!.classList.add("collapse"); + document.getElementById("servers")!.classList.add("collapse"); + }; - const mobileBack = document.getElementById("mobileback") as HTMLDivElement; - mobileBack.textContent = "#"; - mobileBack.onclick = () => { - ( - document.getElementById("channels")!.parentNode as HTMLElement - ).classList.remove("collapse"); - document.getElementById("servertd")!.classList.remove("collapse"); - document.getElementById("servers")!.classList.remove("collapse"); - }; - } -})(); + const mobileBack = document.getElementById("mobileback") as HTMLDivElement; + mobileBack.textContent = "#"; + mobileBack.onclick = () => { + ( + document.getElementById("channels")!.parentNode as HTMLElement + ).classList.remove("collapse"); + document.getElementById("servertd")!.classList.remove("collapse"); + document.getElementById("servers")!.classList.remove("collapse"); + }; + } + })(); diff --git a/src/webpage/infiniteScroller.ts b/src/webpage/infiniteScroller.ts index 0020cb6..2cd2f18 100644 --- a/src/webpage/infiniteScroller.ts +++ b/src/webpage/infiniteScroller.ts @@ -1,323 +1,323 @@ class InfiniteScroller { - readonly getIDFromOffset: ( - ID: string, - offset: number - ) => Promise; - readonly getHTMLFromID: (ID: string) => Promise; - readonly destroyFromID: (ID: string) => Promise; - readonly reachesBottom: () => void; - private readonly minDist = 2000; - private readonly fillDist = 3000; - private readonly maxDist = 6000; - HTMLElements: [HTMLElement, string][] = []; - div: HTMLDivElement | null = null; - timeout: NodeJS.Timeout | null = null; - beenloaded = false; - scrollBottom = 0; - scrollTop = 0; - needsupdate = true; - averageheight = 60; - watchtime = false; - changePromise: Promise | undefined; - scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; +readonly getIDFromOffset: ( +ID: string, +offset: number +) => Promise; + readonly getHTMLFromID: (ID: string) => Promise; + readonly destroyFromID: (ID: string) => Promise; + readonly reachesBottom: () => void; + private readonly minDist = 2000; + private readonly fillDist = 3000; + private readonly maxDist = 6000; + HTMLElements: [HTMLElement, string][] = []; + div: HTMLDivElement | null = null; + timeout: NodeJS.Timeout | null = null; + beenloaded = false; + scrollBottom = 0; + scrollTop = 0; + needsupdate = true; + averageheight = 60; + watchtime = false; + changePromise: Promise | undefined; + scollDiv!: { scrollTop: number; scrollHeight: number; clientHeight: number }; - constructor( - getIDFromOffset: InfiniteScroller["getIDFromOffset"], - getHTMLFromID: InfiniteScroller["getHTMLFromID"], - destroyFromID: InfiniteScroller["destroyFromID"], - reachesBottom: InfiniteScroller["reachesBottom"] = () => {} - ) { - this.getIDFromOffset = getIDFromOffset; - this.getHTMLFromID = getHTMLFromID; - this.destroyFromID = destroyFromID; - this.reachesBottom = reachesBottom; - } + constructor( + getIDFromOffset: InfiniteScroller["getIDFromOffset"], + getHTMLFromID: InfiniteScroller["getHTMLFromID"], + destroyFromID: InfiniteScroller["destroyFromID"], + reachesBottom: InfiniteScroller["reachesBottom"] = () => {} + ) { + this.getIDFromOffset = getIDFromOffset; + this.getHTMLFromID = getHTMLFromID; + this.destroyFromID = destroyFromID; + this.reachesBottom = reachesBottom; + } - async getDiv(initialId: string): Promise { - if (this.div) { - throw new Error("Div already exists, exiting."); - } + async getDiv(initialId: string): Promise { + if (this.div) { + throw new Error("Div already exists, exiting."); + } - const scroll = document.createElement("div"); - scroll.classList.add("flexttb", "scroller"); - this.div = scroll; + const scroll = document.createElement("div"); + scroll.classList.add("flexttb", "scroller"); + this.div = scroll; - this.div.addEventListener("scroll", () => { - this.checkscroll(); - if (this.scrollBottom < 5) { - this.scrollBottom = 5; - } - if (this.timeout === null) { - this.timeout = setTimeout(this.updatestuff.bind(this), 300); - } - this.watchForChange(); - }); + this.div.addEventListener("scroll", () => { + this.checkscroll(); + if (this.scrollBottom < 5) { + this.scrollBottom = 5; + } + if (this.timeout === null) { + this.timeout = setTimeout(this.updatestuff.bind(this), 300); + } + this.watchForChange(); + }); - let oldheight = 0; - new ResizeObserver(() => { - this.checkscroll(); - const func = this.snapBottom(); - this.updatestuff(); - const change = oldheight - scroll.offsetHeight; - if (change > 0 && this.div) { - this.div.scrollTop += change; - } - oldheight = scroll.offsetHeight; - this.watchForChange(); - func(); - }).observe(scroll); + let oldheight = 0; + new ResizeObserver(() => { + this.checkscroll(); + const func = this.snapBottom(); + this.updatestuff(); + const change = oldheight - scroll.offsetHeight; + if (change > 0 && this.div) { + this.div.scrollTop += change; + } + oldheight = scroll.offsetHeight; + this.watchForChange(); + func(); + }).observe(scroll); - new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); + new ResizeObserver(this.watchForChange.bind(this)).observe(scroll); - await this.firstElement(initialId); - this.updatestuff(); - await this.watchForChange().then(() => { - this.updatestuff(); - this.beenloaded = true; - }); + await this.firstElement(initialId); + this.updatestuff(); + await this.watchForChange().then(() => { + this.updatestuff(); + this.beenloaded = true; + }); - return scroll; - } + return scroll; + } - checkscroll(): void { - if (this.beenloaded && this.div && !document.body.contains(this.div)) { - console.warn("not in document"); - this.div = null; - } - } + checkscroll(): void { + if (this.beenloaded && this.div && !document.body.contains(this.div)) { + console.warn("not in document"); + this.div = null; + } + } - async updatestuff(): Promise { - this.timeout = null; - if (!this.div) return; + async updatestuff(): Promise { + this.timeout = null; + if (!this.div) return; - this.scrollBottom = - this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight; - this.averageheight = this.div.scrollHeight / this.HTMLElements.length; - if (this.averageheight < 10) { - this.averageheight = 60; - } - this.scrollTop = this.div.scrollTop; + this.scrollBottom = + this.div.scrollHeight - this.div.scrollTop - this.div.clientHeight; + this.averageheight = this.div.scrollHeight / this.HTMLElements.length; + if (this.averageheight < 10) { + this.averageheight = 60; + } + this.scrollTop = this.div.scrollTop; - if (!this.scrollBottom && !(await this.watchForChange())) { - this.reachesBottom(); - } - if (!this.scrollTop) { - await this.watchForChange(); - } - this.needsupdate = false; - } + if (!this.scrollBottom && !(await this.watchForChange())) { + this.reachesBottom(); + } + if (!this.scrollTop) { + await this.watchForChange(); + } + this.needsupdate = false; + } - async firstElement(id: string): Promise { - if (!this.div) return; - const html = await this.getHTMLFromID(id); - this.div.appendChild(html); - this.HTMLElements.push([html, id]); - } + async firstElement(id: string): Promise { + if (!this.div) return; + const html = await this.getHTMLFromID(id); + this.div.appendChild(html); + this.HTMLElements.push([html, id]); + } - async addedBottom(): Promise { - await this.updatestuff(); - const func = this.snapBottom(); - await this.watchForChange(); - func(); - } + async addedBottom(): Promise { + await this.updatestuff(); + const func = this.snapBottom(); + await this.watchForChange(); + func(); + } - snapBottom(): () => void { - const scrollBottom = this.scrollBottom; - return () => { - if (this.div && scrollBottom < 4) { - this.div.scrollTop = this.div.scrollHeight; - } - }; - } + snapBottom(): () => void { + const scrollBottom = this.scrollBottom; + return () => { + if (this.div && scrollBottom < 4) { + this.div.scrollTop = this.div.scrollHeight; + } + }; + } - private async watchForTop( - already = false, - fragment = new DocumentFragment() - ): Promise { - if (!this.div) return false; - try { - let again = false; - if (this.scrollTop < (already ? this.fillDist : this.minDist)) { - let nextid: string | undefined; - const firstelm = this.HTMLElements.at(0); - if (firstelm) { - const previd = firstelm[1]; - nextid = await this.getIDFromOffset(previd, 1); - } + private async watchForTop( + already = false, + fragment = new DocumentFragment() + ): Promise { + if (!this.div) return false; + try { + let again = false; + if (this.scrollTop < (already ? this.fillDist : this.minDist)) { + let nextid: string | undefined; + const firstelm = this.HTMLElements.at(0); + if (firstelm) { + const previd = firstelm[1]; + nextid = await this.getIDFromOffset(previd, 1); + } - if (nextid) { - const html = await this.getHTMLFromID(nextid); - if (!html) { - this.destroyFromID(nextid); - return false; - } - again = true; - fragment.prepend(html); - this.HTMLElements.unshift([html, nextid]); - this.scrollTop += this.averageheight; - } - } - if (this.scrollTop > this.maxDist) { - const html = this.HTMLElements.shift(); - if (html) { - again = true; - await this.destroyFromID(html[1]); - this.scrollTop -= this.averageheight; - } - } - if (again) { - await this.watchForTop(true, fragment); - } - return again; - } finally { - if (!already) { - if (this.div.scrollTop === 0) { - this.scrollTop = 1; - this.div.scrollTop = 10; - } - this.div.prepend(fragment, fragment); - } - } - } + if (nextid) { + const html = await this.getHTMLFromID(nextid); + if (!html) { + this.destroyFromID(nextid); + return false; + } + again = true; + fragment.prepend(html); + this.HTMLElements.unshift([html, nextid]); + this.scrollTop += this.averageheight; + } + } + if (this.scrollTop > this.maxDist) { + const html = this.HTMLElements.shift(); + if (html) { + again = true; + await this.destroyFromID(html[1]); + this.scrollTop -= this.averageheight; + } + } + if (again) { + await this.watchForTop(true, fragment); + } + return again; + } finally { + if (!already) { + if (this.div.scrollTop === 0) { + this.scrollTop = 1; + this.div.scrollTop = 10; + } + this.div.prepend(fragment, fragment); + } + } + } - async watchForBottom( - already = false, - fragment = new DocumentFragment() - ): Promise { - let func: Function | undefined; - if (!already) func = this.snapBottom(); - if (!this.div) return false; - try { - let again = false; - const scrollBottom = this.scrollBottom; - if (scrollBottom < (already ? this.fillDist : this.minDist)) { - let nextid: string | undefined; - const lastelm = this.HTMLElements.at(-1); - if (lastelm) { - const previd = lastelm[1]; - nextid = await this.getIDFromOffset(previd, -1); - } - if (nextid) { - again = true; - const html = await this.getHTMLFromID(nextid); - fragment.appendChild(html); - this.HTMLElements.push([html, nextid]); - this.scrollBottom += this.averageheight; - } - } - if (scrollBottom > this.maxDist) { - const html = this.HTMLElements.pop(); - if (html) { - await this.destroyFromID(html[1]); - this.scrollBottom -= this.averageheight; - again = true; - } - } - if (again) { - await this.watchForBottom(true, fragment); - } - return again; - } finally { - if (!already) { - this.div.append(fragment); - if (func) { - func(); - } - } - } - } + async watchForBottom( + already = false, + fragment = new DocumentFragment() + ): Promise { + let func: Function | undefined; + if (!already) func = this.snapBottom(); + if (!this.div) return false; + try { + let again = false; + const scrollBottom = this.scrollBottom; + if (scrollBottom < (already ? this.fillDist : this.minDist)) { + let nextid: string | undefined; + const lastelm = this.HTMLElements.at(-1); + if (lastelm) { + const previd = lastelm[1]; + nextid = await this.getIDFromOffset(previd, -1); + } + if (nextid) { + again = true; + const html = await this.getHTMLFromID(nextid); + fragment.appendChild(html); + this.HTMLElements.push([html, nextid]); + this.scrollBottom += this.averageheight; + } + } + if (scrollBottom > this.maxDist) { + const html = this.HTMLElements.pop(); + if (html) { + await this.destroyFromID(html[1]); + this.scrollBottom -= this.averageheight; + again = true; + } + } + if (again) { + await this.watchForBottom(true, fragment); + } + return again; + } finally { + if (!already) { + this.div.append(fragment); + if (func) { + func(); + } + } + } + } - async watchForChange(): Promise { - if (this.changePromise) { - this.watchtime = true; - return await this.changePromise; - } else { - this.watchtime = false; - } + async watchForChange(): Promise { + if (this.changePromise) { + this.watchtime = true; + return await this.changePromise; + } else { + this.watchtime = false; + } - this.changePromise = new Promise(async (res) => { - try { - if (!this.div) { - res(false); - return false; - } - const out = (await Promise.allSettled([ - this.watchForTop(), - this.watchForBottom(), - ])) as { value: boolean }[]; - const changed = out[0].value || out[1].value; - if (this.timeout === null && changed) { - this.timeout = setTimeout(this.updatestuff.bind(this), 300); - } - res(Boolean(changed)); - return Boolean(changed); - } catch (e) { - console.error(e); - res(false); - return false; - } finally { - setTimeout(() => { - this.changePromise = undefined; - if (this.watchtime) { - this.watchForChange(); - } - }, 300); - } - }); + this.changePromise = new Promise(async (res) => { + try { + if (!this.div) { + res(false); + return false; + } + const out = (await Promise.allSettled([ + this.watchForTop(), + this.watchForBottom(), + ])) as { value: boolean }[]; + const changed = out[0].value || out[1].value; + if (this.timeout === null && changed) { + this.timeout = setTimeout(this.updatestuff.bind(this), 300); + } + res(Boolean(changed)); + return Boolean(changed); + } catch (e) { + console.error(e); + res(false); + return false; + } finally { + setTimeout(() => { + this.changePromise = undefined; + if (this.watchtime) { + this.watchForChange(); + } + }, 300); + } + }); - return await this.changePromise; - } + return await this.changePromise; + } - async focus(id: string, flash = true): Promise { - let element: HTMLElement | undefined; - for (const thing of this.HTMLElements) { - if (thing[1] === id) { - element = thing[0]; - } - } - if (element) { - if (flash) { - element.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - await new Promise((resolve) => setTimeout(resolve, 1000)); - element.classList.remove("jumped"); - await new Promise((resolve) => setTimeout(resolve, 100)); - element.classList.add("jumped"); - } else { - element.scrollIntoView(); - } - } else { - for (const thing of this.HTMLElements) { - await this.destroyFromID(thing[1]); - } - this.HTMLElements = []; - await this.firstElement(id); - this.updatestuff(); - await this.watchForChange(); - await new Promise((resolve) => setTimeout(resolve, 100)); - await this.focus(id, true); - } - } + async focus(id: string, flash = true): Promise { + let element: HTMLElement | undefined; + for (const thing of this.HTMLElements) { + if (thing[1] === id) { + element = thing[0]; + } + } + if (element) { + if (flash) { + element.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + element.classList.remove("jumped"); + await new Promise((resolve) => setTimeout(resolve, 100)); + element.classList.add("jumped"); + } else { + element.scrollIntoView(); + } + } else { + for (const thing of this.HTMLElements) { + await this.destroyFromID(thing[1]); + } + this.HTMLElements = []; + await this.firstElement(id); + this.updatestuff(); + await this.watchForChange(); + await new Promise((resolve) => setTimeout(resolve, 100)); + await this.focus(id, true); + } + } - async delete(): Promise { - if (this.div) { - this.div.remove(); - this.div = null; - } - try { - for (const thing of this.HTMLElements) { - await this.destroyFromID(thing[1]); - } - } catch (e) { - console.error(e); - } - this.HTMLElements = []; - if (this.timeout) { - clearTimeout(this.timeout); - } - } -} + async delete(): Promise { + if (this.div) { + this.div.remove(); + this.div = null; + } + try { + for (const thing of this.HTMLElements) { + await this.destroyFromID(thing[1]); + } + } catch (e) { + console.error(e); + } + this.HTMLElements = []; + if (this.timeout) { + clearTimeout(this.timeout); + } + } + } -export { InfiniteScroller }; + export { InfiniteScroller }; diff --git a/src/webpage/invite.ts b/src/webpage/invite.ts index 331fcc0..4dfc19e 100644 --- a/src/webpage/invite.ts +++ b/src/webpage/invite.ts @@ -1,147 +1,147 @@ import { getBulkUsers, Specialuser, getapiurls } from "./login.js"; (async () => { - const users = getBulkUsers(); - const well = new URLSearchParams(window.location.search).get("instance"); - const joinable: Specialuser[] = []; +const users = getBulkUsers(); +const well = new URLSearchParams(window.location.search).get("instance"); +const joinable: Specialuser[] = []; - for (const key in users.users) { - if (Object.prototype.hasOwnProperty.call(users.users, key)) { - const user: Specialuser = users.users[key]; - if (well && user.serverurls.wellknown.includes(well)) { - joinable.push(user); - } - console.log(user); - } - } +for (const key in users.users) { +if (Object.prototype.hasOwnProperty.call(users.users, key)) { +const user: Specialuser = users.users[key]; +if (well && user.serverurls.wellknown.includes(well)) { +joinable.push(user); +} +console.log(user); +} +} - let urls: { api: string; cdn: string } | undefined; +let urls: { api: string; cdn: string } | undefined; - if (!joinable.length && well) { - const out = await getapiurls(well); - if (out) { - urls = out; - for (const key in users.users) { - if (Object.prototype.hasOwnProperty.call(users.users, key)) { - const user: Specialuser = users.users[key]; - if (user.serverurls.api.includes(out.api)) { - joinable.push(user); - } - console.log(user); - } - } - } else { - throw new Error( - "Someone needs to handle the case where the servers don't exist" - ); - } - } else { - urls = joinable[0].serverurls; - } +if (!joinable.length && well) { +const out = await getapiurls(well); +if (out) { +urls = out; +for (const key in users.users) { +if (Object.prototype.hasOwnProperty.call(users.users, key)) { +const user: Specialuser = users.users[key]; +if (user.serverurls.api.includes(out.api)) { +joinable.push(user); +} +console.log(user); +} +} +} else { +throw new Error( +"Someone needs to handle the case where the servers don't exist" +); +} +} else { +urls = joinable[0].serverurls; +} - if (!joinable.length) { - document.getElementById("AcceptInvite")!.textContent = - "Create an account to accept the invite"; - } +if (!joinable.length) { +document.getElementById("AcceptInvite")!.textContent = +"Create an account to accept the invite"; +} - const code = window.location.pathname.split("/")[2]; - let guildinfo: any; +const code = window.location.pathname.split("/")[2]; +let guildinfo: any; - fetch(`${urls!.api}/invites/${code}`, { - method: "GET", - }) - .then((response) => response.json()) - .then((json) => { - const guildjson = json.guild; - guildinfo = guildjson; - document.getElementById("invitename")!.textContent = guildjson.name; - document.getElementById( - "invitedescription" - )!.textContent = `${json.inviter.username} invited you to join ${guildjson.name}`; - if (guildjson.icon) { - const img = document.createElement("img"); - img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; - img.classList.add("inviteGuild"); - document.getElementById("inviteimg")!.append(img); - } else { - const txt = guildjson.name - .replace(/'s /g, " ") - .replace(/\w+/g, (word: any[]) => word[0]) - .replace(/\s/g, ""); - const div = document.createElement("div"); - div.textContent = txt; - div.classList.add("inviteGuild"); - document.getElementById("inviteimg")!.append(div); - } - }); +fetch(`${urls!.api}/invites/${code}`, { +method: "GET", +}) +.then((response) => response.json()) +.then((json) => { +const guildjson = json.guild; +guildinfo = guildjson; +document.getElementById("invitename")!.textContent = guildjson.name; +document.getElementById( +"invitedescription" +)!.textContent = `${json.inviter.username} invited you to join ${guildjson.name}`; +if (guildjson.icon) { +const img = document.createElement("img"); +img.src = `${urls!.cdn}/icons/${guildjson.id}/${guildjson.icon}.png`; +img.classList.add("inviteGuild"); +document.getElementById("inviteimg")!.append(img); +} else { +const txt = guildjson.name +.replace(/'s /g, " ") +.replace(/\w+/g, (word: any[]) => word[0]) +.replace(/\s/g, ""); +const div = document.createElement("div"); +div.textContent = txt; +div.classList.add("inviteGuild"); +document.getElementById("inviteimg")!.append(div); +} +}); - function showAccounts(): void { - const table = document.createElement("dialog"); - for (const user of joinable) { - console.log(user.pfpsrc); +function showAccounts(): void { +const table = document.createElement("dialog"); +for (const user of joinable) { +console.log(user.pfpsrc); - const userinfo = document.createElement("div"); - userinfo.classList.add("flexltr", "switchtable"); +const userinfo = document.createElement("div"); +userinfo.classList.add("flexltr", "switchtable"); - const pfp = document.createElement("img"); - pfp.src = user.pfpsrc; - pfp.classList.add("pfp"); - userinfo.append(pfp); +const pfp = document.createElement("img"); +pfp.src = user.pfpsrc; +pfp.classList.add("pfp"); +userinfo.append(pfp); - const userDiv = document.createElement("div"); - userDiv.classList.add("userinfo"); - userDiv.textContent = user.username; - userDiv.append(document.createElement("br")); +const userDiv = document.createElement("div"); +userDiv.classList.add("userinfo"); +userDiv.textContent = user.username; +userDiv.append(document.createElement("br")); - const span = document.createElement("span"); - span.textContent = user.serverurls.wellknown - .replace("https://", "") - .replace("http://", ""); - span.classList.add("serverURL"); - userDiv.append(span); +const span = document.createElement("span"); +span.textContent = user.serverurls.wellknown +.replace("https://", "") +.replace("http://", ""); +span.classList.add("serverURL"); +userDiv.append(span); - userinfo.append(userDiv); - table.append(userinfo); +userinfo.append(userDiv); +table.append(userinfo); - userinfo.addEventListener("click", () => { - console.log(user); - fetch(`${urls!.api}/invites/${code}`, { - method: "POST", - headers: { - Authorization: user.token, - }, - }).then(() => { - users.currentuser = user.uid; - localStorage.setItem("userinfos", JSON.stringify(users)); - window.location.href = "/channels/" + guildinfo.id; - }); - }); - } +userinfo.addEventListener("click", () => { +console.log(user); +fetch(`${urls!.api}/invites/${code}`, { +method: "POST", +headers: { +Authorization: user.token, +}, +}).then(() => { +users.currentuser = user.uid; +localStorage.setItem("userinfos", JSON.stringify(users)); +window.location.href = "/channels/" + guildinfo.id; +}); +}); +} - const td = document.createElement("div"); - td.classList.add("switchtable"); - td.textContent = "Login or create an account ⇌"; - td.addEventListener("click", () => { - const l = new URLSearchParams("?"); - l.set("goback", window.location.href); - l.set("instance", well!); - window.location.href = "/login?" + l.toString(); - }); +const td = document.createElement("div"); +td.classList.add("switchtable"); +td.textContent = "Login or create an account ⇌"; +td.addEventListener("click", () => { +const l = new URLSearchParams("?"); +l.set("goback", window.location.href); +l.set("instance", well!); +window.location.href = "/login?" + l.toString(); +}); - if (!joinable.length) { - const l = new URLSearchParams("?"); - l.set("goback", window.location.href); - l.set("instance", well!); - window.location.href = "/login?" + l.toString(); - } +if (!joinable.length) { +const l = new URLSearchParams("?"); +l.set("goback", window.location.href); +l.set("instance", well!); +window.location.href = "/login?" + l.toString(); +} - table.append(td); - table.classList.add("accountSwitcher"); - console.log(table); - document.body.append(table); - } +table.append(td); +table.classList.add("accountSwitcher"); +console.log(table); +document.body.append(table); +} - document - .getElementById("AcceptInvite")! - .addEventListener("click", showAccounts); +document +.getElementById("AcceptInvite")! +.addEventListener("click", showAccounts); })(); diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts index 1bbeffc..6438ff1 100644 --- a/src/webpage/jsontypes.ts +++ b/src/webpage/jsontypes.ts @@ -1,501 +1,501 @@ type readyjson = { - op: 0; - t: "READY"; - s: number; - d: { - v: number; - user: mainuserjson; - user_settings: { - index: number; - afk_timeout: number; - allow_accessibility_detection: boolean; - animate_emoji: boolean; - animate_stickers: number; - contact_sync_enabled: boolean; - convert_emoticons: boolean; - custom_status: string; - default_guilds_restricted: boolean; - detect_platform_accounts: boolean; - developer_mode: boolean; - disable_games_tab: boolean; - enable_tts_command: boolean; - explicit_content_filter: 0; - friend_discovery_flags: 0; - friend_source_flags: { - all: boolean; - }; //might be missing things here - gateway_connected: boolean; - gif_auto_play: boolean; - guild_folders: []; //need an example of this not empty - guild_positions: []; //need an example of this not empty - inline_attachment_media: boolean; - inline_embed_media: boolean; - locale: string; - message_display_compact: boolean; - native_phone_integration_enabled: boolean; - render_embeds: boolean; - render_reactions: boolean; - restricted_guilds: []; //need an example of this not empty - show_current_game: boolean; - status: string; - stream_notifications_enabled: boolean; - theme: string; - timezone_offset: number; - view_nsfw_guilds: boolean; - }; - guilds: guildjson[]; - relationships: { - id: string; - type: 0 | 1 | 2 | 3 | 4; - nickname: string | null; - user: userjson; - }[]; - read_state: { - entries: { - id: string; - channel_id: string; - last_message_id: string; - last_pin_timestamp: string; - mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware - }[]; - partial: boolean; - version: number; - }; - user_guild_settings: { - entries: { - channel_overrides: unknown[]; //will have to find example - message_notifications: number; - flags: number; - hide_muted_channels: boolean; - mobile_push: boolean; - mute_config: null; - mute_scheduled_events: boolean; - muted: boolean; - notify_highlights: number; - suppress_everyone: boolean; - suppress_roles: boolean; - version: number; - guild_id: string; - }[]; - partial: boolean; - version: number; - }; - private_channels: dirrectjson[]; - session_id: string; - country_code: string; - users: userjson[]; - merged_members: [memberjson][]; - sessions: { - active: boolean; - activities: []; //will need to find example of this - client_info: { - version: number; - }; - session_id: string; - status: string; - }[]; - resume_gateway_url: string; - consents: { - personalization: { - consented: boolean; - }; - }; - experiments: []; //not sure if I need to do this :P - guild_join_requests: []; //need to get examples - connected_accounts: []; //need to get examples - guild_experiments: []; //need to get examples - geo_ordered_rtc_regions: []; //need to get examples - api_code_version: number; - friend_suggestion_count: number; - analytics_token: string; - tutorial: boolean; - session_type: string; - auth_session_id_hash: string; - notification_settings: { - flags: number; - }; - }; +op: 0; +t: "READY"; +s: number; +d: { +v: number; +user: mainuserjson; +user_settings: { +index: number; +afk_timeout: number; +allow_accessibility_detection: boolean; +animate_emoji: boolean; +animate_stickers: number; +contact_sync_enabled: boolean; +convert_emoticons: boolean; +custom_status: string; +default_guilds_restricted: boolean; +detect_platform_accounts: boolean; +developer_mode: boolean; +disable_games_tab: boolean; +enable_tts_command: boolean; +explicit_content_filter: 0; +friend_discovery_flags: 0; +friend_source_flags: { +all: boolean; +}; //might be missing things here +gateway_connected: boolean; +gif_auto_play: boolean; +guild_folders: []; //need an example of this not empty +guild_positions: []; //need an example of this not empty +inline_attachment_media: boolean; +inline_embed_media: boolean; +locale: string; +message_display_compact: boolean; +native_phone_integration_enabled: boolean; +render_embeds: boolean; +render_reactions: boolean; +restricted_guilds: []; //need an example of this not empty +show_current_game: boolean; +status: string; +stream_notifications_enabled: boolean; +theme: string; +timezone_offset: number; +view_nsfw_guilds: boolean; +}; +guilds: guildjson[]; +relationships: { +id: string; +type: 0 | 1 | 2 | 3 | 4; +nickname: string | null; +user: userjson; +}[]; +read_state: { +entries: { +id: string; +channel_id: string; +last_message_id: string; +last_pin_timestamp: string; +mention_count: number; //in theory, the server doesn't actually send this as far as I'm aware +}[]; +partial: boolean; +version: number; +}; +user_guild_settings: { +entries: { +channel_overrides: unknown[]; //will have to find example +message_notifications: number; +flags: number; +hide_muted_channels: boolean; +mobile_push: boolean; +mute_config: null; +mute_scheduled_events: boolean; +muted: boolean; +notify_highlights: number; +suppress_everyone: boolean; +suppress_roles: boolean; +version: number; +guild_id: string; +}[]; +partial: boolean; +version: number; +}; +private_channels: dirrectjson[]; +session_id: string; +country_code: string; +users: userjson[]; +merged_members: [memberjson][]; +sessions: { +active: boolean; +activities: []; //will need to find example of this +client_info: { +version: number; +}; +session_id: string; +status: string; +}[]; +resume_gateway_url: string; +consents: { +personalization: { +consented: boolean; +}; +}; +experiments: []; //not sure if I need to do this :P +guild_join_requests: []; //need to get examples +connected_accounts: []; //need to get examples +guild_experiments: []; //need to get examples +geo_ordered_rtc_regions: []; //need to get examples +api_code_version: number; +friend_suggestion_count: number; +analytics_token: string; +tutorial: boolean; +session_type: string; +auth_session_id_hash: string; +notification_settings: { +flags: number; +}; +}; }; type mainuserjson = userjson & { - flags: number; - mfa_enabled?: boolean; - email?: string; - phone?: string; - verified: boolean; - nsfw_allowed: boolean; - premium: boolean; - purchased_flags: number; - premium_usage_flags: number; - disabled: boolean; +flags: number; +mfa_enabled?: boolean; +email?: string; +phone?: string; +verified: boolean; +nsfw_allowed: boolean; +premium: boolean; +purchased_flags: number; +premium_usage_flags: number; +disabled: boolean; }; type userjson = { - username: string; - discriminator: string; - id: string; - public_flags: number; - avatar: string | null; - accent_color: number; - banner?: string; - bio: string; - bot: boolean; - premium_since: string; - premium_type: number; - theme_colors: string; - pronouns: string; - badge_ids: string[]; +username: string; +discriminator: string; +id: string; +public_flags: number; +avatar: string | null; +accent_color: number; +banner?: string; +bio: string; +bot: boolean; +premium_since: string; +premium_type: number; +theme_colors: string; +pronouns: string; +badge_ids: string[]; }; type memberjson = { - index?: number; - id: string; - user: userjson | null; - guild_id: string; - guild: { - id: string; - } | null; - nick?: string; - roles: string[]; - joined_at: string; - premium_since: string; - deaf: boolean; - mute: boolean; - pending: boolean; - last_message_id?: boolean; //What??? +index?: number; +id: string; +user: userjson | null; +guild_id: string; +guild: { +id: string; +} | null; +nick?: string; +roles: string[]; +joined_at: string; +premium_since: string; +deaf: boolean; +mute: boolean; +pending: boolean; +last_message_id?: boolean; //What??? }; type emojijson = { - name: string; - id?: string; - animated?: boolean; +name: string; +id?: string; +animated?: boolean; }; type guildjson = { - application_command_counts: { [key: string]: number }; - channels: channeljson[]; - data_mode: string; - emojis: emojijson[]; - guild_scheduled_events: []; - id: string; - large: boolean; - lazy: boolean; - member_count: number; - premium_subscription_count: number; - properties: { - region: string | null; - name: string; - description: string; - icon: string; - splash: string; - banner: string; - features: string[]; - preferred_locale: string; - owner_id: string; - application_id: string; - afk_channel_id: string; - afk_timeout: number; - member_count: number; - system_channel_id: string; - verification_level: number; - explicit_content_filter: number; - default_message_notifications: number; - mfa_level: number; - vanity_url_code: number; - premium_tier: number; - premium_progress_bar_enabled: boolean; - system_channel_flags: number; - discovery_splash: string; - rules_channel_id: string; - public_updates_channel_id: string; - max_video_channel_users: number; - max_members: number; - nsfw_level: number; - hub_type: null; - home_header: null; - id: string; - latest_onboarding_question_id: string; - max_stage_video_channel_users: number; - nsfw: boolean; - safety_alerts_channel_id: string; - }; - roles: rolesjson[]; - stage_instances: []; - stickers: []; - threads: []; - version: string; - guild_hashes: {}; - joined_at: string; +application_command_counts: { [key: string]: number }; +channels: channeljson[]; +data_mode: string; +emojis: emojijson[]; +guild_scheduled_events: []; +id: string; +large: boolean; +lazy: boolean; +member_count: number; +premium_subscription_count: number; +properties: { +region: string | null; +name: string; +description: string; +icon: string; +splash: string; +banner: string; +features: string[]; +preferred_locale: string; +owner_id: string; +application_id: string; +afk_channel_id: string; +afk_timeout: number; +member_count: number; +system_channel_id: string; +verification_level: number; +explicit_content_filter: number; +default_message_notifications: number; +mfa_level: number; +vanity_url_code: number; +premium_tier: number; +premium_progress_bar_enabled: boolean; +system_channel_flags: number; +discovery_splash: string; +rules_channel_id: string; +public_updates_channel_id: string; +max_video_channel_users: number; +max_members: number; +nsfw_level: number; +hub_type: null; +home_header: null; +id: string; +latest_onboarding_question_id: string; +max_stage_video_channel_users: number; +nsfw: boolean; +safety_alerts_channel_id: string; +}; +roles: rolesjson[]; +stage_instances: []; +stickers: []; +threads: []; +version: string; +guild_hashes: {}; +joined_at: string; }; type startTypingjson = { - d: { - channel_id: string; - guild_id?: string; - user_id: string; - timestamp: number; - member?: memberjson; - }; +d: { +channel_id: string; +guild_id?: string; +user_id: string; +timestamp: number; +member?: memberjson; +}; }; type channeljson = { - id: string; - created_at: string; - name: string; - icon: string; - type: number; - last_message_id: string; - guild_id: string; - parent_id: string; - last_pin_timestamp: string; - default_auto_archive_duration: number; - permission_overwrites: { - id: string; - allow: string; - deny: string; - }[]; - video_quality_mode: null; - nsfw: boolean; - topic: string; - retention_policy_id: string; - flags: number; - default_thread_rate_limit_per_user: number; - position: number; +id: string; +created_at: string; +name: string; +icon: string; +type: number; +last_message_id: string; +guild_id: string; +parent_id: string; +last_pin_timestamp: string; +default_auto_archive_duration: number; +permission_overwrites: { +id: string; +allow: string; +deny: string; +}[]; +video_quality_mode: null; +nsfw: boolean; +topic: string; +retention_policy_id: string; +flags: number; +default_thread_rate_limit_per_user: number; +position: number; }; type rolesjson = { - id: string; - guild_id: string; - color: number; - hoist: boolean; - managed: boolean; - mentionable: boolean; - name: string; - permissions: string; - position: number; - icon: string; - unicode_emoji: string; - flags: number; +id: string; +guild_id: string; +color: number; +hoist: boolean; +managed: boolean; +mentionable: boolean; +name: string; +permissions: string; +position: number; +icon: string; +unicode_emoji: string; +flags: number; }; type dirrectjson = { - id: string; - flags: number; - last_message_id: string; - type: number; - recipients: userjson[]; - is_spam: boolean; +id: string; +flags: number; +last_message_id: string; +type: number; +recipients: userjson[]; +is_spam: boolean; }; type messagejson = { - id: string; - channel_id: string; - guild_id: string; - author: userjson; - member?: memberjson; - content: string; - timestamp: string; - edited_timestamp: string; - tts: boolean; - mention_everyone: boolean; - mentions: []; //need examples to fix - mention_roles: []; //need examples to fix - attachments: filejson[]; - embeds: embedjson[]; - reactions: { - count: number; - emoji: emojijson; //very likely needs expanding - me: boolean; - }[]; - nonce: string; - pinned: boolean; - type: number; +id: string; +channel_id: string; +guild_id: string; +author: userjson; +member?: memberjson; +content: string; +timestamp: string; +edited_timestamp: string; +tts: boolean; +mention_everyone: boolean; +mentions: []; //need examples to fix +mention_roles: []; //need examples to fix +attachments: filejson[]; +embeds: embedjson[]; +reactions: { +count: number; +emoji: emojijson; //very likely needs expanding +me: boolean; +}[]; +nonce: string; +pinned: boolean; +type: number; }; type filejson = { - id: string; - filename: string; - content_type: string; - width?: number; - height?: number; - proxy_url: string | undefined; - url: string; - size: number; +id: string; +filename: string; +content_type: string; +width?: number; +height?: number; +proxy_url: string | undefined; +url: string; +size: number; }; type embedjson = { - type: string | null; - color?: number; - author: { - icon_url?: string; - name?: string; - url?: string; - title?: string; - }; - title?: string; - url?: string; - description?: string; - fields?: { - name: string; - value: string; - inline: boolean; - }[]; - footer?: { - icon_url?: string; - text?: string; - thumbnail?: string; - }; - timestamp?: string; - thumbnail: { - proxy_url: string; - url: string; - width: number; - height: number; - }; - provider: { - name: string; - }; - video?: { - url: string; - width?: number | null; - height?: number | null; - proxy_url?: string; - }; - invite?: { - url: string; - code: string; - }; +type: string | null; +color?: number; +author: { +icon_url?: string; +name?: string; +url?: string; +title?: string; +}; +title?: string; +url?: string; +description?: string; +fields?: { +name: string; +value: string; +inline: boolean; +}[]; +footer?: { +icon_url?: string; +text?: string; +thumbnail?: string; +}; +timestamp?: string; +thumbnail: { +proxy_url: string; +url: string; +width: number; +height: number; +}; +provider: { +name: string; +}; +video?: { +url: string; +width?: number | null; +height?: number | null; +proxy_url?: string; +}; +invite?: { +url: string; +code: string; +}; }; type invitejson = { - code: string; - temporary: boolean; - uses: number; - max_use: number; - max_age: number; - created_at: string; - expires_at: string; - guild_id: string; - channel_id: string; - inviter_id: string; - target_user_id: string | null; - target_user_type: string | null; - vanity_url: string | null; - flags: number; - guild: guildjson["properties"]; - channel: channeljson; - inviter: userjson; +code: string; +temporary: boolean; +uses: number; +max_use: number; +max_age: number; +created_at: string; +expires_at: string; +guild_id: string; +channel_id: string; +inviter_id: string; +target_user_id: string | null; +target_user_type: string | null; +vanity_url: string | null; +flags: number; +guild: guildjson["properties"]; +channel: channeljson; +inviter: userjson; }; type presencejson = { - status: string; - since: number | null; - activities: any[]; //bit more complicated but not now - afk: boolean; - user?: userjson; +status: string; +since: number | null; +activities: any[]; //bit more complicated but not now +afk: boolean; +user?: userjson; }; type messageCreateJson = { - op: 0; - d: { - guild_id?: string; - channel_id?: string; - } & messagejson; - s: number; - t: "MESSAGE_CREATE"; +op: 0; +d: { +guild_id?: string; +channel_id?: string; +} & messagejson; +s: number; +t: "MESSAGE_CREATE"; }; type wsjson = - | { - op: 0; - d: any; - s: number; - t: - | "TYPING_START" - | "USER_UPDATE" - | "CHANNEL_UPDATE" - | "CHANNEL_CREATE" - | "CHANNEL_DELETE" - | "GUILD_DELETE" - | "GUILD_CREATE" - | "MESSAGE_REACTION_REMOVE_ALL" - | "MESSAGE_REACTION_REMOVE_EMOJI"; - } - | { - op: 0; - t: "GUILD_MEMBERS_CHUNK"; - d: memberChunk; - s: number; - } - | { - op: 0; - d: { - id: string; - guild_id?: string; - channel_id: string; - }; - s: number; - t: "MESSAGE_DELETE"; - } - | { - op: 0; - d: { - guild_id?: string; - channel_id: string; - } & messagejson; - s: number; - t: "MESSAGE_UPDATE"; - } - | messageCreateJson - | readyjson - | { - op: 11; - s: undefined; - d: {}; - } - | { - op: 10; - s: undefined; - d: { - heartbeat_interval: number; - }; - } - | { - op: 0; - t: "MESSAGE_REACTION_ADD"; - d: { - user_id: string; - channel_id: string; - message_id: string; - guild_id?: string; - emoji: emojijson; - member?: memberjson; - }; - s: number; - } - | { - op: 0; - t: "MESSAGE_REACTION_REMOVE"; - d: { - user_id: string; - channel_id: string; - message_id: string; - guild_id: string; - emoji: emojijson; - }; - s: 3; - }; +| { +op: 0; +d: any; +s: number; +t: +| "TYPING_START" +| "USER_UPDATE" +| "CHANNEL_UPDATE" +| "CHANNEL_CREATE" +| "CHANNEL_DELETE" +| "GUILD_DELETE" +| "GUILD_CREATE" +| "MESSAGE_REACTION_REMOVE_ALL" +| "MESSAGE_REACTION_REMOVE_EMOJI"; +} +| { +op: 0; +t: "GUILD_MEMBERS_CHUNK"; +d: memberChunk; +s: number; +} +| { +op: 0; +d: { +id: string; +guild_id?: string; +channel_id: string; +}; +s: number; +t: "MESSAGE_DELETE"; +} +| { +op: 0; +d: { +guild_id?: string; +channel_id: string; +} & messagejson; +s: number; +t: "MESSAGE_UPDATE"; +} +| messageCreateJson +| readyjson +| { +op: 11; +s: undefined; +d: {}; +} +| { +op: 10; +s: undefined; +d: { +heartbeat_interval: number; +}; +} +| { +op: 0; +t: "MESSAGE_REACTION_ADD"; +d: { +user_id: string; +channel_id: string; +message_id: string; +guild_id?: string; +emoji: emojijson; +member?: memberjson; +}; +s: number; +} +| { +op: 0; +t: "MESSAGE_REACTION_REMOVE"; +d: { +user_id: string; +channel_id: string; +message_id: string; +guild_id: string; +emoji: emojijson; +}; +s: 3; +}; type memberChunk = { - guild_id: string; - nonce: string; - members: memberjson[]; - presences: presencejson[]; - chunk_index: number; - chunk_count: number; - not_found: string[]; +guild_id: string; +nonce: string; +members: memberjson[]; +presences: presencejson[]; +chunk_index: number; +chunk_count: number; +not_found: string[]; }; export { - readyjson, - dirrectjson, - startTypingjson, - channeljson, - guildjson, - rolesjson, - userjson, - memberjson, - mainuserjson, - messagejson, - filejson, - embedjson, - emojijson, - presencejson, - wsjson, - messageCreateJson, - memberChunk, - invitejson, +readyjson, +dirrectjson, +startTypingjson, +channeljson, +guildjson, +rolesjson, +userjson, +memberjson, +mainuserjson, +messagejson, +filejson, +embedjson, +emojijson, +presencejson, +wsjson, +messageCreateJson, +memberChunk, +invitejson, }; diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 40caa46..127e810 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -6,14 +6,14 @@ import { User } from "./user.js"; import { Dialog } from "./dialog.js"; import { getapiurls, getBulkInfo, setTheme, Specialuser } from "./login.js"; import { - channeljson, - guildjson, - memberjson, - messageCreateJson, - presencejson, - readyjson, - startTypingjson, - wsjson, +channeljson, +guildjson, +memberjson, +messageCreateJson, +presencejson, +readyjson, +startTypingjson, +wsjson, } from "./jsontypes.js"; import { Member } from "./member.js"; import { FormError, Settings } from "./settings.js"; @@ -22,1803 +22,1803 @@ import { MarkDown } from "./markdown.js"; const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]); class Localuser { - badges: Map< - string, - { id: string; description: string; icon: string; link: string } - > = new Map(); - lastSequence: number | null = null; - token!: string; - userinfo!: Specialuser; - serverurls!: Specialuser["serverurls"]; - initialized!: boolean; - info!: Specialuser["serverurls"]; - headers!: { "Content-type": string; Authorization: string }; - userConnections!: Dialog; - devPortal!: Dialog; - ready!: readyjson; - guilds!: Guild[]; - guildids: Map = new Map(); - user!: User; - status!: string; - channelfocus: Channel | undefined; - lookingguild: Guild | undefined; - guildhtml: Map = new Map(); - ws: WebSocket | undefined; - connectionSucceed = 0; - errorBackoff = 0; - channelids: Map = new Map(); - readonly userMap: Map = new Map(); - instancePing = { - name: "Unknown", - }; - mfa_enabled!: boolean; - get perminfo() { - return this.userinfo.localuserStore; - } - set perminfo(e) { - this.userinfo.localuserStore = e; - } - constructor(userinfo: Specialuser | -1) { - if (userinfo === -1) { - return; - } - this.token = userinfo.token; - this.userinfo = userinfo; - this.perminfo.guilds ??= {}; - this.serverurls = this.userinfo.serverurls; - this.initialized = false; - this.info = this.serverurls; - this.headers = { - "Content-type": "application/json; charset=UTF-8", - Authorization: this.userinfo.token, - }; - } - gottenReady(ready: readyjson): void { - this.initialized = true; - this.ready = ready; - this.guilds = []; - this.guildids = new Map(); - this.user = new User(ready.d.user, this); - this.user.setstatus("online"); - this.mfa_enabled = ready.d.user.mfa_enabled as boolean; - this.userinfo.username = this.user.username; - this.userinfo.pfpsrc = this.user.getpfpsrc(); - this.status = this.ready.d.user_settings.status; - this.channelfocus = undefined; - this.lookingguild = undefined; - this.guildhtml = new Map(); - const members: { [key: string]: memberjson } = {}; - for (const thing of ready.d.merged_members) { - members[thing[0].guild_id] = thing[0]; - } +badges: Map< +string, +{ id: string; description: string; icon: string; link: string } +> = new Map(); +lastSequence: number | null = null; +token!: string; +userinfo!: Specialuser; +serverurls!: Specialuser["serverurls"]; +initialized!: boolean; +info!: Specialuser["serverurls"]; +headers!: { "Content-type": string; Authorization: string }; +userConnections!: Dialog; +devPortal!: Dialog; +ready!: readyjson; +guilds!: Guild[]; +guildids: Map = new Map(); + user!: User; + status!: string; + channelfocus: Channel | undefined; + lookingguild: Guild | undefined; + guildhtml: Map = new Map(); + ws: WebSocket | undefined; + connectionSucceed = 0; + errorBackoff = 0; + channelids: Map = new Map(); + readonly userMap: Map = new Map(); + instancePing = { + name: "Unknown", + }; + mfa_enabled!: boolean; + get perminfo() { + return this.userinfo.localuserStore; + } + set perminfo(e) { + this.userinfo.localuserStore = e; + } + constructor(userinfo: Specialuser | -1) { + if (userinfo === -1) { + return; + } + this.token = userinfo.token; + this.userinfo = userinfo; + this.perminfo.guilds ??= {}; + this.serverurls = this.userinfo.serverurls; + this.initialized = false; + this.info = this.serverurls; + this.headers = { + "Content-type": "application/json; charset=UTF-8", + Authorization: this.userinfo.token, + }; + } + gottenReady(ready: readyjson): void { + this.initialized = true; + this.ready = ready; + this.guilds = []; + this.guildids = new Map(); + this.user = new User(ready.d.user, this); + this.user.setstatus("online"); + this.mfa_enabled = ready.d.user.mfa_enabled as boolean; + this.userinfo.username = this.user.username; + this.userinfo.pfpsrc = this.user.getpfpsrc(); + this.status = this.ready.d.user_settings.status; + this.channelfocus = undefined; + this.lookingguild = undefined; + this.guildhtml = new Map(); + const members: { [key: string]: memberjson } = {}; + for (const thing of ready.d.merged_members) { + members[thing[0].guild_id] = thing[0]; + } - for (const thing of ready.d.guilds) { - const temp = new Guild(thing, this, members[thing.id]); - this.guilds.push(temp); - this.guildids.set(temp.id, temp); - } - { - const temp = new Direct(ready.d.private_channels, this); - this.guilds.push(temp); - this.guildids.set(temp.id, temp); - } - console.log(ready.d.user_guild_settings.entries); + for (const thing of ready.d.guilds) { + const temp = new Guild(thing, this, members[thing.id]); + this.guilds.push(temp); + this.guildids.set(temp.id, temp); + } + { + const temp = new Direct(ready.d.private_channels, this); + this.guilds.push(temp); + this.guildids.set(temp.id, temp); + } + console.log(ready.d.user_guild_settings.entries); - for (const thing of ready.d.user_guild_settings.entries) { - (this.guildids.get(thing.guild_id) as Guild).notisetting(thing); - } + for (const thing of ready.d.user_guild_settings.entries) { + (this.guildids.get(thing.guild_id) as Guild).notisetting(thing); + } - for (const thing of ready.d.read_state.entries) { - const channel = this.channelids.get(thing.channel_id); - if (!channel) { - continue; - } - channel.readStateInfo(thing); - } - for (const thing of ready.d.relationships) { - const user = new User(thing.user, this); - user.nickname = thing.nickname; - user.relationshipType = thing.type; - } + for (const thing of ready.d.read_state.entries) { + const channel = this.channelids.get(thing.channel_id); + if (!channel) { + continue; + } + channel.readStateInfo(thing); + } + for (const thing of ready.d.relationships) { + const user = new User(thing.user, this); + user.nickname = thing.nickname; + user.relationshipType = thing.type; + } - this.pingEndpoint(); - this.userinfo.updateLocal(); - } - outoffocus(): void { - const servers = document.getElementById("servers") as HTMLDivElement; - servers.innerHTML = ""; - const channels = document.getElementById("channels") as HTMLDivElement; - channels.innerHTML = ""; - if (this.channelfocus) { - this.channelfocus.infinite.delete(); - } - this.lookingguild = undefined; - this.channelfocus = undefined; - } - unload(): void { - this.initialized = false; - this.outoffocus(); - this.guilds = []; - this.guildids = new Map(); - if (this.ws) { - this.ws.close(4001); - } - } - swapped = false; - async initwebsocket(): Promise { - let returny: () => void; - const ws = new WebSocket( - this.serverurls.gateway.toString() + - "?encoding=json&v=9" + - (DecompressionStream ? "&compress=zlib-stream" : "") - ); - this.ws = ws; - let ds: DecompressionStream; - let w: WritableStreamDefaultWriter; - let r: ReadableStreamDefaultReader; - let arr: Uint8Array; - let build = ""; - if (DecompressionStream) { - ds = new DecompressionStream("deflate"); - w = ds.writable.getWriter(); - r = ds.readable.getReader(); - arr = new Uint8Array(); - } - const promise = new Promise((res) => { - returny = res; - ws.addEventListener("open", (_event) => { - console.log("WebSocket connected"); - ws.send( - JSON.stringify({ - op: 2, - d: { - token: this.token, - capabilities: 16381, - properties: { - browser: "Jank Client", - client_build_number: 0, //might update this eventually lol - release_channel: "Custom", - browser_user_agent: navigator.userAgent, - }, - compress: Boolean(DecompressionStream), - presence: { - status: "online", - since: null, //new Date().getTime() - activities: [], - afk: false, - }, - }, - }) - ); - }); - const textdecode = new TextDecoder(); - if (DecompressionStream) { - (async () => { - while (true) { - const read = await r.read(); - const data = textdecode.decode(read.value); - build += data; - try { - const temp = JSON.parse(build); - build = ""; - if (temp.op === 0 && temp.t === "READY") { - returny(); - } - await this.handleEvent(temp); - } catch {} - } - })(); - } - }); + this.pingEndpoint(); + this.userinfo.updateLocal(); + } + outoffocus(): void { + const servers = document.getElementById("servers") as HTMLDivElement; + servers.innerHTML = ""; + const channels = document.getElementById("channels") as HTMLDivElement; + channels.innerHTML = ""; + if (this.channelfocus) { + this.channelfocus.infinite.delete(); + } + this.lookingguild = undefined; + this.channelfocus = undefined; + } + unload(): void { + this.initialized = false; + this.outoffocus(); + this.guilds = []; + this.guildids = new Map(); + if (this.ws) { + this.ws.close(4001); + } + } + swapped = false; + async initwebsocket(): Promise { + let returny: () => void; + const ws = new WebSocket( + this.serverurls.gateway.toString() + + "?encoding=json&v=9" + + (DecompressionStream ? "&compress=zlib-stream" : "") + ); + this.ws = ws; + let ds: DecompressionStream; + let w: WritableStreamDefaultWriter; + let r: ReadableStreamDefaultReader; + let arr: Uint8Array; + let build = ""; + if (DecompressionStream) { + ds = new DecompressionStream("deflate"); + w = ds.writable.getWriter(); + r = ds.readable.getReader(); + arr = new Uint8Array(); + } + const promise = new Promise((res) => { + returny = res; + ws.addEventListener("open", (_event) => { + console.log("WebSocket connected"); + ws.send( + JSON.stringify({ + op: 2, + d: { + token: this.token, + capabilities: 16381, + properties: { + browser: "Jank Client", + client_build_number: 0, //might update this eventually lol + release_channel: "Custom", + browser_user_agent: navigator.userAgent, + }, + compress: Boolean(DecompressionStream), + presence: { + status: "online", + since: null, //new Date().getTime() + activities: [], + afk: false, + }, + }, + }) + ); + }); + const textdecode = new TextDecoder(); + if (DecompressionStream) { + (async () => { + while (true) { + const read = await r.read(); + const data = textdecode.decode(read.value); + build += data; + try { + const temp = JSON.parse(build); + build = ""; + if (temp.op === 0 && temp.t === "READY") { + returny(); + } + await this.handleEvent(temp); + } catch {} + } + })(); + } + }); - let order = new Promise((res) => res()); + let order = new Promise((res) => res()); - ws.addEventListener("message", async (event) => { - const temp2 = order; - order = new Promise(async (res) => { - await temp2; - let temp: { op: number; t: string }; - try { - if (event.data instanceof Blob) { - const buff = await event.data.arrayBuffer(); - const array = new Uint8Array(buff); + ws.addEventListener("message", async (event) => { + const temp2 = order; + order = new Promise(async (res) => { + await temp2; + let temp: { op: number; t: string }; + try { + if (event.data instanceof Blob) { + const buff = await event.data.arrayBuffer(); + const array = new Uint8Array(buff); - const temparr = new Uint8Array(array.length + arr.length); - temparr.set(arr, 0); - temparr.set(array, arr.length); - arr = temparr; + const temparr = new Uint8Array(array.length + arr.length); + temparr.set(arr, 0); + temparr.set(array, arr.length); + arr = temparr; - const len = array.length; - if ( - !( - array[len - 1] === 255 && - array[len - 2] === 255 && - array[len - 3] === 0 && - array[len - 4] === 0 - ) - ) { - return; - } - w.write(arr.buffer); - arr = new Uint8Array(); - return; //had to move the while loop due to me being dumb - } else { - temp = JSON.parse(event.data); - } - if (temp.op === 0 && temp.t === "READY") { - returny(); - } - await this.handleEvent(temp as readyjson); - } catch (e) { - console.error(e); - } finally { - res(); - } - }); - }); + const len = array.length; + if ( + !( + array[len - 1] === 255 && + array[len - 2] === 255 && + array[len - 3] === 0 && + array[len - 4] === 0 + ) + ) { + return; + } + w.write(arr.buffer); + arr = new Uint8Array(); + return; //had to move the while loop due to me being dumb + } else { + temp = JSON.parse(event.data); + } + if (temp.op === 0 && temp.t === "READY") { + returny(); + } + await this.handleEvent(temp as readyjson); + } catch (e) { + console.error(e); + } finally { + res(); + } + }); + }); - ws.addEventListener("close", async (event) => { - this.ws = undefined; - console.log("WebSocket closed with code " + event.code); + ws.addEventListener("close", async (event) => { + this.ws = undefined; + console.log("WebSocket closed with code " + event.code); - this.unload(); - (document.getElementById("loading") as HTMLElement).classList.remove( - "doneloading" - ); - (document.getElementById("loading") as HTMLElement).classList.add( - "loading" - ); - this.fetchingmembers = new Map(); - this.noncemap = new Map(); - this.noncebuild = new Map(); - if ( - (event.code > 1000 && event.code < 1016) || - wsCodesRetry.has(event.code) - ) { - if ( - this.connectionSucceed !== 0 && - Date.now() > this.connectionSucceed + 20000 - ) - this.errorBackoff = 0; - else this.errorBackoff++; - this.connectionSucceed = 0; + this.unload(); + (document.getElementById("loading") as HTMLElement).classList.remove( + "doneloading" + ); + (document.getElementById("loading") as HTMLElement).classList.add( + "loading" + ); + this.fetchingmembers = new Map(); + this.noncemap = new Map(); + this.noncebuild = new Map(); + if ( + (event.code > 1000 && event.code < 1016) || + wsCodesRetry.has(event.code) + ) { + if ( + this.connectionSucceed !== 0 && + Date.now() > this.connectionSucceed + 20000 + ) + this.errorBackoff = 0; + else this.errorBackoff++; + this.connectionSucceed = 0; - (document.getElementById("load-desc") as HTMLElement).innerHTML = - "Unable to connect to the Spacebar server, retrying in " + - Math.round(0.2 + this.errorBackoff * 2.8) + - " seconds..."; - switch ( - this.errorBackoff //try to recover from bad domain - ) { - case 3: - const newurls = await getapiurls(this.info.wellknown); - if (newurls) { - this.info = newurls; - this.serverurls = newurls; - this.userinfo.json.serverurls = this.info; - this.userinfo.updateLocal(); - break; - } - break; + (document.getElementById("load-desc") as HTMLElement).innerHTML = + "Unable to connect to the Spacebar server, retrying in " + + Math.round(0.2 + this.errorBackoff * 2.8) + + " seconds..."; + switch ( + this.errorBackoff //try to recover from bad domain + ) { + case 3: + const newurls = await getapiurls(this.info.wellknown); + if (newurls) { + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + break; + } + break; - case 4: { - const newurls = await getapiurls( - new URL(this.info.wellknown).origin - ); - if (newurls) { - this.info = newurls; - this.serverurls = newurls; - this.userinfo.json.serverurls = this.info; - this.userinfo.updateLocal(); - break; - } - break; - } - case 5: { - const breakappart = new URL(this.info.wellknown).origin.split("."); - const url = - "https://" + breakappart.at(-2) + "." + breakappart.at(-1); - const newurls = await getapiurls(url); - if (newurls) { - this.info = newurls; - this.serverurls = newurls; - this.userinfo.json.serverurls = this.info; - this.userinfo.updateLocal(); - } - break; - } - } - setTimeout(() => { - if (this.swapped) return; - (document.getElementById("load-desc") as HTMLElement).textContent = - "Retrying..."; - this.initwebsocket().then(() => { - this.loaduser(); - this.init(); - const loading = document.getElementById("loading") as HTMLElement; - loading.classList.add("doneloading"); - loading.classList.remove("loading"); - console.log("done loading"); - }); - }, 200 + this.errorBackoff * 2800); - } else - (document.getElementById("load-desc") as HTMLElement).textContent = - "Unable to connect to the Spacebar server. Please try logging out and back in."; - }); + case 4: { + const newurls = await getapiurls( + new URL(this.info.wellknown).origin + ); + if (newurls) { + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + break; + } + break; + } + case 5: { + const breakappart = new URL(this.info.wellknown).origin.split("."); + const url = + "https://" + breakappart.at(-2) + "." + breakappart.at(-1); + const newurls = await getapiurls(url); + if (newurls) { + this.info = newurls; + this.serverurls = newurls; + this.userinfo.json.serverurls = this.info; + this.userinfo.updateLocal(); + } + break; + } + } + setTimeout(() => { + if (this.swapped) return; + (document.getElementById("load-desc") as HTMLElement).textContent = + "Retrying..."; + this.initwebsocket().then(() => { + this.loaduser(); + this.init(); + const loading = document.getElementById("loading") as HTMLElement; + loading.classList.add("doneloading"); + loading.classList.remove("loading"); + console.log("done loading"); + }); + }, 200 + this.errorBackoff * 2800); + } else + (document.getElementById("load-desc") as HTMLElement).textContent = + "Unable to connect to the Spacebar server. Please try logging out and back in."; + }); - await promise; - } - async handleEvent(temp: wsjson) { - console.debug(temp); - if (temp.s) this.lastSequence = temp.s; - if (temp.op == 0) { - switch (temp.t) { - case "MESSAGE_CREATE": - if (this.initialized) { - this.messageCreate(temp); - } - break; - case "MESSAGE_DELETE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.id); - if (!message) break; - message.deleteEvent(); - break; - } - case "READY": - this.gottenReady(temp as readyjson); - break; - case "MESSAGE_UPDATE": { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.id); - if (!message) break; - message.giveData(temp.d); - break; - } - case "TYPING_START": - if (this.initialized) { - this.typingStart(temp); - } - break; - case "USER_UPDATE": - if (this.initialized) { - const users = this.userMap.get(temp.d.id); - if (users) { - users.userupdate(temp.d); - } - } - break; - case "CHANNEL_UPDATE": - if (this.initialized) { - this.updateChannel(temp.d); - } - break; - case "CHANNEL_CREATE": - if (this.initialized) { - this.createChannel(temp.d); - } - break; - case "CHANNEL_DELETE": - if (this.initialized) { - this.delChannel(temp.d); - } - break; - case "GUILD_DELETE": { - const guildy = this.guildids.get(temp.d.id); - if (guildy) { - this.guildids.delete(temp.d.id); - this.guilds.splice(this.guilds.indexOf(guildy), 1); - guildy.html.remove(); - } - break; - } - case "GUILD_CREATE": { - const guildy = new Guild(temp.d, this, this.user); - this.guilds.push(guildy); - this.guildids.set(guildy.id, guildy); - (document.getElementById("servers") as HTMLDivElement).insertBefore( - guildy.generateGuildIcon(), - document.getElementById("bottomseparator") - ); - break; - } - case "MESSAGE_REACTION_ADD": - { - temp.d.guild_id ??= "@me"; - const guild = this.guildids.get(temp.d.guild_id); - if (!guild) break; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - let thing: Member | { id: string }; - if (temp.d.member) { - thing = (await Member.new(temp.d.member, guild)) as Member; - } else { - thing = { id: temp.d.user_id }; - } - message.reactionAdd(temp.d.emoji, thing); - } - break; - case "MESSAGE_REACTION_REMOVE": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - message.reactionRemove(temp.d.emoji, temp.d.user_id); - } - break; - case "MESSAGE_REACTION_REMOVE_ALL": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - message.reactionRemoveAll(); - } - break; - case "MESSAGE_REACTION_REMOVE_EMOJI": - { - temp.d.guild_id ??= "@me"; - const channel = this.channelids.get(temp.d.channel_id); - if (!channel) break; - const message = channel.messages.get(temp.d.message_id); - if (!message) break; - message.reactionRemoveEmoji(temp.d.emoji); - } - break; - case "GUILD_MEMBERS_CHUNK": - this.gotChunk(temp.d); - break; - } - } else if (temp.op === 10) { - if (!this.ws) return; - console.log("heartbeat down"); - this.heartbeat_interval = temp.d.heartbeat_interval; - this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); - } else if (temp.op === 11) { - setTimeout((_: any) => { - if (!this.ws) return; - if (this.connectionSucceed === 0) this.connectionSucceed = Date.now(); - this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); - }, this.heartbeat_interval); - } - } - heartbeat_interval: number = 0; - updateChannel(json: channeljson): void { - const guild = this.guildids.get(json.guild_id); - if (guild) { - guild.updateChannel(json); - if (json.guild_id === this.lookingguild?.id) { - this.loadGuild(json.guild_id); - } - } - } - createChannel(json: channeljson): undefined | Channel { - json.guild_id ??= "@me"; - const guild = this.guildids.get(json.guild_id); - if (!guild) return; - const channel = guild.createChannelpac(json); - if (json.guild_id === this.lookingguild?.id) { - this.loadGuild(json.guild_id); - } - if (channel.id === this.gotoid) { - guild.loadGuild(); - guild.loadChannel(channel.id); - this.gotoid = undefined; - } - return channel; // Add this line to return the 'channel' variable - } - gotoid: string | undefined; - async goToChannel(id: string) { - const channel = this.channelids.get(id); - if (channel) { - const guild = channel.guild; - guild.loadGuild(); - guild.loadChannel(id); - } else { - this.gotoid = id; - } - } - delChannel(json: channeljson): void { - let guild_id = json.guild_id; - guild_id ??= "@me"; - const guild = this.guildids.get(guild_id); - if (guild) { - guild.delChannel(json); - } + await promise; + } + async handleEvent(temp: wsjson) { + console.debug(temp); + if (temp.s) this.lastSequence = temp.s; + if (temp.op == 0) { + switch (temp.t) { + case "MESSAGE_CREATE": + if (this.initialized) { + this.messageCreate(temp); + } + break; + case "MESSAGE_DELETE": { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if (!channel) break; + const message = channel.messages.get(temp.d.id); + if (!message) break; + message.deleteEvent(); + break; + } + case "READY": + this.gottenReady(temp as readyjson); + break; + case "MESSAGE_UPDATE": { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if (!channel) break; + const message = channel.messages.get(temp.d.id); + if (!message) break; + message.giveData(temp.d); + break; + } + case "TYPING_START": + if (this.initialized) { + this.typingStart(temp); + } + break; + case "USER_UPDATE": + if (this.initialized) { + const users = this.userMap.get(temp.d.id); + if (users) { + users.userupdate(temp.d); + } + } + break; + case "CHANNEL_UPDATE": + if (this.initialized) { + this.updateChannel(temp.d); + } + break; + case "CHANNEL_CREATE": + if (this.initialized) { + this.createChannel(temp.d); + } + break; + case "CHANNEL_DELETE": + if (this.initialized) { + this.delChannel(temp.d); + } + break; + case "GUILD_DELETE": { + const guildy = this.guildids.get(temp.d.id); + if (guildy) { + this.guildids.delete(temp.d.id); + this.guilds.splice(this.guilds.indexOf(guildy), 1); + guildy.html.remove(); + } + break; + } + case "GUILD_CREATE": { + const guildy = new Guild(temp.d, this, this.user); + this.guilds.push(guildy); + this.guildids.set(guildy.id, guildy); + (document.getElementById("servers") as HTMLDivElement).insertBefore( + guildy.generateGuildIcon(), + document.getElementById("bottomseparator") + ); + break; + } + case "MESSAGE_REACTION_ADD": + { + temp.d.guild_id ??= "@me"; + const guild = this.guildids.get(temp.d.guild_id); + if (!guild) break; + const channel = this.channelids.get(temp.d.channel_id); + if (!channel) break; + const message = channel.messages.get(temp.d.message_id); + if (!message) break; + let thing: Member | { id: string }; + if (temp.d.member) { + thing = (await Member.new(temp.d.member, guild)) as Member; + } else { + thing = { id: temp.d.user_id }; + } + message.reactionAdd(temp.d.emoji, thing); + } + break; + case "MESSAGE_REACTION_REMOVE": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if (!channel) break; + const message = channel.messages.get(temp.d.message_id); + if (!message) break; + message.reactionRemove(temp.d.emoji, temp.d.user_id); + } + break; + case "MESSAGE_REACTION_REMOVE_ALL": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if (!channel) break; + const message = channel.messages.get(temp.d.message_id); + if (!message) break; + message.reactionRemoveAll(); + } + break; + case "MESSAGE_REACTION_REMOVE_EMOJI": + { + temp.d.guild_id ??= "@me"; + const channel = this.channelids.get(temp.d.channel_id); + if (!channel) break; + const message = channel.messages.get(temp.d.message_id); + if (!message) break; + message.reactionRemoveEmoji(temp.d.emoji); + } + break; + case "GUILD_MEMBERS_CHUNK": + this.gotChunk(temp.d); + break; + } + } else if (temp.op === 10) { + if (!this.ws) return; + console.log("heartbeat down"); + this.heartbeat_interval = temp.d.heartbeat_interval; + this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); + } else if (temp.op === 11) { + setTimeout((_: any) => { + if (!this.ws) return; + if (this.connectionSucceed === 0) this.connectionSucceed = Date.now(); + this.ws.send(JSON.stringify({ op: 1, d: this.lastSequence })); + }, this.heartbeat_interval); + } + } + heartbeat_interval: number = 0; + updateChannel(json: channeljson): void { + const guild = this.guildids.get(json.guild_id); + if (guild) { + guild.updateChannel(json); + if (json.guild_id === this.lookingguild?.id) { + this.loadGuild(json.guild_id); + } + } + } + createChannel(json: channeljson): undefined | Channel { + json.guild_id ??= "@me"; + const guild = this.guildids.get(json.guild_id); + if (!guild) return; + const channel = guild.createChannelpac(json); + if (json.guild_id === this.lookingguild?.id) { + this.loadGuild(json.guild_id); + } + if (channel.id === this.gotoid) { + guild.loadGuild(); + guild.loadChannel(channel.id); + this.gotoid = undefined; + } + return channel; // Add this line to return the 'channel' variable + } + gotoid: string | undefined; + async goToChannel(id: string) { + const channel = this.channelids.get(id); + if (channel) { + const guild = channel.guild; + guild.loadGuild(); + guild.loadChannel(id); + } else { + this.gotoid = id; + } + } + delChannel(json: channeljson): void { + let guild_id = json.guild_id; + guild_id ??= "@me"; + const guild = this.guildids.get(guild_id); + if (guild) { + guild.delChannel(json); + } - if (json.guild_id === this.lookingguild?.id) { - this.loadGuild(json.guild_id); - } - } - init(): void { - const location = window.location.href.split("/"); - this.buildservers(); - if (location[3] === "channels") { - const guild = this.loadGuild(location[4]); - if (!guild) { - return; - } - guild.loadChannel(location[5]); - this.channelfocus = this.channelids.get(location[5]); - } - } - loaduser(): void { - (document.getElementById("username") as HTMLSpanElement).textContent = - this.user.username; - (document.getElementById("userpfp") as HTMLImageElement).src = - this.user.getpfpsrc(); - (document.getElementById("status") as HTMLSpanElement).textContent = - this.status; - } - isAdmin(): boolean { - if (this.lookingguild) { - return this.lookingguild.isAdmin(); - } else { - return false; - } - } - loadGuild(id: string): Guild | undefined { - let guild = this.guildids.get(id); - if (!guild) { - guild = this.guildids.get("@me"); - } - if (this.lookingguild === guild) { - return guild; - } - if (this.channelfocus) { - this.channelfocus.infinite.delete(); - this.channelfocus = undefined; - } - if (this.lookingguild) { - this.lookingguild.html.classList.remove("serveropen"); - } + if (json.guild_id === this.lookingguild?.id) { + this.loadGuild(json.guild_id); + } + } + init(): void { + const location = window.location.href.split("/"); + this.buildservers(); + if (location[3] === "channels") { + const guild = this.loadGuild(location[4]); + if (!guild) { + return; + } + guild.loadChannel(location[5]); + this.channelfocus = this.channelids.get(location[5]); + } + } + loaduser(): void { + (document.getElementById("username") as HTMLSpanElement).textContent = + this.user.username; + (document.getElementById("userpfp") as HTMLImageElement).src = + this.user.getpfpsrc(); + (document.getElementById("status") as HTMLSpanElement).textContent = + this.status; + } + isAdmin(): boolean { + if (this.lookingguild) { + return this.lookingguild.isAdmin(); + } else { + return false; + } + } + loadGuild(id: string): Guild | undefined { + let guild = this.guildids.get(id); + if (!guild) { + guild = this.guildids.get("@me"); + } + if (this.lookingguild === guild) { + return guild; + } + if (this.channelfocus) { + this.channelfocus.infinite.delete(); + this.channelfocus = undefined; + } + if (this.lookingguild) { + this.lookingguild.html.classList.remove("serveropen"); + } - if (!guild) return; - if (guild.html) { - guild.html.classList.add("serveropen"); - } - this.lookingguild = guild; - (document.getElementById("serverName") as HTMLElement).textContent = - guild.properties.name; - //console.log(this.guildids,id) - const channels = document.getElementById("channels") as HTMLDivElement; - channels.innerHTML = ""; - const html = guild.getHTML(); - channels.appendChild(html); - return guild; - } - buildservers(): void { - const serverlist = document.getElementById("servers") as HTMLDivElement; // - const outdiv = document.createElement("div"); - const home: any = document.createElement("span"); - const div = document.createElement("div"); - div.classList.add("home", "servericon"); + if (!guild) return; + if (guild.html) { + guild.html.classList.add("serveropen"); + } + this.lookingguild = guild; + (document.getElementById("serverName") as HTMLElement).textContent = + guild.properties.name; + //console.log(this.guildids,id) + const channels = document.getElementById("channels") as HTMLDivElement; + channels.innerHTML = ""; + const html = guild.getHTML(); + channels.appendChild(html); + return guild; + } + buildservers(): void { + const serverlist = document.getElementById("servers") as HTMLDivElement; // + const outdiv = document.createElement("div"); + const home: any = document.createElement("span"); + const div = document.createElement("div"); + div.classList.add("home", "servericon"); - home.classList.add("svgtheme", "svgicon", "svg-home"); - home["all"] = this.guildids.get("@me"); - (this.guildids.get("@me") as Guild).html = outdiv; - const unread = document.createElement("div"); - unread.classList.add("unread"); - outdiv.append(unread); - outdiv.append(div); - div.appendChild(home); + home.classList.add("svgtheme", "svgicon", "svg-home"); + home["all"] = this.guildids.get("@me"); + (this.guildids.get("@me") as Guild).html = outdiv; + const unread = document.createElement("div"); + unread.classList.add("unread"); + outdiv.append(unread); + outdiv.append(div); + div.appendChild(home); - outdiv.classList.add("servernoti"); - serverlist.append(outdiv); - home.onclick = function () { - this["all"].loadGuild(); - this["all"].loadChannel(); - }; - const sentdms = document.createElement("div"); - sentdms.classList.add("sentdms"); - serverlist.append(sentdms); - sentdms.id = "sentdms"; + outdiv.classList.add("servernoti"); + serverlist.append(outdiv); + home.onclick = function () { + this["all"].loadGuild(); + this["all"].loadChannel(); + }; + const sentdms = document.createElement("div"); + sentdms.classList.add("sentdms"); + serverlist.append(sentdms); + sentdms.id = "sentdms"; - const br = document.createElement("hr"); - br.classList.add("lightbr"); - serverlist.appendChild(br); - for (const thing of this.guilds) { - if (thing instanceof Direct) { - (thing as Direct).unreaddms(); - continue; - } - const divy = thing.generateGuildIcon(); - serverlist.append(divy); - } - { - const br = document.createElement("hr"); - br.classList.add("lightbr"); - serverlist.appendChild(br); - br.id = "bottomseparator"; + const br = document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + for (const thing of this.guilds) { + if (thing instanceof Direct) { + (thing as Direct).unreaddms(); + continue; + } + const divy = thing.generateGuildIcon(); + serverlist.append(divy); + } + { + const br = document.createElement("hr"); + br.classList.add("lightbr"); + serverlist.appendChild(br); + br.id = "bottomseparator"; - const div = document.createElement("div"); - div.textContent = "+"; - div.classList.add("home", "servericon"); - serverlist.appendChild(div); - div.onclick = (_) => { - this.createGuild(); - }; - const guilddsdiv = document.createElement("div"); - const guildDiscoveryContainer = document.createElement("span"); - guildDiscoveryContainer.classList.add( - "svgtheme", - "svgicon", - "svg-explore" - ); - guilddsdiv.classList.add("home", "servericon"); - guilddsdiv.appendChild(guildDiscoveryContainer); - serverlist.appendChild(guilddsdiv); - guildDiscoveryContainer.addEventListener("click", () => { - this.guildDiscovery(); - }); - } - this.unreads(); - } - createGuild() { - let inviteurl = ""; - const error = document.createElement("span"); - const fields: { name: string; icon: string | null } = { - name: "", - icon: null, - }; - const full = new Dialog([ - "tabs", - [ - [ - "Join using invite", - [ - "vdiv", - [ - "textbox", - "Invite Link/Code", - "", - function (this: HTMLInputElement) { - inviteurl = this.value; - }, - ], - ["html", error], - [ - "button", - "", - "Submit", - (_: any) => { - let parsed = ""; - if (inviteurl.includes("/")) { - parsed = - inviteurl.split("/")[inviteurl.split("/").length - 1]; - } else { - parsed = inviteurl; - } - fetch(this.info.api + "/invites/" + parsed, { - method: "POST", - headers: this.headers, - }) - .then((r) => r.json()) - .then((_) => { - if (_.message) { - error.textContent = _.message; - } - }); - }, - ], - ], - ], - [ - "Create Guild", - [ - "vdiv", - ["title", "Create a guild"], - [ - "fileupload", - "Icon:", - function (event: Event) { - const target = event.target as HTMLInputElement; - if (!target.files) return; - const reader = new FileReader(); - reader.readAsDataURL(target.files[0]); - reader.onload = () => { - fields.icon = reader.result as string; - }; - }, - ], - [ - "textbox", - "Name:", - "", - function (this: HTMLInputElement, event: Event) { - const target = event.target as HTMLInputElement; - fields.name = target.value; - }, - ], - [ - "button", - "", - "submit", - () => { - this.makeGuild(fields).then((_) => { - if (_.message) { - alert(_.errors.name._errors[0].message); - } else { - full.hide(); - } - }); - }, - ], - ], - ], - ], - ]); - full.show(); - } - async makeGuild(fields: { name: string; icon: string | null }) { - return await ( - await fetch(this.info.api + "/guilds", { - method: "POST", - headers: this.headers, - body: JSON.stringify(fields), - }) - ).json(); - } - async guildDiscovery() { - const content = document.createElement("div"); - content.classList.add("guildy"); - content.textContent = "Loading..."; - const full = new Dialog(["html", content]); - full.show(); + const div = document.createElement("div"); + div.textContent = "+"; + div.classList.add("home", "servericon"); + serverlist.appendChild(div); + div.onclick = (_) => { + this.createGuild(); + }; + const guilddsdiv = document.createElement("div"); + const guildDiscoveryContainer = document.createElement("span"); + guildDiscoveryContainer.classList.add( + "svgtheme", + "svgicon", + "svg-explore" + ); + guilddsdiv.classList.add("home", "servericon"); + guilddsdiv.appendChild(guildDiscoveryContainer); + serverlist.appendChild(guilddsdiv); + guildDiscoveryContainer.addEventListener("click", () => { + this.guildDiscovery(); + }); + } + this.unreads(); + } + createGuild() { + let inviteurl = ""; + const error = document.createElement("span"); + const fields: { name: string; icon: string | null } = { + name: "", + icon: null, + }; + const full = new Dialog([ + "tabs", + [ + [ + "Join using invite", + [ + "vdiv", + [ + "textbox", + "Invite Link/Code", + "", + function (this: HTMLInputElement) { + inviteurl = this.value; + }, + ], + ["html", error], + [ + "button", + "", + "Submit", + (_: any) => { + let parsed = ""; + if (inviteurl.includes("/")) { + parsed = + inviteurl.split("/")[inviteurl.split("/").length - 1]; + } else { + parsed = inviteurl; + } + fetch(this.info.api + "/invites/" + parsed, { + method: "POST", + headers: this.headers, + }) + .then((r) => r.json()) + .then((_) => { + if (_.message) { + error.textContent = _.message; + } + }); + }, + ], + ], + ], + [ + "Create Guild", + [ + "vdiv", + ["title", "Create a guild"], + [ + "fileupload", + "Icon:", + function (event: Event) { + const target = event.target as HTMLInputElement; + if (!target.files) return; + const reader = new FileReader(); + reader.readAsDataURL(target.files[0]); + reader.onload = () => { + fields.icon = reader.result as string; + }; + }, + ], + [ + "textbox", + "Name:", + "", + function (this: HTMLInputElement, event: Event) { + const target = event.target as HTMLInputElement; + fields.name = target.value; + }, + ], + [ + "button", + "", + "submit", + () => { + this.makeGuild(fields).then((_) => { + if (_.message) { + alert(_.errors.name._errors[0].message); + } else { + full.hide(); + } + }); + }, + ], + ], + ], + ], + ]); + full.show(); + } + async makeGuild(fields: { name: string; icon: string | null }) { + return await ( + await fetch(this.info.api + "/guilds", { + method: "POST", + headers: this.headers, + body: JSON.stringify(fields), + }) + ).json(); + } + async guildDiscovery() { + const content = document.createElement("div"); + content.classList.add("guildy"); + content.textContent = "Loading..."; + const full = new Dialog(["html", content]); + full.show(); - const res = await fetch(this.info.api + "/discoverable-guilds?limit=50", { - headers: this.headers, - }); - const json = await res.json(); + const res = await fetch(this.info.api + "/discoverable-guilds?limit=50", { + headers: this.headers, + }); + const json = await res.json(); - content.innerHTML = ""; - const title = document.createElement("h2"); - title.textContent = "Guild discovery (" + json.total + " entries)"; - content.appendChild(title); + content.innerHTML = ""; + const title = document.createElement("h2"); + title.textContent = "Guild discovery (" + json.total + " entries)"; + content.appendChild(title); - const guilds = document.createElement("div"); - guilds.id = "discovery-guild-content"; + const guilds = document.createElement("div"); + guilds.id = "discovery-guild-content"; - json.guilds.forEach((guild: guildjson["properties"]) => { - const content = document.createElement("div"); - content.classList.add("discovery-guild"); + json.guilds.forEach((guild: guildjson["properties"]) => { + const content = document.createElement("div"); + content.classList.add("discovery-guild"); - if (guild.banner) { - const banner = document.createElement("img"); - banner.classList.add("banner"); - banner.crossOrigin = "anonymous"; - banner.src = - this.info.cdn + - "/icons/" + - guild.id + - "/" + - guild.banner + - ".png?size=256"; - banner.alt = ""; - content.appendChild(banner); - } + if (guild.banner) { + const banner = document.createElement("img"); + banner.classList.add("banner"); + banner.crossOrigin = "anonymous"; + banner.src = + this.info.cdn + + "/icons/" + + guild.id + + "/" + + guild.banner + + ".png?size=256"; + banner.alt = ""; + content.appendChild(banner); + } - const nameContainer = document.createElement("div"); - nameContainer.classList.add("flex"); - const img = document.createElement("img"); - img.classList.add("icon"); - img.crossOrigin = "anonymous"; - img.src = - this.info.cdn + - (guild.icon - ? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48" - : "/embed/avatars/3.png"); - img.alt = ""; - nameContainer.appendChild(img); + const nameContainer = document.createElement("div"); + nameContainer.classList.add("flex"); + const img = document.createElement("img"); + img.classList.add("icon"); + img.crossOrigin = "anonymous"; + img.src = + this.info.cdn + + (guild.icon + ? "/icons/" + guild.id + "/" + guild.icon + ".png?size=48" + : "/embed/avatars/3.png"); + img.alt = ""; + nameContainer.appendChild(img); - const name = document.createElement("h3"); - name.textContent = guild.name; - nameContainer.appendChild(name); - content.appendChild(nameContainer); - const desc = document.createElement("p"); - desc.textContent = guild.description; - content.appendChild(desc); + const name = document.createElement("h3"); + name.textContent = guild.name; + nameContainer.appendChild(name); + content.appendChild(nameContainer); + const desc = document.createElement("p"); + desc.textContent = guild.description; + content.appendChild(desc); - content.addEventListener("click", async () => { - const joinRes = await fetch( - this.info.api + "/guilds/" + guild.id + "/members/@me", - { - method: "PUT", - headers: this.headers, - } - ); - if (joinRes.ok) full.hide(); - }); - guilds.appendChild(content); - }); - content.appendChild(guilds); - } - messageCreate(messagep: messageCreateJson): void { - messagep.d.guild_id ??= "@me"; - const channel = this.channelids.get(messagep.d.channel_id); - if (channel) { - channel.messageCreate(messagep); - this.unreads(); - } - } - unreads(): void { - for (const thing of this.guilds) { - if (thing.id === "@me") { - continue; - } - const html = this.guildhtml.get(thing.id); - thing.unreads(html); - } - } - async typingStart(typing: startTypingjson): Promise { - const channel = this.channelids.get(typing.d.channel_id); - if (!channel) return; - channel.typingStart(typing); - } - updatepfp(file: Blob): void { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => { - fetch(this.info.api + "/users/@me", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - avatar: reader.result, - }), - }); - }; - } - updatebanner(file: Blob | null): void { - if (file) { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => { - fetch(this.info.api + "/users/@me", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - banner: reader.result, - }), - }); - }; - } else { - fetch(this.info.api + "/users/@me", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ - banner: null, - }), - }); - } - } - updateProfile(json: { - bio?: string; - pronouns?: string; - accent_color?: number; - }) { - fetch(this.info.api + "/users/@me/profile", { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(json), - }); - } - async showusersettings() { - const settings = new Settings("Settings"); - { - const userOptions = settings.addButton("User Settings", { ltr: true }); - const hypotheticalProfile = document.createElement("div"); - let file: undefined | File | null; - let newpronouns: string | undefined; - let newbio: string | undefined; - const hypouser = this.user.clone(); - let color: string; - async function regen() { - hypotheticalProfile.textContent = ""; - const hypoprofile = await hypouser.buildprofile(-1, -1); + content.addEventListener("click", async () => { + const joinRes = await fetch( + this.info.api + "/guilds/" + guild.id + "/members/@me", + { + method: "PUT", + headers: this.headers, + } + ); + if (joinRes.ok) full.hide(); + }); + guilds.appendChild(content); + }); + content.appendChild(guilds); + } + messageCreate(messagep: messageCreateJson): void { + messagep.d.guild_id ??= "@me"; + const channel = this.channelids.get(messagep.d.channel_id); + if (channel) { + channel.messageCreate(messagep); + this.unreads(); + } + } + unreads(): void { + for (const thing of this.guilds) { + if (thing.id === "@me") { + continue; + } + const html = this.guildhtml.get(thing.id); + thing.unreads(html); + } + } + async typingStart(typing: startTypingjson): Promise { + const channel = this.channelids.get(typing.d.channel_id); + if (!channel) return; + channel.typingStart(typing); + } + updatepfp(file: Blob): void { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + avatar: reader.result, + }), + }); + }; + } + updatebanner(file: Blob | null): void { + if (file) { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: reader.result, + }), + }); + }; + } else { + fetch(this.info.api + "/users/@me", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ + banner: null, + }), + }); + } + } + updateProfile(json: { + bio?: string; + pronouns?: string; + accent_color?: number; + }) { + fetch(this.info.api + "/users/@me/profile", { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(json), + }); + } + async showusersettings() { + const settings = new Settings("Settings"); + { + const userOptions = settings.addButton("User Settings", { ltr: true }); + const hypotheticalProfile = document.createElement("div"); + let file: undefined | File | null; + let newpronouns: string | undefined; + let newbio: string | undefined; + const hypouser = this.user.clone(); + let color: string; + async function regen() { + hypotheticalProfile.textContent = ""; + const hypoprofile = await hypouser.buildprofile(-1, -1); - hypotheticalProfile.appendChild(hypoprofile); - } - regen(); - const settingsLeft = userOptions.addOptions(""); - const settingsRight = userOptions.addOptions(""); - settingsRight.addHTMLArea(hypotheticalProfile); + hypotheticalProfile.appendChild(hypoprofile); + } + regen(); + const settingsLeft = userOptions.addOptions(""); + const settingsRight = userOptions.addOptions(""); + settingsRight.addHTMLArea(hypotheticalProfile); - const finput = settingsLeft.addFileInput( - "Upload pfp:", - (_) => { - if (file) { - this.updatepfp(file); - } - }, - { clear: true } - ); - finput.watchForChange((_) => { - if (!_) { - file = null; - hypouser.avatar = null; - hypouser.hypotheticalpfp = true; - regen(); - return; - } - if (_.length) { - file = _[0]; - const blob = URL.createObjectURL(file); - hypouser.avatar = blob; - hypouser.hypotheticalpfp = true; - regen(); - } - }); - let bfile: undefined | File | null; - const binput = settingsLeft.addFileInput( - "Upload banner:", - (_) => { - if (bfile !== undefined) { - this.updatebanner(bfile); - } - }, - { clear: true } - ); - binput.watchForChange((_) => { - if (!_) { - bfile = null; - hypouser.banner = undefined; - hypouser.hypotheticalbanner = true; - regen(); - return; - } - if (_.length) { - bfile = _[0]; - const blob = URL.createObjectURL(bfile); - hypouser.banner = blob; - hypouser.hypotheticalbanner = true; - regen(); - } - }); - let changed = false; - const pronounbox = settingsLeft.addTextInput( - "Pronouns", - (_) => { - if (newpronouns || newbio || changed) { - this.updateProfile({ - pronouns: newpronouns, - bio: newbio, - accent_color: Number.parseInt("0x" + color.substr(1), 16), - }); - } - }, - { initText: this.user.pronouns } - ); - pronounbox.watchForChange((_) => { - hypouser.pronouns = _; - newpronouns = _; - regen(); - }); - const bioBox = settingsLeft.addMDInput("Bio:", (_) => {}, { - initText: this.user.bio.rawString, - }); - bioBox.watchForChange((_) => { - newbio = _; - hypouser.bio = new MarkDown(_, this); - regen(); - }); + const finput = settingsLeft.addFileInput( + "Upload pfp:", + (_) => { + if (file) { + this.updatepfp(file); + } + }, + { clear: true } + ); + finput.watchForChange((_) => { + if (!_) { + file = null; + hypouser.avatar = null; + hypouser.hypotheticalpfp = true; + regen(); + return; + } + if (_.length) { + file = _[0]; + const blob = URL.createObjectURL(file); + hypouser.avatar = blob; + hypouser.hypotheticalpfp = true; + regen(); + } + }); + let bfile: undefined | File | null; + const binput = settingsLeft.addFileInput( + "Upload banner:", + (_) => { + if (bfile !== undefined) { + this.updatebanner(bfile); + } + }, + { clear: true } + ); + binput.watchForChange((_) => { + if (!_) { + bfile = null; + hypouser.banner = undefined; + hypouser.hypotheticalbanner = true; + regen(); + return; + } + if (_.length) { + bfile = _[0]; + const blob = URL.createObjectURL(bfile); + hypouser.banner = blob; + hypouser.hypotheticalbanner = true; + regen(); + } + }); + let changed = false; + const pronounbox = settingsLeft.addTextInput( + "Pronouns", + (_) => { + if (newpronouns || newbio || changed) { + this.updateProfile({ + pronouns: newpronouns, + bio: newbio, + accent_color: Number.parseInt("0x" + color.substr(1), 16), + }); + } + }, + { initText: this.user.pronouns } + ); + pronounbox.watchForChange((_) => { + hypouser.pronouns = _; + newpronouns = _; + regen(); + }); + const bioBox = settingsLeft.addMDInput("Bio:", (_) => {}, { + initText: this.user.bio.rawString, + }); + bioBox.watchForChange((_) => { + newbio = _; + hypouser.bio = new MarkDown(_, this); + regen(); + }); - if (this.user.accent_color) { - color = "#" + this.user.accent_color.toString(16); - } else { - color = "transparent"; - } - const colorPicker = settingsLeft.addColorInput( - "Profile color", - (_) => {}, - { initColor: color } - ); - colorPicker.watchForChange((_) => { - console.log(); - color = _; - hypouser.accent_color = Number.parseInt("0x" + _.substr(1), 16); - changed = true; - regen(); - }); - } - { - const tas = settings.addButton("Themes & sounds"); - { - const themes = ["Dark", "WHITE", "Light"]; - tas.addSelect( - "Theme:", - (_) => { - localStorage.setItem("theme", themes[_]); - setTheme(); - }, - themes, - { - defaultIndex: themes.indexOf( - localStorage.getItem("theme") as string - ), - } - ); - } - { - const sounds = Voice.sounds; - tas - .addSelect( - "Notification sound:", - (_) => { - Voice.setNotificationSound(sounds[_]); - }, - sounds, - { defaultIndex: sounds.indexOf(Voice.getNotificationSound()) } - ) - .watchForChange((_) => { - Voice.noises(sounds[_]); - }); - } + if (this.user.accent_color) { + color = "#" + this.user.accent_color.toString(16); + } else { + color = "transparent"; + } + const colorPicker = settingsLeft.addColorInput( + "Profile color", + (_) => {}, + { initColor: color } + ); + colorPicker.watchForChange((_) => { + console.log(); + color = _; + hypouser.accent_color = Number.parseInt("0x" + _.substr(1), 16); + changed = true; + regen(); + }); + } + { + const tas = settings.addButton("Themes & sounds"); + { + const themes = ["Dark", "WHITE", "Light"]; + tas.addSelect( + "Theme:", + (_) => { + localStorage.setItem("theme", themes[_]); + setTheme(); + }, + themes, + { + defaultIndex: themes.indexOf( + localStorage.getItem("theme") as string + ), + } + ); + } + { + const sounds = Voice.sounds; + tas + .addSelect( + "Notification sound:", + (_) => { + Voice.setNotificationSound(sounds[_]); + }, + sounds, + { defaultIndex: sounds.indexOf(Voice.getNotificationSound()) } + ) + .watchForChange((_) => { + Voice.noises(sounds[_]); + }); + } - { - const userinfos = getBulkInfo(); - tas.addColorInput( - "Accent color:", - (_) => { - userinfos.accent_color = _; - localStorage.setItem("userinfos", JSON.stringify(userinfos)); - document.documentElement.style.setProperty( - "--accent-color", - userinfos.accent_color - ); - }, - { initColor: userinfos.accent_color } - ); - } - } - { - const security = settings.addButton("Account Settings"); - const genSecurity = () => { - security.removeAll(); - if (this.mfa_enabled) { - security.addButtonInput("", "Disable 2FA", () => { - const form = security.addSubForm( - "2FA Disable", - (_: any) => { - if (_.message) { - switch (_.code) { - case 60008: - form.error("code", "Invalid code"); - break; - } - } else { - this.mfa_enabled = false; - security.returnFromSub(); - genSecurity(); - } - }, - { - fetchURL: this.info.api + "/users/@me/mfa/totp/disable", - headers: this.headers, - } - ); - form.addTextInput("Code:", "code", { required: true }); - }); - } else { - security.addButtonInput("", "Enable 2FA", async () => { - let secret = ""; - for (let i = 0; i < 18; i++) { - secret += "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[ - Math.floor(Math.random() * 32) - ]; - } - const form = security.addSubForm( - "2FA Setup", - (_: any) => { - if (_.message) { - switch (_.code) { - case 60008: - form.error("code", "Invalid code"); - break; - case 400: - form.error("password", "Incorrect password"); - break; - } - } else { - genSecurity(); - this.mfa_enabled = true; - security.returnFromSub(); - } - }, - { - fetchURL: this.info.api + "/users/@me/mfa/totp/enable/", - headers: this.headers, - } - ); - form.addTitle( - "Copy this secret into your totp(time-based one time password) app" - ); - form.addText( - `Your secret is: ${secret} and it's 6 digits, with a 30 second token period` - ); - form.addTextInput("Account Password:", "password", { - required: true, - password: true, - }); - form.addTextInput("Code:", "code", { required: true }); - form.setValue("secret", secret); - }); - } - security.addButtonInput("", "Change discriminator", () => { - const form = security.addSubForm( - "Change Discriminator", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("New discriminator:", "discriminator"); - }); - security.addButtonInput("", "Change email", () => { - const form = security.addSubForm( - "Change Email", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("Password:", "password", { password: true }); - if (this.mfa_enabled) { - form.addTextInput("Code:", "code"); - } - form.addTextInput("New email:", "email"); - }); - security.addButtonInput("", "Change username", () => { - const form = security.addSubForm( - "Change Username", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("Password:", "password", { password: true }); - if (this.mfa_enabled) { - form.addTextInput("Code:", "code"); - } - form.addTextInput("New username:", "username"); - }); - security.addButtonInput("", "Change password", () => { - const form = security.addSubForm( - "Change Password", - (_) => { - security.returnFromSub(); - }, - { - fetchURL: this.info.api + "/users/@me/", - headers: this.headers, - method: "PATCH", - } - ); - form.addTextInput("Old password:", "password", { password: true }); - if (this.mfa_enabled) { - form.addTextInput("Code:", "code"); - } - let in1 = ""; - let in2 = ""; - form.addTextInput("New password:", "").watchForChange((text) => { - in1 = text; - }); - const copy = form.addTextInput("New password again:", ""); - copy.watchForChange((text) => { - in2 = text; - }); - form.setValue("new_password", () => { - if (in1 === in2) { - return in1; - } else { - throw new FormError(copy, "Passwords don't match"); - } - }); - }); - }; - genSecurity(); - } - { - const connections = settings.addButton("Connections"); - const connectionContainer = document.createElement("div"); - connectionContainer.id = "connection-container"; + { + const userinfos = getBulkInfo(); + tas.addColorInput( + "Accent color:", + (_) => { + userinfos.accent_color = _; + localStorage.setItem("userinfos", JSON.stringify(userinfos)); + document.documentElement.style.setProperty( + "--accent-color", + userinfos.accent_color + ); + }, + { initColor: userinfos.accent_color } + ); + } + } + { + const security = settings.addButton("Account Settings"); + const genSecurity = () => { + security.removeAll(); + if (this.mfa_enabled) { + security.addButtonInput("", "Disable 2FA", () => { + const form = security.addSubForm( + "2FA Disable", + (_: any) => { + if (_.message) { + switch (_.code) { + case 60008: + form.error("code", "Invalid code"); + break; + } + } else { + this.mfa_enabled = false; + security.returnFromSub(); + genSecurity(); + } + }, + { + fetchURL: this.info.api + "/users/@me/mfa/totp/disable", + headers: this.headers, + } + ); + form.addTextInput("Code:", "code", { required: true }); + }); + } else { + security.addButtonInput("", "Enable 2FA", async () => { + let secret = ""; + for (let i = 0; i < 18; i++) { + secret += "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[ + Math.floor(Math.random() * 32) + ]; + } + const form = security.addSubForm( + "2FA Setup", + (_: any) => { + if (_.message) { + switch (_.code) { + case 60008: + form.error("code", "Invalid code"); + break; + case 400: + form.error("password", "Incorrect password"); + break; + } + } else { + genSecurity(); + this.mfa_enabled = true; + security.returnFromSub(); + } + }, + { + fetchURL: this.info.api + "/users/@me/mfa/totp/enable/", + headers: this.headers, + } + ); + form.addTitle( + "Copy this secret into your totp(time-based one time password) app" + ); + form.addText( + `Your secret is: ${secret} and it's 6 digits, with a 30 second token period` + ); + form.addTextInput("Account Password:", "password", { + required: true, + password: true, + }); + form.addTextInput("Code:", "code", { required: true }); + form.setValue("secret", secret); + }); + } + security.addButtonInput("", "Change discriminator", () => { + const form = security.addSubForm( + "Change Discriminator", + (_) => { + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("New discriminator:", "discriminator"); + }); + security.addButtonInput("", "Change email", () => { + const form = security.addSubForm( + "Change Email", + (_) => { + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("Password:", "password", { password: true }); + if (this.mfa_enabled) { + form.addTextInput("Code:", "code"); + } + form.addTextInput("New email:", "email"); + }); + security.addButtonInput("", "Change username", () => { + const form = security.addSubForm( + "Change Username", + (_) => { + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("Password:", "password", { password: true }); + if (this.mfa_enabled) { + form.addTextInput("Code:", "code"); + } + form.addTextInput("New username:", "username"); + }); + security.addButtonInput("", "Change password", () => { + const form = security.addSubForm( + "Change Password", + (_) => { + security.returnFromSub(); + }, + { + fetchURL: this.info.api + "/users/@me/", + headers: this.headers, + method: "PATCH", + } + ); + form.addTextInput("Old password:", "password", { password: true }); + if (this.mfa_enabled) { + form.addTextInput("Code:", "code"); + } + let in1 = ""; + let in2 = ""; + form.addTextInput("New password:", "").watchForChange((text) => { + in1 = text; + }); + const copy = form.addTextInput("New password again:", ""); + copy.watchForChange((text) => { + in2 = text; + }); + form.setValue("new_password", () => { + if (in1 === in2) { + return in1; + } else { + throw new FormError(copy, "Passwords don't match"); + } + }); + }); + }; + genSecurity(); + } + { + const connections = settings.addButton("Connections"); + const connectionContainer = document.createElement("div"); + connectionContainer.id = "connection-container"; - fetch(this.info.api + "/connections", { - headers: this.headers, - }) - .then((r) => r.json()) - .then((json) => { - Object.keys(json) - .sort((key) => (json[key].enabled ? -1 : 1)) - .forEach((key) => { - const connection = json[key]; + fetch(this.info.api + "/connections", { + headers: this.headers, + }) + .then((r) => r.json()) + .then((json) => { + Object.keys(json) + .sort((key) => (json[key].enabled ? -1 : 1)) + .forEach((key) => { + const connection = json[key]; - const container = document.createElement("div"); - container.textContent = - key.charAt(0).toUpperCase() + key.slice(1); + const container = document.createElement("div"); + container.textContent = + key.charAt(0).toUpperCase() + key.slice(1); - if (connection.enabled) { - container.addEventListener("click", async () => { - const connectionRes = await fetch( - this.info.api + "/connections/" + key + "/authorize", - { - headers: this.headers, - } - ); - const connectionJSON = await connectionRes.json(); - window.open( - connectionJSON.url, - "_blank", - "noopener noreferrer" - ); - }); - } else { - container.classList.add("disabled"); - container.title = - "This connection has been disabled server-side."; - } + if (connection.enabled) { + container.addEventListener("click", async () => { + const connectionRes = await fetch( + this.info.api + "/connections/" + key + "/authorize", + { + headers: this.headers, + } + ); + const connectionJSON = await connectionRes.json(); + window.open( + connectionJSON.url, + "_blank", + "noopener noreferrer" + ); + }); + } else { + container.classList.add("disabled"); + container.title = + "This connection has been disabled server-side."; + } - connectionContainer.appendChild(container); - }); - }); - connections.addHTMLArea(connectionContainer); - } - { - const devPortal = settings.addButton("Developer Portal"); + connectionContainer.appendChild(container); + }); + }); + connections.addHTMLArea(connectionContainer); + } + { + const devPortal = settings.addButton("Developer Portal"); - const teamsRes = await fetch(this.info.api + "/teams", { - headers: this.headers, - }); - const teams = await teamsRes.json(); + const teamsRes = await fetch(this.info.api + "/teams", { + headers: this.headers, + }); + const teams = await teamsRes.json(); - devPortal.addButtonInput("", "Create application", () => { - const form = devPortal.addSubForm( - "Create application", - (json: any) => { - if (json.message) form.error("name", json.message); - else { - devPortal.returnFromSub(); - this.manageApplication(json.id); - } - }, - { - fetchURL: this.info.api + "/applications", - headers: this.headers, - method: "POST", - } - ); + devPortal.addButtonInput("", "Create application", () => { + const form = devPortal.addSubForm( + "Create application", + (json: any) => { + if (json.message) form.error("name", json.message); + else { + devPortal.returnFromSub(); + this.manageApplication(json.id); + } + }, + { + fetchURL: this.info.api + "/applications", + headers: this.headers, + method: "POST", + } + ); - form.addTextInput("Name", "name", { required: true }); - form.addSelect( - "Team", - "team_id", - ["Personal", ...teams.map((team: { name: string }) => team.name)], - { - defaultIndex: 0, - } - ); - }); + form.addTextInput("Name", "name", { required: true }); + form.addSelect( + "Team", + "team_id", + ["Personal", ...teams.map((team: { name: string }) => team.name)], + { + defaultIndex: 0, + } + ); + }); - const appListContainer = document.createElement("div"); - appListContainer.id = "app-list-container"; - fetch(this.info.api + "/applications", { - headers: this.headers, - }) - .then((r) => r.json()) - .then((json) => { - json.forEach( - (application: { - cover_image: any; - icon: any; - id: string | undefined; - name: string | number; - bot: any; - }) => { - const container = document.createElement("div"); + const appListContainer = document.createElement("div"); + appListContainer.id = "app-list-container"; + fetch(this.info.api + "/applications", { + headers: this.headers, + }) + .then((r) => r.json()) + .then((json) => { + json.forEach( + (application: { + cover_image: any; + icon: any; + id: string | undefined; + name: string | number; + bot: any; + }) => { + const container = document.createElement("div"); - if (application.cover_image || application.icon) { - const cover = document.createElement("img"); - cover.crossOrigin = "anonymous"; - cover.src = - this.info.cdn + - "/app-icons/" + - application.id + - "/" + - (application.cover_image || application.icon) + - ".png?size=256"; - cover.alt = ""; - cover.loading = "lazy"; - container.appendChild(cover); - } + if (application.cover_image || application.icon) { + const cover = document.createElement("img"); + cover.crossOrigin = "anonymous"; + cover.src = + this.info.cdn + + "/app-icons/" + + application.id + + "/" + + (application.cover_image || application.icon) + + ".png?size=256"; + cover.alt = ""; + cover.loading = "lazy"; + container.appendChild(cover); + } - const name = document.createElement("h2"); - name.textContent = - application.name + (application.bot ? " (Bot)" : ""); - container.appendChild(name); + const name = document.createElement("h2"); + name.textContent = + application.name + (application.bot ? " (Bot)" : ""); + container.appendChild(name); - container.addEventListener("click", async () => { - this.manageApplication(application.id); - }); - appListContainer.appendChild(container); - } - ); - }); - devPortal.addHTMLArea(appListContainer); - } - settings.show(); - } - async manageApplication(appId = "") { - const res = await fetch(this.info.api + "/applications/" + appId, { - headers: this.headers, - }); - const json = await res.json(); + container.addEventListener("click", async () => { + this.manageApplication(application.id); + }); + appListContainer.appendChild(container); + } + ); + }); + devPortal.addHTMLArea(appListContainer); + } + settings.show(); + } + async manageApplication(appId = "") { + const res = await fetch(this.info.api + "/applications/" + appId, { + headers: this.headers, + }); + const json = await res.json(); - const fields: any = {}; - const appDialog = new Dialog([ - "vdiv", - ["title", "Editing " + json.name], - [ - "vdiv", - [ - "textbox", - "Application name:", - json.name, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.name = target.value; - }, - ], - [ - "mdbox", - "Description:", - json.description, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.description = target.value; - }, - ], - [ - "vdiv", - json.icon - ? [ - "img", - this.info.cdn + - "/app-icons/" + - appId + - "/" + - json.icon + - ".png?size=128", - [128, 128], - ] - : ["text", "No icon"], - [ - "fileupload", - "Application icon:", - (event) => { - const reader = new FileReader(); - const files = (event.target as HTMLInputElement).files; - if (files) { - reader.readAsDataURL(files[0]); - reader.onload = () => { - fields.icon = reader.result; - }; - } - }, - ], - ], - ], - [ - "hdiv", - [ - "textbox", - "Privacy policy URL:", - json.privacy_policy_url || "", - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.privacy_policy_url = target.value; - }, - ], - [ - "textbox", - "Terms of Service URL:", - json.terms_of_service_url || "", - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.terms_of_service_url = target.value; - }, - ], - ], - [ - "hdiv", - [ - "checkbox", - "Make bot publicly inviteable?", - json.bot_public, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.bot_public = target.checked; - }, - ], - [ - "checkbox", - "Require code grant to invite the bot?", - json.bot_require_code_grant, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.bot_require_code_grant = target.checked; - }, - ], - ], - [ - "hdiv", - [ - "button", - "", - "Save changes", - async () => { - const updateRes = await fetch( - this.info.api + "/applications/" + appId, - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields), - } - ); - if (updateRes.ok) appDialog.hide(); - else { - const updateJSON = await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - }, - ], - [ - "button", - "", - (json.bot ? "Manage" : "Add") + " bot", - async () => { - if (!json.bot) { - if ( - !confirm( - "Are you sure you want to add a bot to this application? There's no going back." - ) - ) - return; + const fields: any = {}; + const appDialog = new Dialog([ + "vdiv", + ["title", "Editing " + json.name], + [ + "vdiv", + [ + "textbox", + "Application name:", + json.name, + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.name = target.value; + }, + ], + [ + "mdbox", + "Description:", + json.description, + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.description = target.value; + }, + ], + [ + "vdiv", + json.icon + ? [ + "img", + this.info.cdn + + "/app-icons/" + + appId + + "/" + + json.icon + + ".png?size=128", + [128, 128], + ] + : ["text", "No icon"], + [ + "fileupload", + "Application icon:", + (event) => { + const reader = new FileReader(); + const files = (event.target as HTMLInputElement).files; + if (files) { + reader.readAsDataURL(files[0]); + reader.onload = () => { + fields.icon = reader.result; + }; + } + }, + ], + ], + ], + [ + "hdiv", + [ + "textbox", + "Privacy policy URL:", + json.privacy_policy_url || "", + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.privacy_policy_url = target.value; + }, + ], + [ + "textbox", + "Terms of Service URL:", + json.terms_of_service_url || "", + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.terms_of_service_url = target.value; + }, + ], + ], + [ + "hdiv", + [ + "checkbox", + "Make bot publicly inviteable?", + json.bot_public, + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.bot_public = target.checked; + }, + ], + [ + "checkbox", + "Require code grant to invite the bot?", + json.bot_require_code_grant, + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.bot_require_code_grant = target.checked; + }, + ], + ], + [ + "hdiv", + [ + "button", + "", + "Save changes", + async () => { + const updateRes = await fetch( + this.info.api + "/applications/" + appId, + { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(fields), + } + ); + if (updateRes.ok) appDialog.hide(); + else { + const updateJSON = await updateRes.json(); + alert("An error occurred: " + updateJSON.message); + } + }, + ], + [ + "button", + "", + (json.bot ? "Manage" : "Add") + " bot", + async () => { + if (!json.bot) { + if ( + !confirm( + "Are you sure you want to add a bot to this application? There's no going back." + ) + ) + return; - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot", - { - method: "POST", - headers: this.headers, - } - ); - const updateJSON = await updateRes.json(); - alert("Bot token:\n" + updateJSON.token); - } + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot", + { + method: "POST", + headers: this.headers, + } + ); + const updateJSON = await updateRes.json(); + alert("Bot token:\n" + updateJSON.token); + } - appDialog.hide(); - this.manageBot(appId); - }, - ], - ], - ]); - appDialog.show(); - } - async manageBot(appId = "") { - const res = await fetch(this.info.api + "/applications/" + appId, { - headers: this.headers, - }); - const json = await res.json(); - if (!json.bot) - return alert( - "For some reason, this application doesn't have a bot (yet)." - ); + appDialog.hide(); + this.manageBot(appId); + }, + ], + ], + ]); + appDialog.show(); + } + async manageBot(appId = "") { + const res = await fetch(this.info.api + "/applications/" + appId, { + headers: this.headers, + }); + const json = await res.json(); + if (!json.bot) + return alert( + "For some reason, this application doesn't have a bot (yet)." + ); - const fields: any = { - username: json.bot.username, - avatar: json.bot.avatar - ? this.info.cdn + - "/app-icons/" + - appId + - "/" + - json.bot.avatar + - ".png?size=256" - : "", - }; - const botDialog = new Dialog([ - "vdiv", - ["title", "Editing bot: " + json.bot.username], - [ - "hdiv", - [ - "textbox", - "Bot username:", - json.bot.username, - (event: Event) => { - const target = event.target as HTMLInputElement; - fields.username = target.value; - }, - ], - [ - "vdiv", - fields.avatar - ? ["img", fields.avatar, [128, 128]] - : ["text", "No avatar"], - [ - "fileupload", - "Bot avatar:", - (event) => { - const reader = new FileReader(); - const files = (event.target as HTMLInputElement).files; - if (files) { - const file = files[0]; - reader.readAsDataURL(file); - reader.onload = () => { - fields.avatar = reader.result; - }; - } - }, - ], - ], - ], - [ - "hdiv", - [ - "button", - "", - "Save changes", - async () => { - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot", - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify(fields), - } - ); - if (updateRes.ok) botDialog.hide(); - else { - const updateJSON = await updateRes.json(); - alert("An error occurred: " + updateJSON.message); - } - }, - ], - [ - "button", - "", - "Reset token", - async () => { - if ( - !confirm( - "Are you sure you want to reset the bot token? Your bot will stop working until you update it." - ) - ) - return; + const fields: any = { + username: json.bot.username, + avatar: json.bot.avatar + ? this.info.cdn + + "/app-icons/" + + appId + + "/" + + json.bot.avatar + + ".png?size=256" + : "", + }; + const botDialog = new Dialog([ + "vdiv", + ["title", "Editing bot: " + json.bot.username], + [ + "hdiv", + [ + "textbox", + "Bot username:", + json.bot.username, + (event: Event) => { + const target = event.target as HTMLInputElement; + fields.username = target.value; + }, + ], + [ + "vdiv", + fields.avatar + ? ["img", fields.avatar, [128, 128]] + : ["text", "No avatar"], + [ + "fileupload", + "Bot avatar:", + (event) => { + const reader = new FileReader(); + const files = (event.target as HTMLInputElement).files; + if (files) { + const file = files[0]; + reader.readAsDataURL(file); + reader.onload = () => { + fields.avatar = reader.result; + }; + } + }, + ], + ], + ], + [ + "hdiv", + [ + "button", + "", + "Save changes", + async () => { + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot", + { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(fields), + } + ); + if (updateRes.ok) botDialog.hide(); + else { + const updateJSON = await updateRes.json(); + alert("An error occurred: " + updateJSON.message); + } + }, + ], + [ + "button", + "", + "Reset token", + async () => { + if ( + !confirm( + "Are you sure you want to reset the bot token? Your bot will stop working until you update it." + ) + ) + return; - const updateRes = await fetch( - this.info.api + "/applications/" + appId + "/bot/reset", - { - method: "POST", - headers: this.headers, - } - ); - const updateJSON = await updateRes.json(); - alert("New token:\n" + updateJSON.token); - botDialog.hide(); - }, - ], - ], - ]); - botDialog.show(); - } + const updateRes = await fetch( + this.info.api + "/applications/" + appId + "/bot/reset", + { + method: "POST", + headers: this.headers, + } + ); + const updateJSON = await updateRes.json(); + alert("New token:\n" + updateJSON.token); + botDialog.hide(); + }, + ], + ], + ]); + botDialog.show(); + } - //---------- resolving members code ----------- - readonly waitingmembers: Map< - string, - Map void> - > = new Map(); - readonly presences: Map = new Map(); - async resolvemember( - id: string, - guildid: string - ): Promise { - if (guildid === "@me") { - return undefined; - } - const guild = this.guildids.get(guildid); - const borked = true; - if (borked && guild && guild.member_count > 250) { - //sorry puyo, I need to fix member resolving while it's broken on large guilds - try { - const req = await fetch( - this.info.api + "/guilds/" + guild.id + "/members/" + id, - { - headers: this.headers, - } - ); - if (req.status !== 200) { - return undefined; - } - return await req.json(); - } catch { - return undefined; - } - } - let guildmap = this.waitingmembers.get(guildid); - if (!guildmap) { - guildmap = new Map(); - this.waitingmembers.set(guildid, guildmap); - } - const promise: Promise = new Promise((res) => { - guildmap.set(id, res); - this.getmembers(); - }); - return await promise; - } - fetchingmembers: Map = new Map(); - noncemap: Map void> = new Map(); - noncebuild: Map = new Map(); - async gotChunk(chunk: { - chunk_index: number; - chunk_count: number; - nonce: string; - not_found?: string[]; - members?: memberjson[]; - presences: presencejson[]; - }) { - for (const thing of chunk.presences) { - if (thing.user) { - this.presences.set(thing.user.id, thing); - } - } - chunk.members ??= []; - const arr = this.noncebuild.get(chunk.nonce); - if (!arr) return; - arr[0] = arr[0].concat(chunk.members); - if (chunk.not_found) { - arr[1] = chunk.not_found; - } - arr[2].push(chunk.chunk_index); - if (arr[2].length === chunk.chunk_count) { - this.noncebuild.delete(chunk.nonce); - const func = this.noncemap.get(chunk.nonce); - if (!func) return; - func([arr[0], arr[1]]); - this.noncemap.delete(chunk.nonce); - } - } - async getmembers() { - const promise = new Promise((res) => { - setTimeout(res, 10); - }); - await promise; //allow for more to be sent at once :P - if (this.ws) { - this.waitingmembers.forEach(async (value, guildid) => { - const keys = value.keys(); - if (this.fetchingmembers.has(guildid)) { - return; - } - const build: string[] = []; - for (const key of keys) { - build.push(key); - if (build.length === 100) { - break; - } - } - if (!build.length) { - this.waitingmembers.delete(guildid); - return; - } - const promise: Promise<[memberjson[], string[]]> = new Promise( - (res) => { - const nonce = "" + Math.floor(Math.random() * 100000000000); - this.noncemap.set(nonce, res); - this.noncebuild.set(nonce, [[], [], []]); - if (!this.ws) return; - this.ws.send( - JSON.stringify({ - op: 8, - d: { - user_ids: build, - guild_id: guildid, - limit: 100, - nonce, - presences: true, - }, - }) - ); - this.fetchingmembers.set(guildid, true); - } - ); - const prom = await promise; - const data = prom[0]; - for (const thing of data) { - if (value.has(thing.id)) { - const func = value.get(thing.id); - if (!func) { - value.delete(thing.id); - continue; - } - func(thing); - value.delete(thing.id); - } - } - for (const thing of prom[1]) { - if (value.has(thing)) { - const func = value.get(thing); - if (!func) { - value.delete(thing); - continue; - } - func(undefined); - value.delete(thing); - } - } - this.fetchingmembers.delete(guildid); - this.getmembers(); - }); - } - } - async pingEndpoint() { - const userInfo = getBulkInfo(); - if (!userInfo.instances) userInfo.instances = {}; - const wellknown = this.info.wellknown; - if (!userInfo.instances[wellknown]) { - const pingRes = await fetch(this.info.api + "/ping"); - const pingJSON = await pingRes.json(); - userInfo.instances[wellknown] = pingJSON; - localStorage.setItem("userinfos", JSON.stringify(userInfo)); - } - this.instancePing = userInfo.instances[wellknown].instance; + //---------- resolving members code ----------- + readonly waitingmembers: Map< + string, + Map void> + > = new Map(); + readonly presences: Map = new Map(); + async resolvemember( + id: string, + guildid: string + ): Promise { + if (guildid === "@me") { + return undefined; + } + const guild = this.guildids.get(guildid); + const borked = true; + if (borked && guild && guild.member_count > 250) { + //sorry puyo, I need to fix member resolving while it's broken on large guilds + try { + const req = await fetch( + this.info.api + "/guilds/" + guild.id + "/members/" + id, + { + headers: this.headers, + } + ); + if (req.status !== 200) { + return undefined; + } + return await req.json(); + } catch { + return undefined; + } + } + let guildmap = this.waitingmembers.get(guildid); + if (!guildmap) { + guildmap = new Map(); + this.waitingmembers.set(guildid, guildmap); + } + const promise: Promise = new Promise((res) => { + guildmap.set(id, res); + this.getmembers(); + }); + return await promise; + } + fetchingmembers: Map = new Map(); + noncemap: Map void> = new Map(); + noncebuild: Map = new Map(); + async gotChunk(chunk: { + chunk_index: number; + chunk_count: number; + nonce: string; + not_found?: string[]; + members?: memberjson[]; + presences: presencejson[]; + }) { + for (const thing of chunk.presences) { + if (thing.user) { + this.presences.set(thing.user.id, thing); + } + } + chunk.members ??= []; + const arr = this.noncebuild.get(chunk.nonce); + if (!arr) return; + arr[0] = arr[0].concat(chunk.members); + if (chunk.not_found) { + arr[1] = chunk.not_found; + } + arr[2].push(chunk.chunk_index); + if (arr[2].length === chunk.chunk_count) { + this.noncebuild.delete(chunk.nonce); + const func = this.noncemap.get(chunk.nonce); + if (!func) return; + func([arr[0], arr[1]]); + this.noncemap.delete(chunk.nonce); + } + } + async getmembers() { + const promise = new Promise((res) => { + setTimeout(res, 10); + }); + await promise; //allow for more to be sent at once :P + if (this.ws) { + this.waitingmembers.forEach(async (value, guildid) => { + const keys = value.keys(); + if (this.fetchingmembers.has(guildid)) { + return; + } + const build: string[] = []; + for (const key of keys) { + build.push(key); + if (build.length === 100) { + break; + } + } + if (!build.length) { + this.waitingmembers.delete(guildid); + return; + } + const promise: Promise<[memberjson[], string[]]> = new Promise( + (res) => { + const nonce = "" + Math.floor(Math.random() * 100000000000); + this.noncemap.set(nonce, res); + this.noncebuild.set(nonce, [[], [], []]); + if (!this.ws) return; + this.ws.send( + JSON.stringify({ + op: 8, + d: { + user_ids: build, + guild_id: guildid, + limit: 100, + nonce, + presences: true, + }, + }) + ); + this.fetchingmembers.set(guildid, true); + } + ); + const prom = await promise; + const data = prom[0]; + for (const thing of data) { + if (value.has(thing.id)) { + const func = value.get(thing.id); + if (!func) { + value.delete(thing.id); + continue; + } + func(thing); + value.delete(thing.id); + } + } + for (const thing of prom[1]) { + if (value.has(thing)) { + const func = value.get(thing); + if (!func) { + value.delete(thing); + continue; + } + func(undefined); + value.delete(thing); + } + } + this.fetchingmembers.delete(guildid); + this.getmembers(); + }); + } + } + async pingEndpoint() { + const userInfo = getBulkInfo(); + if (!userInfo.instances) userInfo.instances = {}; + const wellknown = this.info.wellknown; + if (!userInfo.instances[wellknown]) { + const pingRes = await fetch(this.info.api + "/ping"); + const pingJSON = await pingRes.json(); + userInfo.instances[wellknown] = pingJSON; + localStorage.setItem("userinfos", JSON.stringify(userInfo)); + } + this.instancePing = userInfo.instances[wellknown].instance; - this.pageTitle("Loading..."); - } - pageTitle(channelName = "", guildName = "") { - (document.getElementById("channelname") as HTMLSpanElement).textContent = - channelName; - ( - document.getElementsByTagName("title")[0] as HTMLTitleElement - ).textContent = - channelName + - (guildName ? " | " + guildName : "") + - " | " + - this.instancePing.name + - " | Jank Client"; - } - async instanceStats() { - const res = await fetch(this.info.api + "/policies/stats", { - headers: this.headers, - }); - const json = await res.json(); + this.pageTitle("Loading..."); + } + pageTitle(channelName = "", guildName = "") { + (document.getElementById("channelname") as HTMLSpanElement).textContent = + channelName; + ( + document.getElementsByTagName("title")[0] as HTMLTitleElement + ).textContent = + channelName + + (guildName ? " | " + guildName : "") + + " | " + + this.instancePing.name + + " | Jank Client"; + } + async instanceStats() { + const res = await fetch(this.info.api + "/policies/stats", { + headers: this.headers, + }); + const json = await res.json(); - const dialog = new Dialog([ - "vdiv", - ["title", "Instance stats: " + this.instancePing.name], - ["text", "Registered users: " + json.counts.user], - ["text", "Servers: " + json.counts.guild], - ["text", "Messages: " + json.counts.message], - ["text", "Members: " + json.counts.members], - ]); - dialog.show(); - } -} -export { Localuser }; + const dialog = new Dialog([ + "vdiv", + ["title", "Instance stats: " + this.instancePing.name], + ["text", "Registered users: " + json.counts.user], + ["text", "Servers: " + json.counts.guild], + ["text", "Messages: " + json.counts.message], + ["text", "Members: " + json.counts.members], + ]); + dialog.show(); + } + } + export { Localuser }; diff --git a/src/webpage/login.ts b/src/webpage/login.ts index 5e53c99..c9c308f 100644 --- a/src/webpage/login.ts +++ b/src/webpage/login.ts @@ -3,623 +3,623 @@ import { Dialog } from "./dialog.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); function setTheme() { - let name = localStorage.getItem("theme"); - if (!name) { - localStorage.setItem("theme", "Dark"); - name = "Dark"; - } - document.body.className = name + "-theme"; +let name = localStorage.getItem("theme"); +if (!name) { +localStorage.setItem("theme", "Dark"); +name = "Dark"; +} +document.body.className = name + "-theme"; } let instances: - | { - name: string; - description?: string; - descriptionLong?: string; - image?: string; - url?: string; - display?: boolean; - online?: boolean; - uptime: { alltime: number; daytime: number; weektime: number }; - urls: { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login?: string; - }; - }[] - | null; +| { +name: string; +description?: string; +descriptionLong?: string; +image?: string; +url?: string; +display?: boolean; +online?: boolean; +uptime: { alltime: number; daytime: number; weektime: number }; +urls: { +wellknown: string; +api: string; +cdn: string; +gateway: string; +login?: string; +}; +}[] +| null; setTheme(); function getBulkUsers() { - const json = getBulkInfo(); - for (const thing in json.users) { - json.users[thing] = new Specialuser(json.users[thing]); - } - return json; +const json = getBulkInfo(); +for (const thing in json.users) { +json.users[thing] = new Specialuser(json.users[thing]); +} +return json; } function trimswitcher() { - const json = getBulkInfo(); - const map = new Map(); - for (const thing in json.users) { - const user = json.users[thing]; - let wellknown = user.serverurls.wellknown; - if (wellknown.at(-1) !== "/") { - wellknown += "/"; - } - wellknown += user.username; - if (map.has(wellknown)) { - const otheruser = map.get(wellknown); - if (otheruser[1].serverurls.wellknown.at(-1) === "/") { - delete json.users[otheruser[0]]; - map.set(wellknown, [thing, user]); - } else { - delete json.users[thing]; - } - } else { - map.set(wellknown, [thing, user]); - } - } - for (const thing in json.users) { - if (thing.at(-1) === "/") { - const user = json.users[thing]; - delete json.users[thing]; - json.users[thing.slice(0, -1)] = user; - } - } - localStorage.setItem("userinfos", JSON.stringify(json)); - console.log(json); +const json = getBulkInfo(); +const map = new Map(); +for (const thing in json.users) { +const user = json.users[thing]; +let wellknown = user.serverurls.wellknown; +if (wellknown.at(-1) !== "/") { +wellknown += "/"; +} +wellknown += user.username; +if (map.has(wellknown)) { +const otheruser = map.get(wellknown); +if (otheruser[1].serverurls.wellknown.at(-1) === "/") { +delete json.users[otheruser[0]]; +map.set(wellknown, [thing, user]); +} else { +delete json.users[thing]; +} +} else { +map.set(wellknown, [thing, user]); +} +} +for (const thing in json.users) { +if (thing.at(-1) === "/") { +const user = json.users[thing]; +delete json.users[thing]; +json.users[thing.slice(0, -1)] = user; +} +} +localStorage.setItem("userinfos", JSON.stringify(json)); +console.log(json); } function getBulkInfo() { - return JSON.parse(localStorage.getItem("userinfos")!); +return JSON.parse(localStorage.getItem("userinfos")!); } function setDefaults() { - let userinfos = getBulkInfo(); - if (!userinfos) { - localStorage.setItem( - "userinfos", - JSON.stringify({ - currentuser: null, - users: {}, - preferences: { - theme: "Dark", - notifications: false, - notisound: "three", - }, - }) - ); - userinfos = getBulkInfo(); - } - if (userinfos.users === undefined) { - userinfos.users = {}; - } - if (userinfos.accent_color === undefined) { - userinfos.accent_color = "#242443"; - } - document.documentElement.style.setProperty( - "--accent-color", - userinfos.accent_color - ); - if (userinfos.preferences === undefined) { - userinfos.preferences = { - theme: "Dark", - notifications: false, - notisound: "three", - }; - } - if (userinfos.preferences && userinfos.preferences.notisound === undefined) { - userinfos.preferences.notisound = "three"; - } - localStorage.setItem("userinfos", JSON.stringify(userinfos)); +let userinfos = getBulkInfo(); +if (!userinfos) { +localStorage.setItem( +"userinfos", +JSON.stringify({ +currentuser: null, +users: {}, +preferences: { +theme: "Dark", +notifications: false, +notisound: "three", +}, +}) +); +userinfos = getBulkInfo(); +} +if (userinfos.users === undefined) { +userinfos.users = {}; +} +if (userinfos.accent_color === undefined) { +userinfos.accent_color = "#242443"; +} +document.documentElement.style.setProperty( +"--accent-color", +userinfos.accent_color +); +if (userinfos.preferences === undefined) { +userinfos.preferences = { +theme: "Dark", +notifications: false, +notisound: "three", +}; +} +if (userinfos.preferences && userinfos.preferences.notisound === undefined) { +userinfos.preferences.notisound = "three"; +} +localStorage.setItem("userinfos", JSON.stringify(userinfos)); } setDefaults(); class Specialuser { - serverurls: { - api: string; - cdn: string; - gateway: string; - wellknown: string; - login: string; - }; - email: string; - token: string; - loggedin; - json; - constructor(json: any) { - if (json instanceof Specialuser) { - console.error("specialuser can't construct from another specialuser"); - } - this.serverurls = json.serverurls; - let apistring = new URL(json.serverurls.api).toString(); - apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; - this.serverurls.api = apistring; - this.serverurls.cdn = new URL(json.serverurls.cdn) - .toString() - .replace(/\/$/, ""); - this.serverurls.gateway = new URL(json.serverurls.gateway) - .toString() - .replace(/\/$/, ""); - this.serverurls.wellknown = new URL(json.serverurls.wellknown) - .toString() - .replace(/\/$/, ""); - this.serverurls.login = new URL(json.serverurls.login) - .toString() - .replace(/\/$/, ""); - this.email = json.email; - this.token = json.token; - this.loggedin = json.loggedin; - this.json = json; - this.json.localuserStore ??= {}; - if (!this.serverurls || !this.email || !this.token) { - console.error( - "There are fundamentally missing pieces of info missing from this user" - ); - } - } - set pfpsrc(e) { - this.json.pfpsrc = e; - this.updateLocal(); - } - get pfpsrc() { - return this.json.pfpsrc; - } - set username(e) { - this.json.username = e; - this.updateLocal(); - } - get username() { - return this.json.username; - } - set localuserStore(e) { - this.json.localuserStore = e; - this.updateLocal(); - } - get localuserStore() { - return this.json.localuserStore; - } - get uid() { - return this.email + this.serverurls.wellknown; - } - toJSON() { - return this.json; - } - updateLocal() { - const info = getBulkInfo(); - info.users[this.uid] = this.toJSON(); - localStorage.setItem("userinfos", JSON.stringify(info)); - } +serverurls: { +api: string; +cdn: string; +gateway: string; +wellknown: string; +login: string; +}; +email: string; +token: string; +loggedin; +json; +constructor(json: any) { +if (json instanceof Specialuser) { +console.error("specialuser can't construct from another specialuser"); +} +this.serverurls = json.serverurls; +let apistring = new URL(json.serverurls.api).toString(); +apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; +this.serverurls.api = apistring; +this.serverurls.cdn = new URL(json.serverurls.cdn) +.toString() +.replace(/\/$/, ""); +this.serverurls.gateway = new URL(json.serverurls.gateway) +.toString() +.replace(/\/$/, ""); +this.serverurls.wellknown = new URL(json.serverurls.wellknown) +.toString() +.replace(/\/$/, ""); +this.serverurls.login = new URL(json.serverurls.login) +.toString() +.replace(/\/$/, ""); +this.email = json.email; +this.token = json.token; +this.loggedin = json.loggedin; +this.json = json; +this.json.localuserStore ??= {}; +if (!this.serverurls || !this.email || !this.token) { +console.error( +"There are fundamentally missing pieces of info missing from this user" +); +} +} +set pfpsrc(e) { +this.json.pfpsrc = e; +this.updateLocal(); +} +get pfpsrc() { +return this.json.pfpsrc; +} +set username(e) { +this.json.username = e; +this.updateLocal(); +} +get username() { +return this.json.username; +} +set localuserStore(e) { +this.json.localuserStore = e; +this.updateLocal(); +} +get localuserStore() { +return this.json.localuserStore; +} +get uid() { +return this.email + this.serverurls.wellknown; +} +toJSON() { +return this.json; +} +updateLocal() { +const info = getBulkInfo(); +info.users[this.uid] = this.toJSON(); +localStorage.setItem("userinfos", JSON.stringify(info)); +} } function adduser(user: typeof Specialuser.prototype.json) { - user = new Specialuser(user); - const info = getBulkInfo(); - info.users[user.uid] = user; - info.currentuser = user.uid; - localStorage.setItem("userinfos", JSON.stringify(info)); - return user; +user = new Specialuser(user); +const info = getBulkInfo(); +info.users[user.uid] = user; +info.currentuser = user.uid; +localStorage.setItem("userinfos", JSON.stringify(info)); +return user; } const instancein = document.getElementById("instancein") as HTMLInputElement; let timeout: string | number | NodeJS.Timeout | undefined; // let instanceinfo; const stringURLMap = new Map(); -const stringURLsMap = new Map< - string, - { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login?: string; - } ->(); -async function getapiurls(str: string): Promise< - | { - api: string; - cdn: string; - gateway: string; - wellknown: string; - login: string; - } - | false -> { - if (!URL.canParse(str)) { - const val = stringURLMap.get(str); - if (val) { - str = val; - } else { - const val = stringURLsMap.get(str); - if (val) { - const responce = await fetch( - val.api + val.api.endsWith("/") ? "" : "/" + "ping" - ); - if (responce.ok) { - if (val.login) { - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - } else { - val.login = val.api; - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - } - } - } - } - } - if (str.at(-1) !== "/") { - str += "/"; - } - let api: string; - try { - const info = await fetch(`${str}/.well-known/spacebar`).then((x) => - x.json() - ); - api = info.api; - } catch { - return false; - } - const url = new URL(api); - try { - const info = await fetch( - `${api}${ - url.pathname.includes("api") ? "" : "api" - }/policies/instance/domains` - ).then((x) => x.json()); - return { - api: info.apiEndpoint, - gateway: info.gateway, - cdn: info.cdn, - wellknown: str, - login: url.toString(), - }; - } catch { - const val = stringURLsMap.get(str); - if (val) { - const responce = await fetch( - val.api + val.api.endsWith("/") ? "" : "/" + "ping" - ); - if (responce.ok) { - if (val.login) { - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - } else { - val.login = val.api; - return val as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - }; - } - } - } - return false; - } -} -async function checkInstance(instance?: string) { - const verify = document.getElementById("verify"); - try { - verify!.textContent = "Checking Instance"; - const instanceValue = instance || (instancein as HTMLInputElement).value; - const instanceinfo = (await getapiurls(instanceValue)) as { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login: string; - value: string; - }; - if (instanceinfo) { - instanceinfo.value = instanceValue; - localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); - verify!.textContent = "Instance is all good"; - // @ts-ignore - if (checkInstance.alt) { - // @ts-ignore - checkInstance.alt(); - } - setTimeout((_: any) => { - console.log(verify!.textContent); - verify!.textContent = ""; - }, 3000); - } else { - verify!.textContent = "Invalid Instance, try again"; - } - } catch { - console.log("catch"); - verify!.textContent = "Invalid Instance, try again"; - } -} + const stringURLsMap = new Map< + string, + { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login?: string; + } + >(); + async function getapiurls(str: string): Promise< + | { + api: string; + cdn: string; + gateway: string; + wellknown: string; + login: string; + } + | false + > { + if (!URL.canParse(str)) { + const val = stringURLMap.get(str); + if (val) { + str = val; + } else { + const val = stringURLsMap.get(str); + if (val) { + const responce = await fetch( + val.api + val.api.endsWith("/") ? "" : "/" + "ping" + ); + if (responce.ok) { + if (val.login) { + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + } else { + val.login = val.api; + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + } + } + } + } + } + if (str.at(-1) !== "/") { + str += "/"; + } + let api: string; + try { + const info = await fetch(`${str}/.well-known/spacebar`).then((x) => + x.json() + ); + api = info.api; + } catch { + return false; + } + const url = new URL(api); + try { + const info = await fetch( + `${api}${ + url.pathname.includes("api") ? "" : "api" + }/policies/instance/domains` + ).then((x) => x.json()); + return { + api: info.apiEndpoint, + gateway: info.gateway, + cdn: info.cdn, + wellknown: str, + login: url.toString(), + }; + } catch { + const val = stringURLsMap.get(str); + if (val) { + const responce = await fetch( + val.api + val.api.endsWith("/") ? "" : "/" + "ping" + ); + if (responce.ok) { + if (val.login) { + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + } else { + val.login = val.api; + return val as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + }; + } + } + } + return false; + } + } + async function checkInstance(instance?: string) { + const verify = document.getElementById("verify"); + try { + verify!.textContent = "Checking Instance"; + const instanceValue = instance || (instancein as HTMLInputElement).value; + const instanceinfo = (await getapiurls(instanceValue)) as { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login: string; + value: string; + }; + if (instanceinfo) { + instanceinfo.value = instanceValue; + localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); + verify!.textContent = "Instance is all good"; + // @ts-ignore + if (checkInstance.alt) { + // @ts-ignore + checkInstance.alt(); + } + setTimeout((_: any) => { + console.log(verify!.textContent); + verify!.textContent = ""; + }, 3000); + } else { + verify!.textContent = "Invalid Instance, try again"; + } + } catch { + console.log("catch"); + verify!.textContent = "Invalid Instance, try again"; + } + } -if (instancein) { - console.log(instancein); - instancein.addEventListener("keydown", (_) => { - const verify = document.getElementById("verify"); - verify!.textContent = "Waiting to check Instance"; - clearTimeout(timeout); - timeout = setTimeout(() => checkInstance(), 1000); - }); - if (localStorage.getItem("instanceinfo")) { - const json = JSON.parse(localStorage.getItem("instanceinfo")!); - if (json.value) { - (instancein as HTMLInputElement).value = json.value; - } else { - (instancein as HTMLInputElement).value = json.wellknown; - } - } else { - checkInstance("https://spacebar.chat/"); - } -} + if (instancein) { + console.log(instancein); + instancein.addEventListener("keydown", (_) => { + const verify = document.getElementById("verify"); + verify!.textContent = "Waiting to check Instance"; + clearTimeout(timeout); + timeout = setTimeout(() => checkInstance(), 1000); + }); + if (localStorage.getItem("instanceinfo")) { + const json = JSON.parse(localStorage.getItem("instanceinfo")!); + if (json.value) { + (instancein as HTMLInputElement).value = json.value; + } else { + (instancein as HTMLInputElement).value = json.wellknown; + } + } else { + checkInstance("https://spacebar.chat/"); + } + } -async function login(username: string, password: string, captcha: string) { - if (captcha === "") { - captcha = ""; - } - const options = { - method: "POST", - body: JSON.stringify({ - login: username, - password, - undelete: false, - captcha_key: captcha, - }), - headers: { - "Content-type": "application/json; charset=UTF-8", - }, - }; - try { - const info = JSON.parse(localStorage.getItem("instanceinfo")!); - const api = info.login + (info.login.startsWith("/") ? "/" : ""); - return await fetch(api + "/auth/login", options) - .then((response) => response.json()) - .then((response) => { - console.log(response, response.message); - if (response.message === "Invalid Form Body") { - return response.errors.login._errors[0].message; - console.log("test"); - } - //this.serverurls||!this.email||!this.token - console.log(response); + async function login(username: string, password: string, captcha: string) { + if (captcha === "") { + captcha = ""; + } + const options = { + method: "POST", + body: JSON.stringify({ + login: username, + password, + undelete: false, + captcha_key: captcha, + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + }, + }; + try { + const info = JSON.parse(localStorage.getItem("instanceinfo")!); + const api = info.login + (info.login.startsWith("/") ? "/" : ""); + return await fetch(api + "/auth/login", options) + .then((response) => response.json()) + .then((response) => { + console.log(response, response.message); + if (response.message === "Invalid Form Body") { + return response.errors.login._errors[0].message; + console.log("test"); + } + //this.serverurls||!this.email||!this.token + console.log(response); - if (response.captcha_sitekey) { - const capt = document.getElementById("h-captcha"); - if (!capt!.children.length) { - const capty = document.createElement("div"); - capty.classList.add("h-captcha"); + if (response.captcha_sitekey) { + const capt = document.getElementById("h-captcha"); + if (!capt!.children.length) { + const capty = document.createElement("div"); + capty.classList.add("h-captcha"); - capty.setAttribute("data-sitekey", response.captcha_sitekey); - const script = document.createElement("script"); - script.src = "https://js.hcaptcha.com/1/api.js"; - capt!.append(script); - capt!.append(capty); - } else { - eval("hcaptcha.reset()"); - } - } else { - console.log(response); - if (response.ticket) { - let onetimecode = ""; - new Dialog([ - "vdiv", - ["title", "2FA code:"], - [ - "textbox", - "", - "", - function (this: HTMLInputElement) { - onetimecode = this.value; - }, - ], - [ - "button", - "", - "Submit", - function () { - fetch(api + "/auth/mfa/totp", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - code: onetimecode, - ticket: response.ticket, - }), - }) - .then((r) => r.json()) - .then((response) => { - if (response.message) { - alert(response.message); - } else { - console.warn(response); - if (!response.token) return; - adduser({ - serverurls: JSON.parse( - localStorage.getItem("instanceinfo")! - ), - email: username, - token: response.token, - }).username = username; - const redir = new URLSearchParams( - window.location.search - ).get("goback"); - if (redir) { - window.location.href = redir; - } else { - window.location.href = "/channels/@me"; - } - } - }); - }, - ], - ]).show(); - } else { - console.warn(response); - if (!response.token) return; - adduser({ - serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), - email: username, - token: response.token, - }).username = username; - const redir = new URLSearchParams(window.location.search).get( - "goback" - ); - if (redir) { - window.location.href = redir; - } else { - window.location.href = "/channels/@me"; - } - return ""; - } - } - }); - } catch (error) { - console.error("Error:", error); - } -} + capty.setAttribute("data-sitekey", response.captcha_sitekey); + const script = document.createElement("script"); + script.src = "https://js.hcaptcha.com/1/api.js"; + capt!.append(script); + capt!.append(capty); + } else { + eval("hcaptcha.reset()"); + } + } else { + console.log(response); + if (response.ticket) { + let onetimecode = ""; + new Dialog([ + "vdiv", + ["title", "2FA code:"], + [ + "textbox", + "", + "", + function (this: HTMLInputElement) { + onetimecode = this.value; + }, + ], + [ + "button", + "", + "Submit", + function () { + fetch(api + "/auth/mfa/totp", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + code: onetimecode, + ticket: response.ticket, + }), + }) + .then((r) => r.json()) + .then((response) => { + if (response.message) { + alert(response.message); + } else { + console.warn(response); + if (!response.token) return; + adduser({ + serverurls: JSON.parse( + localStorage.getItem("instanceinfo")! + ), + email: username, + token: response.token, + }).username = username; + const redir = new URLSearchParams( + window.location.search + ).get("goback"); + if (redir) { + window.location.href = redir; + } else { + window.location.href = "/channels/@me"; + } + } + }); + }, + ], + ]).show(); + } else { + console.warn(response); + if (!response.token) return; + adduser({ + serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), + email: username, + token: response.token, + }).username = username; + const redir = new URLSearchParams(window.location.search).get( + "goback" + ); + if (redir) { + window.location.href = redir; + } else { + window.location.href = "/channels/@me"; + } + return ""; + } + } + }); + } catch (error) { + console.error("Error:", error); + } + } -async function check(e: SubmitEvent) { - e.preventDefault(); - const target = e.target as HTMLFormElement; - const h = await login( - (target[1] as HTMLInputElement).value, - (target[2] as HTMLInputElement).value, - (target[3] as HTMLInputElement).value - ); - const wrongElement = document.getElementById("wrong"); - if (wrongElement) { - wrongElement.textContent = h; - } - console.log(h); -} -if (document.getElementById("form")) { - const form = document.getElementById("form"); - if (form) { - form.addEventListener("submit", (e: SubmitEvent) => check(e)); - } -} -//this currently does not work, and need to be implemented better at some time. -/* -if ("serviceWorker" in navigator){ - navigator.serviceWorker.register("/service.js", { - scope: "/", - }).then((registration) => { - let serviceWorker:ServiceWorker; - if (registration.installing) { - serviceWorker = registration.installing; - console.log("installing"); - } else if (registration.waiting) { - serviceWorker = registration.waiting; - console.log("waiting"); - } else if (registration.active) { - serviceWorker = registration.active; - console.log("active"); - } - if (serviceWorker) { - console.log(serviceWorker.state); - serviceWorker.addEventListener("statechange", (e) => { - console.log(serviceWorker.state); - }); - } - }) -} -*/ -const switchurl = document.getElementById("switch") as HTMLAreaElement; -if (switchurl) { - switchurl.href += window.location.search; - const instance = new URLSearchParams(window.location.search).get("instance"); - console.log(instance); - if (instance) { - instancein.value = instance; - checkInstance(""); - } -} -export { checkInstance }; -trimswitcher(); -export { - mobile, - getBulkUsers, - getBulkInfo, - setTheme, - Specialuser, - getapiurls, - adduser, -}; + async function check(e: SubmitEvent) { + e.preventDefault(); + const target = e.target as HTMLFormElement; + const h = await login( + (target[1] as HTMLInputElement).value, + (target[2] as HTMLInputElement).value, + (target[3] as HTMLInputElement).value + ); + const wrongElement = document.getElementById("wrong"); + if (wrongElement) { + wrongElement.textContent = h; + } + console.log(h); + } + if (document.getElementById("form")) { + const form = document.getElementById("form"); + if (form) { + form.addEventListener("submit", (e: SubmitEvent) => check(e)); + } + } + //this currently does not work, and need to be implemented better at some time. + /* + if ("serviceWorker" in navigator){ + navigator.serviceWorker.register("/service.js", { + scope: "/", + }).then((registration) => { + let serviceWorker:ServiceWorker; + if (registration.installing) { + serviceWorker = registration.installing; + console.log("installing"); + } else if (registration.waiting) { + serviceWorker = registration.waiting; + console.log("waiting"); + } else if (registration.active) { + serviceWorker = registration.active; + console.log("active"); + } + if (serviceWorker) { + console.log(serviceWorker.state); + serviceWorker.addEventListener("statechange", (e) => { + console.log(serviceWorker.state); + }); + } + }) + } + */ + const switchurl = document.getElementById("switch") as HTMLAreaElement; + if (switchurl) { + switchurl.href += window.location.search; + const instance = new URLSearchParams(window.location.search).get("instance"); + console.log(instance); + if (instance) { + instancein.value = instance; + checkInstance(""); + } + } + export { checkInstance }; + trimswitcher(); + export { + mobile, + getBulkUsers, + getBulkInfo, + setTheme, + Specialuser, + getapiurls, + adduser, + }; -const datalist = document.getElementById("instances"); -console.warn(datalist); -export function getInstances() { - return instances; -} + const datalist = document.getElementById("instances"); + console.warn(datalist); + export function getInstances() { + return instances; + } -fetch("/instances.json") - .then((_) => _.json()) - .then( - ( - json: { - name: string; - description?: string; - descriptionLong?: string; - image?: string; - url?: string; - display?: boolean; - online?: boolean; - uptime: { alltime: number; daytime: number; weektime: number }; - urls: { - wellknown: string; - api: string; - cdn: string; - gateway: string; - login?: string; - }; - }[] - ) => { - instances = json; - if (datalist) { - console.warn(json); - if (instancein && instancein.value === "") { - instancein.value = json[0].name; - } - for (const instance of json) { - if (instance.display === false) { - continue; - } - const option = document.createElement("option"); - option.disabled = !instance.online; - option.value = instance.name; - if (instance.url) { - stringURLMap.set(option.value, instance.url); - if (instance.urls) { - stringURLsMap.set(instance.url, instance.urls); - } - } else if (instance.urls) { - stringURLsMap.set(option.value, instance.urls); - } else { - option.disabled = true; - } - if (instance.description) { - option.label = instance.description; - } else { - option.label = instance.name; - } - datalist.append(option); - } - checkInstance(""); - } - } - ); + fetch("/instances.json") + .then((_) => _.json()) + .then( + ( + json: { + name: string; + description?: string; + descriptionLong?: string; + image?: string; + url?: string; + display?: boolean; + online?: boolean; + uptime: { alltime: number; daytime: number; weektime: number }; + urls: { + wellknown: string; + api: string; + cdn: string; + gateway: string; + login?: string; + }; + }[] + ) => { + instances = json; + if (datalist) { + console.warn(json); + if (instancein && instancein.value === "") { + instancein.value = json[0].name; + } + for (const instance of json) { + if (instance.display === false) { + continue; + } + const option = document.createElement("option"); + option.disabled = !instance.online; + option.value = instance.name; + if (instance.url) { + stringURLMap.set(option.value, instance.url); + if (instance.urls) { + stringURLsMap.set(instance.url, instance.urls); + } + } else if (instance.urls) { + stringURLsMap.set(option.value, instance.urls); + } else { + option.disabled = true; + } + if (instance.description) { + option.label = instance.description; + } else { + option.label = instance.name; + } + datalist.append(option); + } + checkInstance(""); + } + } + ); diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 444ea24..f16f002 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -6,865 +6,865 @@ import { Localuser } from "./localuser.js"; import { Member } from "./member.js"; class MarkDown { - txt: string[]; - keep: boolean; - stdsize: boolean; - owner: Localuser | Channel; - info: Localuser["info"]; - constructor( - text: string | string[], - owner: MarkDown["owner"], - { keep = false, stdsize = false } = {} - ) { - if (typeof text === typeof "") { - this.txt = (text as string).split(""); - } else { - this.txt = text as string[]; - } - if (this.txt === undefined) { - this.txt = []; - } - this.info = owner.info; - this.keep = keep; - this.owner = owner; - this.stdsize = stdsize; - } - get localuser() { - if (this.owner instanceof Localuser) { - return this.owner; - } else { - return this.owner.localuser; - } - } - get rawString() { - return this.txt.join(""); - } - get textContent() { - return this.makeHTML().textContent; - } - makeHTML({ keep = this.keep, stdsize = this.stdsize } = {}) { - return this.markdown(this.txt, { keep, stdsize }); - } - markdown(text: string | string[], { keep = false, stdsize = false } = {}) { - let txt: string[]; - if (typeof text === typeof "") { - txt = (text as string).split(""); - } else { - txt = text as string[]; - } - if (txt === undefined) { - txt = []; - } - const span = document.createElement("span"); - let current = document.createElement("span"); - function appendcurrent() { - if (current.innerHTML !== "") { - span.append(current); - current = document.createElement("span"); - } - } - for (let i = 0; i < txt.length; i++) { - if (txt[i] === "\n" || i === 0) { - const first = i === 0; - if (first) { - i--; - } - let element: HTMLElement = document.createElement("span"); - let keepys = ""; +txt: string[]; +keep: boolean; +stdsize: boolean; +owner: Localuser | Channel; +info: Localuser["info"]; +constructor( +text: string | string[], +owner: MarkDown["owner"], +{ keep = false, stdsize = false } = {} +) { +if (typeof text === typeof "") { +this.txt = (text as string).split(""); +} else { +this.txt = text as string[]; +} +if (this.txt === undefined) { +this.txt = []; +} +this.info = owner.info; +this.keep = keep; +this.owner = owner; +this.stdsize = stdsize; +} +get localuser() { +if (this.owner instanceof Localuser) { +return this.owner; +} else { +return this.owner.localuser; +} +} +get rawString() { +return this.txt.join(""); +} +get textContent() { +return this.makeHTML().textContent; +} +makeHTML({ keep = this.keep, stdsize = this.stdsize } = {}) { +return this.markdown(this.txt, { keep, stdsize }); +} +markdown(text: string | string[], { keep = false, stdsize = false } = {}) { +let txt: string[]; +if (typeof text === typeof "") { +txt = (text as string).split(""); +} else { +txt = text as string[]; +} +if (txt === undefined) { +txt = []; +} +const span = document.createElement("span"); +let current = document.createElement("span"); +function appendcurrent() { +if (current.innerHTML !== "") { +span.append(current); +current = document.createElement("span"); +} +} +for (let i = 0; i < txt.length; i++) { +if (txt[i] === "\n" || i === 0) { +const first = i === 0; +if (first) { +i--; +} +let element: HTMLElement = document.createElement("span"); +let keepys = ""; - if (txt[i + 1] === "#") { - if (txt[i + 2] === "#") { - if (txt[i + 3] === "#" && txt[i + 4] === " ") { - element = document.createElement("h3"); - keepys = "### "; - i += 5; - } else if (txt[i + 3] === " ") { - element = document.createElement("h2"); - element.classList.add("h2md"); - keepys = "## "; - i += 4; - } - } else if (txt[i + 2] === " ") { - element = document.createElement("h1"); - keepys = "# "; - i += 3; - } - } else if (txt[i + 1] === ">" && txt[i + 2] === " ") { - element = document.createElement("div"); - const line = document.createElement("div"); - line.classList.add("quoteline"); - element.append(line); - element.classList.add("quote"); - keepys = "> "; - i += 3; - } - if (keepys) { - appendcurrent(); - if (!first && !stdsize) { - span.appendChild(document.createElement("br")); - } - const build: string[] = []; - for (; txt[i] !== "\n" && txt[i] !== undefined; i++) { - build.push(txt[i]); - } - try { - if (stdsize) { - element = document.createElement("span"); - } - if (keep) { - element.append(keepys); - //span.appendChild(document.createElement("br")); - } - element.appendChild(this.markdown(build, { keep, stdsize })); - span.append(element); - } finally { - i -= 1; - continue; - } - } - if (first) { - i++; - } - } - if (txt[i] === "\n") { - if (!stdsize) { - appendcurrent(); - span.append(document.createElement("br")); - } - continue; - } - if (txt[i] === "`") { - let count = 1; - if (txt[i + 1] === "`") { - count++; - if (txt[i + 2] === "`") { - count++; - } - } - let build = ""; - if (keep) { - build += "`".repeat(count); - } - let find = 0; - let j = i + count; - let init = true; - for ( - ; - txt[j] !== undefined && - (txt[j] !== "\n" || count === 3) && - find !== count; - j++ - ) { - if (txt[j] === "`") { - find++; - } else { - if (find !== 0) { - build += "`".repeat(find); - find = 0; - } - if (init && count === 3) { - if (txt[j] === " " || txt[j] === "\n") { - init = false; - } - if (keep) { - build += txt[j]; - } - continue; - } - build += txt[j]; - } - } - if (stdsize) { - build = build.replaceAll("\n", ""); - } - if (find === count) { - appendcurrent(); - i = j; - if (keep) { - build += "`".repeat(find); - } - if (count !== 3 && !stdsize) { - const samp = document.createElement("samp"); - samp.textContent = build; - span.appendChild(samp); - } else { - const pre = document.createElement("pre"); - if (build.at(-1) === "\n") { - build = build.substring(0, build.length - 1); - } - if (txt[i] === "\n") { - i++; - } - pre.textContent = build; - span.appendChild(pre); - } - i--; - continue; - } - } +if (txt[i + 1] === "#") { +if (txt[i + 2] === "#") { +if (txt[i + 3] === "#" && txt[i + 4] === " ") { +element = document.createElement("h3"); +keepys = "### "; +i += 5; +} else if (txt[i + 3] === " ") { +element = document.createElement("h2"); +element.classList.add("h2md"); +keepys = "## "; +i += 4; +} +} else if (txt[i + 2] === " ") { +element = document.createElement("h1"); +keepys = "# "; +i += 3; +} +} else if (txt[i + 1] === ">" && txt[i + 2] === " ") { +element = document.createElement("div"); +const line = document.createElement("div"); +line.classList.add("quoteline"); +element.append(line); +element.classList.add("quote"); +keepys = "> "; +i += 3; +} +if (keepys) { +appendcurrent(); +if (!first && !stdsize) { +span.appendChild(document.createElement("br")); +} +const build: string[] = []; +for (; txt[i] !== "\n" && txt[i] !== undefined; i++) { +build.push(txt[i]); +} +try { +if (stdsize) { +element = document.createElement("span"); +} +if (keep) { +element.append(keepys); +//span.appendChild(document.createElement("br")); +} +element.appendChild(this.markdown(build, { keep, stdsize })); +span.append(element); +} finally { +i -= 1; +continue; +} +} +if (first) { +i++; +} +} +if (txt[i] === "\n") { +if (!stdsize) { +appendcurrent(); +span.append(document.createElement("br")); +} +continue; +} +if (txt[i] === "`") { +let count = 1; +if (txt[i + 1] === "`") { +count++; +if (txt[i + 2] === "`") { +count++; +} +} +let build = ""; +if (keep) { +build += "`".repeat(count); +} +let find = 0; +let j = i + count; +let init = true; +for ( +; +txt[j] !== undefined && +(txt[j] !== "\n" || count === 3) && +find !== count; +j++ +) { +if (txt[j] === "`") { +find++; +} else { +if (find !== 0) { +build += "`".repeat(find); +find = 0; +} +if (init && count === 3) { +if (txt[j] === " " || txt[j] === "\n") { +init = false; +} +if (keep) { +build += txt[j]; +} +continue; +} +build += txt[j]; +} +} +if (stdsize) { +build = build.replaceAll("\n", ""); +} +if (find === count) { +appendcurrent(); +i = j; +if (keep) { +build += "`".repeat(find); +} +if (count !== 3 && !stdsize) { +const samp = document.createElement("samp"); +samp.textContent = build; +span.appendChild(samp); +} else { +const pre = document.createElement("pre"); +if (build.at(-1) === "\n") { +build = build.substring(0, build.length - 1); +} +if (txt[i] === "\n") { +i++; +} +pre.textContent = build; +span.appendChild(pre); +} +i--; +continue; +} +} - if (txt[i] === "*") { - let count = 1; - if (txt[i + 1] === "*") { - count++; - if (txt[i + 2] === "*") { - count++; - } - } - let build: string[] = []; - let find = 0; - let j = i + count; - for (; txt[j] !== undefined && find !== count; j++) { - if (txt[j] === "*") { - find++; - } else { - build.push(txt[j]); - if (find !== 0) { - build = build.concat(new Array(find).fill("*")); - find = 0; - } - } - } - if (find === count && (count != 1 || txt[i + 1] !== " ")) { - appendcurrent(); - i = j; +if (txt[i] === "*") { +let count = 1; +if (txt[i + 1] === "*") { +count++; +if (txt[i + 2] === "*") { +count++; +} +} +let build: string[] = []; +let find = 0; +let j = i + count; +for (; txt[j] !== undefined && find !== count; j++) { +if (txt[j] === "*") { +find++; +} else { +build.push(txt[j]); +if (find !== 0) { +build = build.concat(new Array(find).fill("*")); +find = 0; +} +} +} +if (find === count && (count != 1 || txt[i + 1] !== " ")) { +appendcurrent(); +i = j; - const stars = "*".repeat(count); - if (count === 1) { - const i = document.createElement("i"); - if (keep) { - i.append(stars); - } - i.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - i.append(stars); - } - span.appendChild(i); - } else if (count === 2) { - const b = document.createElement("b"); - if (keep) { - b.append(stars); - } - b.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - b.append(stars); - } - span.appendChild(b); - } else { - const b = document.createElement("b"); - const i = document.createElement("i"); - if (keep) { - b.append(stars); - } - b.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - b.append(stars); - } - i.appendChild(b); - span.appendChild(i); - } - i--; - continue; - } - } +const stars = "*".repeat(count); +if (count === 1) { +const i = document.createElement("i"); +if (keep) { +i.append(stars); +} +i.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +i.append(stars); +} +span.appendChild(i); +} else if (count === 2) { +const b = document.createElement("b"); +if (keep) { +b.append(stars); +} +b.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +b.append(stars); +} +span.appendChild(b); +} else { +const b = document.createElement("b"); +const i = document.createElement("i"); +if (keep) { +b.append(stars); +} +b.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +b.append(stars); +} +i.appendChild(b); +span.appendChild(i); +} +i--; +continue; +} +} - if (txt[i] === "_") { - let count = 1; - if (txt[i + 1] === "_") { - count++; - if (txt[i + 2] === "_") { - count++; - } - } - let build: string[] = []; - let find = 0; - let j = i + count; - for (; txt[j] !== undefined && find !== count; j++) { - if (txt[j] === "_") { - find++; - } else { - build.push(txt[j]); - if (find !== 0) { - build = build.concat(new Array(find).fill("_")); - find = 0; - } - } - } - if ( - find === count && - (count != 1 || - txt[j + 1] === " " || - txt[j + 1] === "\n" || - txt[j + 1] === undefined) - ) { - appendcurrent(); - i = j; - const underscores = "_".repeat(count); - if (count === 1) { - const i = document.createElement("i"); - if (keep) { - i.append(underscores); - } - i.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - i.append(underscores); - } - span.appendChild(i); - } else if (count === 2) { - const u = document.createElement("u"); - if (keep) { - u.append(underscores); - } - u.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - u.append(underscores); - } - span.appendChild(u); - } else { - const u = document.createElement("u"); - const i = document.createElement("i"); - if (keep) { - i.append(underscores); - } - i.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - i.append(underscores); - } - u.appendChild(i); - span.appendChild(u); - } - i--; - continue; - } - } +if (txt[i] === "_") { +let count = 1; +if (txt[i + 1] === "_") { +count++; +if (txt[i + 2] === "_") { +count++; +} +} +let build: string[] = []; +let find = 0; +let j = i + count; +for (; txt[j] !== undefined && find !== count; j++) { +if (txt[j] === "_") { +find++; +} else { +build.push(txt[j]); +if (find !== 0) { +build = build.concat(new Array(find).fill("_")); +find = 0; +} +} +} +if ( +find === count && +(count != 1 || +txt[j + 1] === " " || +txt[j + 1] === "\n" || +txt[j + 1] === undefined) +) { +appendcurrent(); +i = j; +const underscores = "_".repeat(count); +if (count === 1) { +const i = document.createElement("i"); +if (keep) { +i.append(underscores); +} +i.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +i.append(underscores); +} +span.appendChild(i); +} else if (count === 2) { +const u = document.createElement("u"); +if (keep) { +u.append(underscores); +} +u.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +u.append(underscores); +} +span.appendChild(u); +} else { +const u = document.createElement("u"); +const i = document.createElement("i"); +if (keep) { +i.append(underscores); +} +i.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +i.append(underscores); +} +u.appendChild(i); +span.appendChild(u); +} +i--; +continue; +} +} - if (txt[i] === "~" && txt[i + 1] === "~") { - const count = 2; - let build: string[] = []; - let find = 0; - let j = i + 2; - for (; txt[j] !== undefined && find !== count; j++) { - if (txt[j] === "~") { - find++; - } else { - build.push(txt[j]); - if (find !== 0) { - build = build.concat(new Array(find).fill("~")); - find = 0; - } - } - } - if (find === count) { - appendcurrent(); - i = j - 1; - const tildes = "~~"; - if (count === 2) { - const s = document.createElement("s"); - if (keep) { - s.append(tildes); - } - s.appendChild(this.markdown(build, { keep, stdsize })); - if (keep) { - s.append(tildes); - } - span.appendChild(s); - } - continue; - } - } - if (txt[i] === "|" && txt[i + 1] === "|") { - const count = 2; - let build: string[] = []; - let find = 0; - let j = i + 2; - for (; txt[j] !== undefined && find !== count; j++) { - if (txt[j] === "|") { - find++; - } else { - build.push(txt[j]); - if (find !== 0) { - build = build.concat(new Array(find).fill("~")); - find = 0; - } - } - } - if (find === count) { - appendcurrent(); - i = j - 1; - const pipes = "||"; - if (count === 2) { - const j = document.createElement("j"); - if (keep) { - j.append(pipes); - } - j.appendChild(this.markdown(build, { keep, stdsize })); - j.classList.add("spoiler"); - j.onclick = MarkDown.unspoil; - if (keep) { - j.append(pipes); - } - span.appendChild(j); - } - continue; - } - } - if ( - !keep && - txt[i] === "h" && - txt[i + 1] === "t" && - txt[i + 2] === "t" && - txt[i + 3] === "p" - ) { - let build = "http"; - let j = i + 4; - const endchars = new Set(["\\", "<", ">", "|", "]", " "]); - for (; txt[j] !== undefined; j++) { - const char = txt[j]; - if (endchars.has(char)) { - break; - } - build += char; - } - if (URL.canParse(build)) { - appendcurrent(); - const a = document.createElement("a"); - //a.href=build; - MarkDown.safeLink(a, build); - a.textContent = build; - a.target = "_blank"; - i = j - 1; - span.appendChild(a); - continue; - } - } - if (txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#")) { - let id = ""; - let j = i + 2; - const numbers = new Set([ - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - ]); - for (; txt[j] !== undefined; j++) { - const char = txt[j]; - if (!numbers.has(char)) { - break; - } - id += char; - } +if (txt[i] === "~" && txt[i + 1] === "~") { +const count = 2; +let build: string[] = []; +let find = 0; +let j = i + 2; +for (; txt[j] !== undefined && find !== count; j++) { +if (txt[j] === "~") { +find++; +} else { +build.push(txt[j]); +if (find !== 0) { +build = build.concat(new Array(find).fill("~")); +find = 0; +} +} +} +if (find === count) { +appendcurrent(); +i = j - 1; +const tildes = "~~"; +if (count === 2) { +const s = document.createElement("s"); +if (keep) { +s.append(tildes); +} +s.appendChild(this.markdown(build, { keep, stdsize })); +if (keep) { +s.append(tildes); +} +span.appendChild(s); +} +continue; +} +} +if (txt[i] === "|" && txt[i + 1] === "|") { +const count = 2; +let build: string[] = []; +let find = 0; +let j = i + 2; +for (; txt[j] !== undefined && find !== count; j++) { +if (txt[j] === "|") { +find++; +} else { +build.push(txt[j]); +if (find !== 0) { +build = build.concat(new Array(find).fill("~")); +find = 0; +} +} +} +if (find === count) { +appendcurrent(); +i = j - 1; +const pipes = "||"; +if (count === 2) { +const j = document.createElement("j"); +if (keep) { +j.append(pipes); +} +j.appendChild(this.markdown(build, { keep, stdsize })); +j.classList.add("spoiler"); +j.onclick = MarkDown.unspoil; +if (keep) { +j.append(pipes); +} +span.appendChild(j); +} +continue; +} +} +if ( +!keep && +txt[i] === "h" && +txt[i + 1] === "t" && +txt[i + 2] === "t" && +txt[i + 3] === "p" +) { +let build = "http"; +let j = i + 4; +const endchars = new Set(["\\", "<", ">", "|", "]", " "]); +for (; txt[j] !== undefined; j++) { +const char = txt[j]; +if (endchars.has(char)) { +break; +} +build += char; +} +if (URL.canParse(build)) { +appendcurrent(); +const a = document.createElement("a"); +//a.href=build; +MarkDown.safeLink(a, build); +a.textContent = build; +a.target = "_blank"; +i = j - 1; +span.appendChild(a); +continue; +} +} +if (txt[i] === "<" && (txt[i + 1] === "@" || txt[i + 1] === "#")) { +let id = ""; +let j = i + 2; +const numbers = new Set([ +"0", +"1", +"2", +"3", +"4", +"5", +"6", +"7", +"8", +"9", +]); +for (; txt[j] !== undefined; j++) { +const char = txt[j]; +if (!numbers.has(char)) { +break; +} +id += char; +} - if (txt[j] === ">") { - appendcurrent(); - const mention = document.createElement("span"); - mention.classList.add("mentionMD"); - mention.contentEditable = "false"; - const char = txt[i + 1]; - i = j; - switch (char) { - case "@": - const user = this.localuser.userMap.get(id); - if (user) { - mention.textContent = `@${user.name}`; - let guild: null | Guild = null; - if (this.owner instanceof Channel) { - guild = this.owner.guild; - } - if (!keep) { - user.bind(mention, guild); - } - if (guild) { - Member.resolveMember(user, guild).then((member) => { - if (member) { - mention.textContent = `@${member.name}`; - } - }); - } - } else { - mention.textContent = `@unknown`; - } - break; - case "#": - const channel = this.localuser.channelids.get(id); - if (channel) { - mention.textContent = `#${channel.name}`; - if (!keep) { - mention.onclick = (_) => { - this.localuser.goToChannel(id); - }; - } - } else { - mention.textContent = `#unknown`; - } - break; - } - span.appendChild(mention); - mention.setAttribute("real", `<${char}${id}>`); - continue; - } - } - if (txt[i] === "<" && txt[i + 1] === "t" && txt[i + 2] === ":") { - let found = false; - const build = ["<", "t", ":"]; - let j = i + 3; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); +if (txt[j] === ">") { +appendcurrent(); +const mention = document.createElement("span"); +mention.classList.add("mentionMD"); +mention.contentEditable = "false"; +const char = txt[i + 1]; +i = j; +switch (char) { +case "@": +const user = this.localuser.userMap.get(id); +if (user) { +mention.textContent = `@${user.name}`; +let guild: null | Guild = null; +if (this.owner instanceof Channel) { +guild = this.owner.guild; +} +if (!keep) { +user.bind(mention, guild); +} +if (guild) { +Member.resolveMember(user, guild).then((member) => { +if (member) { +mention.textContent = `@${member.name}`; +} +}); +} +} else { +mention.textContent = `@unknown`; +} +break; +case "#": +const channel = this.localuser.channelids.get(id); +if (channel) { +mention.textContent = `#${channel.name}`; +if (!keep) { +mention.onclick = (_) => { +this.localuser.goToChannel(id); +}; +} +} else { +mention.textContent = `#unknown`; +} +break; +} +span.appendChild(mention); +mention.setAttribute("real", `<${char}${id}>`); +continue; +} +} +if (txt[i] === "<" && txt[i + 1] === "t" && txt[i + 2] === ":") { +let found = false; +const build = ["<", "t", ":"]; +let j = i + 3; +for (; txt[j] !== void 0; j++) { +build.push(txt[j]); - if (txt[j] === ">") { - found = true; - break; - } - } +if (txt[j] === ">") { +found = true; +break; +} +} - if (found) { - appendcurrent(); - i = j; - const parts = build - .join("") - .match(/^$/) as RegExpMatchArray; - const dateInput = new Date(Number.parseInt(parts[1]) * 1000); - let time = ""; - if (Number.isNaN(dateInput.getTime())) time = build.join(""); - else { - if (parts[3] === "d") - time = dateInput.toLocaleString(void 0, { - day: "2-digit", - month: "2-digit", - year: "numeric", - }); - else if (parts[3] === "D") - time = dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - }); - else if (!parts[3] || parts[3] === "f") - time = - dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - }) + - " " + - dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - }); - else if (parts[3] === "F") - time = - dateInput.toLocaleString(void 0, { - day: "numeric", - month: "long", - year: "numeric", - weekday: "long", - }) + - " " + - dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - }); - else if (parts[3] === "t") - time = dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - }); - else if (parts[3] === "T") - time = dateInput.toLocaleString(void 0, { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - else if (parts[3] === "R") - time = - Math.round( - (Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60 - ) + " minutes ago"; - } +if (found) { +appendcurrent(); +i = j; +const parts = build +.join("") +.match(/^$/) as RegExpMatchArray; + const dateInput = new Date(Number.parseInt(parts[1]) * 1000); + let time = ""; + if (Number.isNaN(dateInput.getTime())) time = build.join(""); + else { + if (parts[3] === "d") + time = dateInput.toLocaleString(void 0, { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + else if (parts[3] === "D") + time = dateInput.toLocaleString(void 0, { + day: "numeric", + month: "long", + year: "numeric", + }); + else if (!parts[3] || parts[3] === "f") + time = + dateInput.toLocaleString(void 0, { + day: "numeric", + month: "long", + year: "numeric", + }) + + " " + + dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + }); + else if (parts[3] === "F") + time = + dateInput.toLocaleString(void 0, { + day: "numeric", + month: "long", + year: "numeric", + weekday: "long", + }) + + " " + + dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + }); + else if (parts[3] === "t") + time = dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + }); + else if (parts[3] === "T") + time = dateInput.toLocaleString(void 0, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + else if (parts[3] === "R") + time = + Math.round( + (Date.now() - Number.parseInt(parts[1]) * 1000) / 1000 / 60 + ) + " minutes ago"; + } - const timeElem = document.createElement("span"); - timeElem.classList.add("markdown-timestamp"); - timeElem.textContent = time; - span.appendChild(timeElem); - continue; - } - } + const timeElem = document.createElement("span"); + timeElem.classList.add("markdown-timestamp"); + timeElem.textContent = time; + span.appendChild(timeElem); + continue; + } + } - if ( - txt[i] === "<" && - (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")) - ) { - let found = false; - const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; - let j = i + build.length; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); + if ( + txt[i] === "<" && + (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":")) + ) { + let found = false; + const build = txt[i + 1] === "a" ? ["<", "a", ":"] : ["<", ":"]; + let j = i + build.length; + for (; txt[j] !== void 0; j++) { + build.push(txt[j]); - if (txt[j] === ">") { - found = true; - break; - } - } + if (txt[j] === ">") { + found = true; + break; + } + } - if (found) { - const buildjoin = build.join(""); - const parts = buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); - if (parts && parts[2]) { - appendcurrent(); - i = j; - const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); - const owner = - this.owner instanceof Channel ? this.owner.guild : this.owner; - const emoji = new Emoji( - { name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, - owner - ); - span.appendChild(emoji.getHTML(isEmojiOnly)); + if (found) { + const buildjoin = build.join(""); + const parts = buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); + if (parts && parts[2]) { + appendcurrent(); + i = j; + const isEmojiOnly = txt.join("").trim() === buildjoin.trim(); + const owner = + this.owner instanceof Channel ? this.owner.guild : this.owner; + const emoji = new Emoji( + { name: buildjoin, id: parts[2], animated: Boolean(parts[1]) }, + owner + ); + span.appendChild(emoji.getHTML(isEmojiOnly)); - continue; - } - } - } + continue; + } + } + } - if (txt[i] == "[" && !keep) { - let partsFound = 0; - let j = i + 1; - const build = ["["]; - for (; txt[j] !== void 0; j++) { - build.push(txt[j]); + if (txt[i] == "[" && !keep) { + let partsFound = 0; + let j = i + 1; + const build = ["["]; + for (; txt[j] !== void 0; j++) { + build.push(txt[j]); - if (partsFound === 0 && txt[j] === "]") { - if ( - txt[j + 1] === "(" && - txt[j + 2] === "h" && - txt[j + 3] === "t" && - txt[j + 4] === "t" && - txt[j + 5] === "p" && - (txt[j + 6] === "s" || txt[j + 6] === ":") - ) { - partsFound++; - } else { - break; - } - } else if (partsFound === 1 && txt[j] === ")") { - partsFound++; - break; - } - } + if (partsFound === 0 && txt[j] === "]") { + if ( + txt[j + 1] === "(" && + txt[j + 2] === "h" && + txt[j + 3] === "t" && + txt[j + 4] === "t" && + txt[j + 5] === "p" && + (txt[j + 6] === "s" || txt[j + 6] === ":") + ) { + partsFound++; + } else { + break; + } + } else if (partsFound === 1 && txt[j] === ")") { + partsFound++; + break; + } + } - if (partsFound === 2) { - appendcurrent(); + if (partsFound === 2) { + appendcurrent(); - const parts = build - .join("") - .match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/); - if (parts) { - const linkElem = document.createElement("a"); - if (URL.canParse(parts[2])) { - i = j; - MarkDown.safeLink(linkElem, parts[2]); - linkElem.textContent = parts[1]; - linkElem.target = "_blank"; - linkElem.rel = "noopener noreferrer"; - linkElem.title = - (parts[3] - ? parts[3].substring(2, parts[3].length - 1) + "\n\n" - : "") + parts[2]; - span.appendChild(linkElem); + const parts = build + .join("") + .match(/^\[(.+)\]\((https?:.+?)( ('|").+('|"))?\)$/); + if (parts) { + const linkElem = document.createElement("a"); + if (URL.canParse(parts[2])) { + i = j; + MarkDown.safeLink(linkElem, parts[2]); + linkElem.textContent = parts[1]; + linkElem.target = "_blank"; + linkElem.rel = "noopener noreferrer"; + linkElem.title = + (parts[3] + ? parts[3].substring(2, parts[3].length - 1) + "\n\n" + : "") + parts[2]; + span.appendChild(linkElem); - continue; - } - } - } - } + continue; + } + } + } + } - current.textContent += txt[i]; - } - appendcurrent(); - return span; - } - static unspoil(e: any): void { - e.target.classList.remove("spoiler"); - e.target.classList.add("unspoiled"); - } - giveBox(box: HTMLDivElement) { - box.onkeydown = (_) => { - //console.log(_); - }; - let prevcontent = ""; - box.onkeyup = (_) => { - const content = MarkDown.gatherBoxText(box); - if (content !== prevcontent) { - prevcontent = content; - this.txt = content.split(""); - this.boxupdate(box); - } - }; - box.onpaste = (_) => { - if (!_.clipboardData) return; - console.log(_.clipboardData.types); - const data = _.clipboardData.getData("text"); + current.textContent += txt[i]; + } + appendcurrent(); + return span; + } + static unspoil(e: any): void { + e.target.classList.remove("spoiler"); + e.target.classList.add("unspoiled"); + } + giveBox(box: HTMLDivElement) { + box.onkeydown = (_) => { + //console.log(_); + }; + let prevcontent = ""; + box.onkeyup = (_) => { + const content = MarkDown.gatherBoxText(box); + if (content !== prevcontent) { + prevcontent = content; + this.txt = content.split(""); + this.boxupdate(box); + } + }; + box.onpaste = (_) => { + if (!_.clipboardData) return; + console.log(_.clipboardData.types); + const data = _.clipboardData.getData("text"); - document.execCommand("insertHTML", false, data); - _.preventDefault(); - if (!box.onkeyup) return; - box.onkeyup(new KeyboardEvent("_")); - }; - } - boxupdate(box: HTMLElement) { - const restore = saveCaretPosition(box); - box.innerHTML = ""; - box.append(this.makeHTML({ keep: true })); - if (restore) { - restore(); - } - } - static gatherBoxText(element: HTMLElement): string { - if (element.tagName.toLowerCase() === "img") { - return (element as HTMLImageElement).alt; - } - if (element.tagName.toLowerCase() === "br") { - return "\n"; - } - if (element.hasAttribute("real")) { - return element.getAttribute("real") as string; - } - let build = ""; - for (const thing of Array.from(element.childNodes)) { - if (thing instanceof Text) { - const text = thing.textContent; - build += text; - continue; - } - const text = this.gatherBoxText(thing as HTMLElement); - if (text) { - build += text; - } - } - return build; - } - static readonly trustedDomains = new Set([location.host]); - static safeLink(elm: HTMLElement, url: string) { - if (URL.canParse(url)) { - const Url = new URL(url); - if ( - elm instanceof HTMLAnchorElement && - this.trustedDomains.has(Url.host) - ) { - elm.href = url; - elm.target = "_blank"; - return; - } - elm.onmouseup = (_) => { - if (_.button === 2) return; - console.log(":3"); - function open() { - const proxy = window.open(url, "_blank"); - if (proxy && _.button === 1) { - proxy.focus(); - } else if (proxy) { - window.focus(); - } - } - if (this.trustedDomains.has(Url.host)) { - open(); - } else { - const full: Dialog = new Dialog([ - "vdiv", - ["title", "You're leaving spacebar"], - [ - "text", - "You're going to " + - Url.host + - " are you sure you want to go there?", - ], - [ - "hdiv", - ["button", "", "Nevermind", (_: any) => full.hide()], - [ - "button", - "", - "Go there", - (_: any) => { - open(); - full.hide(); - }, - ], - [ - "button", - "", - "Go there and trust in the future", - (_: any) => { - open(); - full.hide(); - this.trustedDomains.add(Url.host); - }, - ], - ], - ]); - full.show(); - } - }; - } else { - throw Error(url + " is not a valid URL"); - } - } - /* - static replace(base: HTMLElement, newelm: HTMLElement) { - const basechildren = base.children; - const newchildren = newelm.children; - for (const thing of Array.from(newchildren)) { - base.append(thing); - } - } + document.execCommand("insertHTML", false, data); + _.preventDefault(); + if (!box.onkeyup) return; + box.onkeyup(new KeyboardEvent("_")); + }; + } + boxupdate(box: HTMLElement) { + const restore = saveCaretPosition(box); + box.innerHTML = ""; + box.append(this.makeHTML({ keep: true })); + if (restore) { + restore(); + } + } + static gatherBoxText(element: HTMLElement): string { + if (element.tagName.toLowerCase() === "img") { + return (element as HTMLImageElement).alt; + } + if (element.tagName.toLowerCase() === "br") { + return "\n"; + } + if (element.hasAttribute("real")) { + return element.getAttribute("real") as string; + } + let build = ""; + for (const thing of Array.from(element.childNodes)) { + if (thing instanceof Text) { + const text = thing.textContent; + build += text; + continue; + } + const text = this.gatherBoxText(thing as HTMLElement); + if (text) { + build += text; + } + } + return build; + } + static readonly trustedDomains = new Set([location.host]); + static safeLink(elm: HTMLElement, url: string) { + if (URL.canParse(url)) { + const Url = new URL(url); + if ( + elm instanceof HTMLAnchorElement && + this.trustedDomains.has(Url.host) + ) { + elm.href = url; + elm.target = "_blank"; + return; + } + elm.onmouseup = (_) => { + if (_.button === 2) return; + console.log(":3"); + function open() { + const proxy = window.open(url, "_blank"); + if (proxy && _.button === 1) { + proxy.focus(); + } else if (proxy) { + window.focus(); + } + } + if (this.trustedDomains.has(Url.host)) { + open(); + } else { + const full: Dialog = new Dialog([ + "vdiv", + ["title", "You're leaving spacebar"], + [ + "text", + "You're going to " + + Url.host + + " are you sure you want to go there?", + ], + [ + "hdiv", + ["button", "", "Nevermind", (_: any) => full.hide()], + [ + "button", + "", + "Go there", + (_: any) => { + open(); + full.hide(); + }, + ], + [ + "button", + "", + "Go there and trust in the future", + (_: any) => { + open(); + full.hide(); + this.trustedDomains.add(Url.host); + }, + ], + ], + ]); + full.show(); + } + }; + } else { + throw Error(url + " is not a valid URL"); + } + } + /* + static replace(base: HTMLElement, newelm: HTMLElement) { + const basechildren = base.children; + const newchildren = newelm.children; + for (const thing of Array.from(newchildren)) { + base.append(thing); + } + } */ -} + } -//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div -let text = ""; -function saveCaretPosition(context: Node) { - const selection = window.getSelection(); - if (!selection) return; - const range = selection.getRangeAt(0); - range.setStart(context, 0); - text = selection.toString(); - let len = text.length + 1; - for (const str in text.split("\n")) { - if (str.length !== 0) { - len--; - } - } - len += +(text[text.length - 1] === "\n"); + //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div + let text = ""; + function saveCaretPosition(context: Node) { + const selection = window.getSelection(); + if (!selection) return; + const range = selection.getRangeAt(0); + range.setStart(context, 0); + text = selection.toString(); + let len = text.length + 1; + for (const str in text.split("\n")) { + if (str.length !== 0) { + len--; + } + } + len += +(text[text.length - 1] === "\n"); - return function restore() { - if (!selection) return; - const pos = getTextNodeAtPosition(context, len); - selection.removeAllRanges(); - const range = new Range(); - range.setStart(pos.node, pos.position); - selection.addRange(range); - }; -} + return function restore() { + if (!selection) return; + const pos = getTextNodeAtPosition(context, len); + selection.removeAllRanges(); + const range = new Range(); + range.setStart(pos.node, pos.position); + selection.addRange(range); + }; + } -function getTextNodeAtPosition(root: Node, index: number) { - const NODE_TYPE = NodeFilter.SHOW_TEXT; - const treeWalker = document.createTreeWalker(root, NODE_TYPE, (elem) => { - if (!elem.textContent) return 0; - if (index > elem.textContent.length) { - index -= elem.textContent.length; - return NodeFilter.FILTER_REJECT; - } - return NodeFilter.FILTER_ACCEPT; - }); - const c = treeWalker.nextNode(); - return { - node: c ? c : root, - position: index, - }; -} -export { MarkDown }; + function getTextNodeAtPosition(root: Node, index: number) { + const NODE_TYPE = NodeFilter.SHOW_TEXT; + const treeWalker = document.createTreeWalker(root, NODE_TYPE, (elem) => { + if (!elem.textContent) return 0; + if (index > elem.textContent.length) { + index -= elem.textContent.length; + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_ACCEPT; + }); + const c = treeWalker.nextNode(); + return { + node: c ? c : root, + position: index, + }; + } + export { MarkDown }; diff --git a/src/webpage/member.ts b/src/webpage/member.ts index d291f5e..47faec4 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -6,251 +6,251 @@ import { memberjson, presencejson } from "./jsontypes.js"; import { Dialog } from "./dialog.js"; class Member extends SnowFlake { - static already = {}; - owner: Guild; - user: User; - roles: Role[] = []; - nick!: string; - [key: string]: any; +static already = {}; +owner: Guild; +user: User; +roles: Role[] = []; +nick!: string; +[key: string]: any; - private constructor(memberjson: memberjson, owner: Guild) { - super(memberjson.id); - this.owner = owner; - if (this.localuser.userMap.has(memberjson.id)) { - this.user = this.localuser.userMap.get(memberjson.id) as User; - } else if (memberjson.user) { - this.user = new User(memberjson.user, owner.localuser); - } else { - 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 { - 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 { - const maybe = user.members.get(guild); - if (!user.members.has(guild)) { - const membpromise = guild.localuser.resolvemember(user.id, guild.id); - const promise = new Promise(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; - } +private constructor(memberjson: memberjson, owner: Guild) { +super(memberjson.id); +this.owner = owner; +if (this.localuser.userMap.has(memberjson.id)) { +this.user = this.localuser.userMap.get(memberjson.id) as User; +} else if (memberjson.user) { +this.user = new User(memberjson.user, owner.localuser); +} else { +throw new Error("Missing user object of this member"); } -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 { + 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 { + const maybe = user.members.get(guild); + if (!user.members.has(guild)) { + const membpromise = guild.localuser.resolvemember(user.id, guild.id); + const promise = new Promise(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 }; diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 486ea88..34e1e3e 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -13,757 +13,757 @@ import { Emoji } from "./emoji.js"; import { Dialog } from "./dialog.js"; class Message extends SnowFlake { - static contextmenu = new Contextmenu("message menu"); - owner: Channel; - headers: Localuser["headers"]; - embeds!: Embed[]; - author!: User; - mentions!: User[]; - mention_roles!: Role[]; - attachments!: File[]; //probably should be its own class tbh, should be Attachments[] - message_reference!: messagejson; - type!: number; - timestamp!: number; - content!: MarkDown; - static del: Promise; - static resolve: Function; - /* - weakdiv:WeakRef; - set div(e:HTMLDivElement){ - if(!e){ - this.weakdiv=null; - return; - } - this.weakdiv=new WeakRef(e); - } - get div(){ - return this.weakdiv?.deref(); - } - //*/ - div: - | (HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement }) - | undefined; - member: Member | undefined; - reactions!: messagejson["reactions"]; - static setup() { - this.del = new Promise((_) => { - this.resolve = _; - }); - Message.setupcmenu(); - } - static setupcmenu() { - Message.contextmenu.addbutton("Copy raw text", function (this: Message) { - navigator.clipboard.writeText(this.content.rawString); - }); - Message.contextmenu.addbutton("Reply", function (this: Message) { - this.channel.setReplying(this); - }); - Message.contextmenu.addbutton("Copy message id", function (this: Message) { - navigator.clipboard.writeText(this.id); - }); - Message.contextmenu.addsubmenu( - "Add reaction", - function (this: Message, _, e: MouseEvent) { - Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => { - this.reactionToggle(_); - }); - } - ); - Message.contextmenu.addbutton( - "Edit", - function (this: Message) { - this.setEdit(); - }, - null, - function () { - return this.author.id === this.localuser.user.id; - } - ); - Message.contextmenu.addbutton( - "Delete message", - function (this: Message) { - this.delete(); - }, - null, - function () { - return this.canDelete(); - } - ); - } - setEdit() { - this.channel.editing = this; - const markdown = ( - document.getElementById("typebox") as HTMLDivElement & { - markdown: MarkDown; - } - )["markdown"] as MarkDown; - markdown.txt = this.content.rawString.split(""); - markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); - } - constructor(messagejson: messagejson, owner: Channel) { - super(messagejson.id); - this.owner = owner; - this.headers = this.owner.headers; - this.giveData(messagejson); - this.owner.messages.set(this.id, this); - } - reactionToggle(emoji: string | Emoji) { - let remove = false; - for (const thing of this.reactions) { - if (thing.emoji.name === emoji) { - remove = thing.me; - break; - } - } - let reactiontxt: string; - if (emoji instanceof Emoji) { - reactiontxt = `${emoji.name}:${emoji.id}`; - } else { - reactiontxt = encodeURIComponent(emoji); - } - fetch( - `${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${reactiontxt}/@me`, - { - method: remove ? "DELETE" : "PUT", - headers: this.headers, - } - ); - } - giveData(messagejson: messagejson) { - const func = this.channel.infinite.snapBottom(); - for (const thing of Object.keys(messagejson)) { - if (thing === "attachments") { - this.attachments = []; - for (const thing of messagejson.attachments) { - this.attachments.push(new File(thing, this)); - } - continue; - } else if (thing === "content") { - this.content = new MarkDown(messagejson[thing], this.channel); - continue; - } else if (thing === "id") { - continue; - } else if (thing === "member") { - Member.new(messagejson.member as memberjson, this.guild).then((_) => { - this.member = _ as Member; - }); - continue; - } else if (thing === "embeds") { - this.embeds = []; - for (const thing in messagejson.embeds) { - this.embeds[thing] = new Embed(messagejson.embeds[thing], this); - } - continue; - } - (this as any)[thing] = (messagejson as any)[thing]; - } - if (messagejson.reactions?.length) { - console.log(messagejson.reactions, ":3"); - } +static contextmenu = new Contextmenu("message menu"); + owner: Channel; + headers: Localuser["headers"]; + embeds!: Embed[]; + author!: User; + mentions!: User[]; + mention_roles!: Role[]; + attachments!: File[]; //probably should be its own class tbh, should be Attachments[] + message_reference!: messagejson; + type!: number; + timestamp!: number; + content!: MarkDown; + static del: Promise; + static resolve: Function; + /* + weakdiv:WeakRef; + set div(e:HTMLDivElement){ + if(!e){ + this.weakdiv=null; + return; + } + this.weakdiv=new WeakRef(e); + } + get div(){ + return this.weakdiv?.deref(); + } + //*/ + div: + | (HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement }) + | undefined; + member: Member | undefined; + reactions!: messagejson["reactions"]; + static setup() { + this.del = new Promise((_) => { + this.resolve = _; + }); + Message.setupcmenu(); + } + static setupcmenu() { + Message.contextmenu.addbutton("Copy raw text", function (this: Message) { + navigator.clipboard.writeText(this.content.rawString); + }); + Message.contextmenu.addbutton("Reply", function (this: Message) { + this.channel.setReplying(this); + }); + Message.contextmenu.addbutton("Copy message id", function (this: Message) { + navigator.clipboard.writeText(this.id); + }); + Message.contextmenu.addsubmenu( + "Add reaction", + function (this: Message, _, e: MouseEvent) { + Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => { + this.reactionToggle(_); + }); + } + ); + Message.contextmenu.addbutton( + "Edit", + function (this: Message) { + this.setEdit(); + }, + null, + function () { + return this.author.id === this.localuser.user.id; + } + ); + Message.contextmenu.addbutton( + "Delete message", + function (this: Message) { + this.delete(); + }, + null, + function () { + return this.canDelete(); + } + ); + } + setEdit() { + this.channel.editing = this; + const markdown = ( + document.getElementById("typebox") as HTMLDivElement & { + markdown: MarkDown; + } + )["markdown"] as MarkDown; + markdown.txt = this.content.rawString.split(""); + markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement); + } + constructor(messagejson: messagejson, owner: Channel) { + super(messagejson.id); + this.owner = owner; + this.headers = this.owner.headers; + this.giveData(messagejson); + this.owner.messages.set(this.id, this); + } + reactionToggle(emoji: string | Emoji) { + let remove = false; + for (const thing of this.reactions) { + if (thing.emoji.name === emoji) { + remove = thing.me; + break; + } + } + let reactiontxt: string; + if (emoji instanceof Emoji) { + reactiontxt = `${emoji.name}:${emoji.id}`; + } else { + reactiontxt = encodeURIComponent(emoji); + } + fetch( + `${this.info.api}/channels/${this.channel.id}/messages/${this.id}/reactions/${reactiontxt}/@me`, + { + method: remove ? "DELETE" : "PUT", + headers: this.headers, + } + ); + } + giveData(messagejson: messagejson) { + const func = this.channel.infinite.snapBottom(); + for (const thing of Object.keys(messagejson)) { + if (thing === "attachments") { + this.attachments = []; + for (const thing of messagejson.attachments) { + this.attachments.push(new File(thing, this)); + } + continue; + } else if (thing === "content") { + this.content = new MarkDown(messagejson[thing], this.channel); + continue; + } else if (thing === "id") { + continue; + } else if (thing === "member") { + Member.new(messagejson.member as memberjson, this.guild).then((_) => { + this.member = _ as Member; + }); + continue; + } else if (thing === "embeds") { + this.embeds = []; + for (const thing in messagejson.embeds) { + this.embeds[thing] = new Embed(messagejson.embeds[thing], this); + } + continue; + } + (this as any)[thing] = (messagejson as any)[thing]; + } + if (messagejson.reactions?.length) { + console.log(messagejson.reactions, ":3"); + } - this.author = new User(messagejson.author, this.localuser); - for (const thing in messagejson.mentions) { - this.mentions[thing] = new User( - messagejson.mentions[thing], - this.localuser - ); - } - if (!this.member && this.guild.id !== "@me") { - this.author.resolvemember(this.guild).then((_) => { - this.member = _; - }); - } - if (this.mentions.length || this.mention_roles.length) { - //currently mention_roles isn't implemented on the spacebar servers - console.log(this.mentions, this.mention_roles); - } - if (this.mentionsuser(this.localuser.user)) { - console.log(this); - } - if (this.div) { - this.generateMessage(); - } - func(); - } - canDelete() { - return ( - this.channel.hasPermission("MANAGE_MESSAGES") || - this.author === this.localuser.user - ); - } - get channel() { - return this.owner; - } - get guild() { - return this.owner.guild; - } - get localuser() { - return this.owner.localuser; - } - get info() { - return this.owner.info; - } - messageevents(obj: HTMLDivElement) { - // const func = Message.contextmenu.bindContextmenu(obj, this, undefined); - this.div = obj; - obj.classList.add("messagediv"); - } - deleteDiv() { - if (!this.div) return; - try { - this.div.remove(); - this.div = undefined; - } catch (e) { - console.error(e); - } - } - mentionsuser(userd: User | Member) { - if (userd instanceof User) { - return this.mentions.includes(userd); - } else if (userd instanceof Member) { - return this.mentions.includes(userd.user); - } else { - return; - } - } - getimages() { - const build: File[] = []; - for (const thing of this.attachments) { - if (thing.content_type.startsWith("image/")) { - build.push(thing); - } - } - return build; - } - async edit(content: string) { - return await fetch( - this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id, - { - method: "PATCH", - headers: this.headers, - body: JSON.stringify({ content }), - } - ); - } - delete() { - fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, { - headers: this.headers, - method: "DELETE", - }); - } - deleteEvent() { - console.log("deleted"); - if (this.div) { - this.div.remove(); - this.div.innerHTML = ""; - this.div = undefined; - } - const prev = this.channel.idToPrev.get(this.id); - const next = this.channel.idToNext.get(this.id); - this.channel.idToPrev.delete(this.id); - this.channel.idToNext.delete(this.id); - this.channel.messages.delete(this.id); - if (prev && next) { - this.channel.idToPrev.set(next, prev); - this.channel.idToNext.set(prev, next); - } else if (prev) { - this.channel.idToNext.delete(prev); - } else if (next) { - this.channel.idToPrev.delete(next); - } - if (prev) { - const prevmessage = this.channel.messages.get(prev); - if (prevmessage) { - prevmessage.generateMessage(); - } - } - if ( - this.channel.lastmessage === this || - this.channel.lastmessageid === this.id - ) { - if (prev) { - this.channel.lastmessage = this.channel.messages.get(prev); - this.channel.lastmessageid = prev; - } else { - this.channel.lastmessage = undefined; - this.channel.lastmessageid = undefined; - } - } - if (this.channel.lastreadmessageid === this.id) { - if (prev) { - this.channel.lastreadmessageid = prev; - } else { - this.channel.lastreadmessageid = undefined; - } - } - console.log("deleted done"); - } - reactdiv!: WeakRef; - blockedPropigate() { - const previd = this.channel.idToPrev.get(this.id); - if (!previd) { - this.generateMessage(); - return; - } - const premessage = this.channel.messages.get(previd); - if (premessage?.author === this.author) { - premessage.blockedPropigate(); - } else { - this.generateMessage(); - } - } - generateMessage(premessage?: Message | undefined, ignoredblock = false) { - if (!this.div) return; - if (!premessage) { - premessage = this.channel.messages.get( - this.channel.idToPrev.get(this.id) as string - ); - } - const div = this.div; - for (const user of this.mentions) { - if (user === this.localuser.user) { - div.classList.add("mentioned"); - } - } - if (this === this.channel.replyingto) { - div.classList.add("replying"); - } - div.innerHTML = ""; - const build = document.createElement("div"); + this.author = new User(messagejson.author, this.localuser); + for (const thing in messagejson.mentions) { + this.mentions[thing] = new User( + messagejson.mentions[thing], + this.localuser + ); + } + if (!this.member && this.guild.id !== "@me") { + this.author.resolvemember(this.guild).then((_) => { + this.member = _; + }); + } + if (this.mentions.length || this.mention_roles.length) { + //currently mention_roles isn't implemented on the spacebar servers + console.log(this.mentions, this.mention_roles); + } + if (this.mentionsuser(this.localuser.user)) { + console.log(this); + } + if (this.div) { + this.generateMessage(); + } + func(); + } + canDelete() { + return ( + this.channel.hasPermission("MANAGE_MESSAGES") || + this.author === this.localuser.user + ); + } + get channel() { + return this.owner; + } + get guild() { + return this.owner.guild; + } + get localuser() { + return this.owner.localuser; + } + get info() { + return this.owner.info; + } + messageevents(obj: HTMLDivElement) { + // const func = Message.contextmenu.bindContextmenu(obj, this, undefined); + this.div = obj; + obj.classList.add("messagediv"); + } + deleteDiv() { + if (!this.div) return; + try { + this.div.remove(); + this.div = undefined; + } catch (e) { + console.error(e); + } + } + mentionsuser(userd: User | Member) { + if (userd instanceof User) { + return this.mentions.includes(userd); + } else if (userd instanceof Member) { + return this.mentions.includes(userd.user); + } else { + return; + } + } + getimages() { + const build: File[] = []; + for (const thing of this.attachments) { + if (thing.content_type.startsWith("image/")) { + build.push(thing); + } + } + return build; + } + async edit(content: string) { + return await fetch( + this.info.api + "/channels/" + this.channel.id + "/messages/" + this.id, + { + method: "PATCH", + headers: this.headers, + body: JSON.stringify({ content }), + } + ); + } + delete() { + fetch(`${this.info.api}/channels/${this.channel.id}/messages/${this.id}`, { + headers: this.headers, + method: "DELETE", + }); + } + deleteEvent() { + console.log("deleted"); + if (this.div) { + this.div.remove(); + this.div.innerHTML = ""; + this.div = undefined; + } + const prev = this.channel.idToPrev.get(this.id); + const next = this.channel.idToNext.get(this.id); + this.channel.idToPrev.delete(this.id); + this.channel.idToNext.delete(this.id); + this.channel.messages.delete(this.id); + if (prev && next) { + this.channel.idToPrev.set(next, prev); + this.channel.idToNext.set(prev, next); + } else if (prev) { + this.channel.idToNext.delete(prev); + } else if (next) { + this.channel.idToPrev.delete(next); + } + if (prev) { + const prevmessage = this.channel.messages.get(prev); + if (prevmessage) { + prevmessage.generateMessage(); + } + } + if ( + this.channel.lastmessage === this || + this.channel.lastmessageid === this.id + ) { + if (prev) { + this.channel.lastmessage = this.channel.messages.get(prev); + this.channel.lastmessageid = prev; + } else { + this.channel.lastmessage = undefined; + this.channel.lastmessageid = undefined; + } + } + if (this.channel.lastreadmessageid === this.id) { + if (prev) { + this.channel.lastreadmessageid = prev; + } else { + this.channel.lastreadmessageid = undefined; + } + } + console.log("deleted done"); + } + reactdiv!: WeakRef; + blockedPropigate() { + const previd = this.channel.idToPrev.get(this.id); + if (!previd) { + this.generateMessage(); + return; + } + const premessage = this.channel.messages.get(previd); + if (premessage?.author === this.author) { + premessage.blockedPropigate(); + } else { + this.generateMessage(); + } + } + generateMessage(premessage?: Message | undefined, ignoredblock = false) { + if (!this.div) return; + if (!premessage) { + premessage = this.channel.messages.get( + this.channel.idToPrev.get(this.id) as string + ); + } + const div = this.div; + for (const user of this.mentions) { + if (user === this.localuser.user) { + div.classList.add("mentioned"); + } + } + if (this === this.channel.replyingto) { + div.classList.add("replying"); + } + div.innerHTML = ""; + const build = document.createElement("div"); - build.classList.add("flexltr", "message"); - div.classList.remove("zeroheight"); - if (this.author.relationshipType === 2) { - if (ignoredblock) { - if (premessage?.author !== this.author) { - const span = document.createElement("span"); - span.textContent = - "You have this user blocked, click to hide these messages."; - div.append(span); - span.classList.add("blocked"); - span.onclick = (_) => { - const scroll = this.channel.infinite.scrollTop; - let next: Message | undefined = this; - while (next?.author === this.author) { - next.generateMessage(); - next = this.channel.messages.get( - this.channel.idToNext.get(next.id) as string - ); - } - if (this.channel.infinite.scollDiv && scroll) { - this.channel.infinite.scollDiv.scrollTop = scroll; - } - }; - } - } else { - div.classList.remove("topMessage"); - if (premessage?.author === this.author) { - div.classList.add("zeroheight"); - premessage.blockedPropigate(); - div.appendChild(build); - return div; - } else { - build.classList.add("blocked", "topMessage"); - const span = document.createElement("span"); - let count = 1; - let next = this.channel.messages.get( - this.channel.idToNext.get(this.id) as string - ); - while (next?.author === this.author) { - count++; - next = this.channel.messages.get( - this.channel.idToNext.get(next.id) as string - ); - } - span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`; - build.append(span); - span.onclick = (_) => { - const scroll = this.channel.infinite.scrollTop; - const func = this.channel.infinite.snapBottom(); - let next: Message | undefined = this; - while (next?.author === this.author) { - next.generateMessage(undefined, true); - next = this.channel.messages.get( - this.channel.idToNext.get(next.id) as string - ); - console.log("loopy"); - } - if (this.channel.infinite.scollDiv && scroll) { - func(); - this.channel.infinite.scollDiv.scrollTop = scroll; - } - }; - div.appendChild(build); - return div; - } - } - } - if (this.message_reference) { - const replyline = document.createElement("div"); - const line = document.createElement("hr"); - const minipfp = document.createElement("img"); - minipfp.classList.add("replypfp"); - replyline.appendChild(line); - replyline.appendChild(minipfp); - const username = document.createElement("span"); - replyline.appendChild(username); - const reply = document.createElement("div"); - username.classList.add("username"); - reply.classList.add("replytext"); - replyline.appendChild(reply); - const line2 = document.createElement("hr"); - replyline.appendChild(line2); - line2.classList.add("reply"); - line.classList.add("startreply"); - replyline.classList.add("replyflex"); - // TODO: Fix this - this.channel.getmessage(this.message_reference.id).then((message) => { - if (message.author.relationshipType === 2) { - username.textContent = "Blocked user"; - return; - } - const author = message.author; - reply.appendChild(message.content.makeHTML({ stdsize: true })); - minipfp.src = author.getpfpsrc(); - author.bind(minipfp, this.guild); - username.textContent = author.username; - author.bind(username, this.guild); - }); - reply.onclick = (_) => { - // TODO: FIX this - this.channel.infinite.focus(this.message_reference.id); - }; - div.appendChild(replyline); - } - div.appendChild(build); - if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { - const pfpRow = document.createElement("div"); - pfpRow.classList.add("flexltr"); - let pfpparent, current; - if (premessage != null) { - pfpparent ??= premessage; - // @ts-ignore - // TODO: type this - let pfpparent2 = pfpparent.all; - pfpparent2 ??= pfpparent; - const old = new Date(pfpparent2.timestamp).getTime() / 1000; - const newt = new Date(this.timestamp).getTime() / 1000; - current = newt - old > 600; - } - const combine = - premessage?.author != this.author || current || this.message_reference; - if (combine) { - const pfp = this.author.buildpfp(); - this.author.bind(pfp, this.guild, false); - pfpRow.appendChild(pfp); - } else { - div["pfpparent"] = pfpparent; - } - pfpRow.classList.add("pfprow"); - build.appendChild(pfpRow); - const text = document.createElement("div"); - text.classList.add("flexttb"); - const texttxt = document.createElement("div"); - texttxt.classList.add("commentrow", "flexttb"); - text.appendChild(texttxt); - if (combine) { - const username = document.createElement("span"); - username.classList.add("username"); - this.author.bind(username, this.guild); - div.classList.add("topMessage"); - username.textContent = this.author.username; - const userwrap = document.createElement("div"); - userwrap.classList.add("flexltr"); - userwrap.appendChild(username); - if (this.author.bot) { - const username = document.createElement("span"); - username.classList.add("bot"); - username.textContent = "BOT"; - userwrap.appendChild(username); - } - const time = document.createElement("span"); - time.textContent = " " + formatTime(new Date(this.timestamp)); - time.classList.add("timestamp"); - userwrap.appendChild(time); + build.classList.add("flexltr", "message"); + div.classList.remove("zeroheight"); + if (this.author.relationshipType === 2) { + if (ignoredblock) { + if (premessage?.author !== this.author) { + const span = document.createElement("span"); + span.textContent = + "You have this user blocked, click to hide these messages."; + div.append(span); + span.classList.add("blocked"); + span.onclick = (_) => { + const scroll = this.channel.infinite.scrollTop; + let next: Message | undefined = this; + while (next?.author === this.author) { + next.generateMessage(); + next = this.channel.messages.get( + this.channel.idToNext.get(next.id) as string + ); + } + if (this.channel.infinite.scollDiv && scroll) { + this.channel.infinite.scollDiv.scrollTop = scroll; + } + }; + } + } else { + div.classList.remove("topMessage"); + if (premessage?.author === this.author) { + div.classList.add("zeroheight"); + premessage.blockedPropigate(); + div.appendChild(build); + return div; + } else { + build.classList.add("blocked", "topMessage"); + const span = document.createElement("span"); + let count = 1; + let next = this.channel.messages.get( + this.channel.idToNext.get(this.id) as string + ); + while (next?.author === this.author) { + count++; + next = this.channel.messages.get( + this.channel.idToNext.get(next.id) as string + ); + } + span.textContent = `You have this user blocked, click to see the ${count} blocked messages.`; + build.append(span); + span.onclick = (_) => { + const scroll = this.channel.infinite.scrollTop; + const func = this.channel.infinite.snapBottom(); + let next: Message | undefined = this; + while (next?.author === this.author) { + next.generateMessage(undefined, true); + next = this.channel.messages.get( + this.channel.idToNext.get(next.id) as string + ); + console.log("loopy"); + } + if (this.channel.infinite.scollDiv && scroll) { + func(); + this.channel.infinite.scollDiv.scrollTop = scroll; + } + }; + div.appendChild(build); + return div; + } + } + } + if (this.message_reference) { + const replyline = document.createElement("div"); + const line = document.createElement("hr"); + const minipfp = document.createElement("img"); + minipfp.classList.add("replypfp"); + replyline.appendChild(line); + replyline.appendChild(minipfp); + const username = document.createElement("span"); + replyline.appendChild(username); + const reply = document.createElement("div"); + username.classList.add("username"); + reply.classList.add("replytext"); + replyline.appendChild(reply); + const line2 = document.createElement("hr"); + replyline.appendChild(line2); + line2.classList.add("reply"); + line.classList.add("startreply"); + replyline.classList.add("replyflex"); + // TODO: Fix this + this.channel.getmessage(this.message_reference.id).then((message) => { + if (message.author.relationshipType === 2) { + username.textContent = "Blocked user"; + return; + } + const author = message.author; + reply.appendChild(message.content.makeHTML({ stdsize: true })); + minipfp.src = author.getpfpsrc(); + author.bind(minipfp, this.guild); + username.textContent = author.username; + author.bind(username, this.guild); + }); + reply.onclick = (_) => { + // TODO: FIX this + this.channel.infinite.focus(this.message_reference.id); + }; + div.appendChild(replyline); + } + div.appendChild(build); + if ({ 0: true, 19: true }[this.type] || this.attachments.length !== 0) { + const pfpRow = document.createElement("div"); + pfpRow.classList.add("flexltr"); + let pfpparent, current; + if (premessage != null) { + pfpparent ??= premessage; + // @ts-ignore + // TODO: type this + let pfpparent2 = pfpparent.all; + pfpparent2 ??= pfpparent; + const old = new Date(pfpparent2.timestamp).getTime() / 1000; + const newt = new Date(this.timestamp).getTime() / 1000; + current = newt - old > 600; + } + const combine = + premessage?.author != this.author || current || this.message_reference; + if (combine) { + const pfp = this.author.buildpfp(); + this.author.bind(pfp, this.guild, false); + pfpRow.appendChild(pfp); + } else { + div["pfpparent"] = pfpparent; + } + pfpRow.classList.add("pfprow"); + build.appendChild(pfpRow); + const text = document.createElement("div"); + text.classList.add("flexttb"); + const texttxt = document.createElement("div"); + texttxt.classList.add("commentrow", "flexttb"); + text.appendChild(texttxt); + if (combine) { + const username = document.createElement("span"); + username.classList.add("username"); + this.author.bind(username, this.guild); + div.classList.add("topMessage"); + username.textContent = this.author.username; + const userwrap = document.createElement("div"); + userwrap.classList.add("flexltr"); + userwrap.appendChild(username); + if (this.author.bot) { + const username = document.createElement("span"); + username.classList.add("bot"); + username.textContent = "BOT"; + userwrap.appendChild(username); + } + const time = document.createElement("span"); + time.textContent = " " + formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + userwrap.appendChild(time); - texttxt.appendChild(userwrap); - } else { - div.classList.remove("topMessage"); - } - const messaged = this.content.makeHTML(); - (div as any)["txt"] = messaged; - const messagedwrap = document.createElement("div"); - messagedwrap.classList.add("flexttb"); - messagedwrap.appendChild(messaged); - texttxt.appendChild(messagedwrap); + texttxt.appendChild(userwrap); + } else { + div.classList.remove("topMessage"); + } + const messaged = this.content.makeHTML(); + (div as any)["txt"] = messaged; + const messagedwrap = document.createElement("div"); + messagedwrap.classList.add("flexttb"); + messagedwrap.appendChild(messaged); + texttxt.appendChild(messagedwrap); - build.appendChild(text); - if (this.attachments.length) { - console.log(this.attachments); - const attach = document.createElement("div"); - attach.classList.add("flexltr"); - for (const thing of this.attachments) { - attach.appendChild(thing.getHTML()); - } - messagedwrap.appendChild(attach); - } - if (this.embeds.length) { - const embeds = document.createElement("div"); - embeds.classList.add("flexltr"); - for (const thing of this.embeds) { - embeds.appendChild(thing.generateHTML()); - } - messagedwrap.appendChild(embeds); - } - // - } else if (this.type === 7) { - const text = document.createElement("div"); - text.classList.add("flexttb"); - const texttxt = document.createElement("div"); - text.appendChild(texttxt); - build.appendChild(text); - texttxt.classList.add("flexltr"); - const messaged = document.createElement("span"); - div["txt"] = messaged; - messaged.textContent = "welcome: "; - texttxt.appendChild(messaged); + build.appendChild(text); + if (this.attachments.length) { + console.log(this.attachments); + const attach = document.createElement("div"); + attach.classList.add("flexltr"); + for (const thing of this.attachments) { + attach.appendChild(thing.getHTML()); + } + messagedwrap.appendChild(attach); + } + if (this.embeds.length) { + const embeds = document.createElement("div"); + embeds.classList.add("flexltr"); + for (const thing of this.embeds) { + embeds.appendChild(thing.generateHTML()); + } + messagedwrap.appendChild(embeds); + } + // + } else if (this.type === 7) { + const text = document.createElement("div"); + text.classList.add("flexttb"); + const texttxt = document.createElement("div"); + text.appendChild(texttxt); + build.appendChild(text); + texttxt.classList.add("flexltr"); + const messaged = document.createElement("span"); + div["txt"] = messaged; + messaged.textContent = "welcome: "; + texttxt.appendChild(messaged); - const username = document.createElement("span"); - username.textContent = this.author.username; - //this.author.profileclick(username); - this.author.bind(username, this.guild); - texttxt.appendChild(username); - username.classList.add("username"); + const username = document.createElement("span"); + username.textContent = this.author.username; + //this.author.profileclick(username); + this.author.bind(username, this.guild); + texttxt.appendChild(username); + username.classList.add("username"); - const time = document.createElement("span"); - time.textContent = " " + formatTime(new Date(this.timestamp)); - time.classList.add("timestamp"); - texttxt.append(time); - div.classList.add("topMessage"); - } - const reactions = document.createElement("div"); - reactions.classList.add("flexltr", "reactiondiv"); - this.reactdiv = new WeakRef(reactions); - this.updateReactions(); - div.append(reactions); - this.bindButtonEvent(); - return div; - } - bindButtonEvent() { - if (this.div) { - let buttons: HTMLDivElement | undefined; - this.div.onmouseenter = (_) => { - if (buttons) { - buttons.remove(); - buttons = undefined; - } - if (this.div) { - buttons = document.createElement("div"); - buttons.classList.add("messageButtons", "flexltr"); - if (this.channel.hasPermission("SEND_MESSAGES")) { - const container = document.createElement("div"); - const reply = document.createElement("span"); - reply.classList.add("svgtheme", "svg-reply", "svgicon"); - container.append(reply); - buttons.append(container); - container.onclick = (_) => { - this.channel.setReplying(this); - }; - } - if (this.author === this.localuser.user) { - const container = document.createElement("div"); - const edit = document.createElement("span"); - edit.classList.add("svgtheme", "svg-edit", "svgicon"); - container.append(edit); - buttons.append(container); - container.onclick = (_) => { - this.setEdit(); - }; - } - if (this.canDelete()) { - const container = document.createElement("div"); - const reply = document.createElement("span"); - reply.classList.add("svgtheme", "svg-delete", "svgicon"); - container.append(reply); - buttons.append(container); - container.onclick = (_) => { - if (_.shiftKey) { - this.delete(); - return; - } - const diaolog = new Dialog([ - "hdiv", - ["title", "are you sure you want to delete this?"], - [ - "button", - "", - "yes", - () => { - this.delete(); - diaolog.hide(); - }, - ], - [ - "button", - "", - "no", - () => { - diaolog.hide(); - }, - ], - ]); - diaolog.show(); - }; - } - if (buttons.childNodes.length !== 0) { - this.div.append(buttons); - } - } - }; - this.div.onmouseleave = (_) => { - if (buttons) { - buttons.remove(); - buttons = undefined; - } - }; - } - } - updateReactions() { - const reactdiv = this.reactdiv.deref(); - if (!reactdiv) return; - const func = this.channel.infinite.snapBottom(); - reactdiv.innerHTML = ""; - for (const thing of this.reactions) { - const reaction = document.createElement("div"); - reaction.classList.add("reaction"); - if (thing.me) { - reaction.classList.add("meReacted"); - } - let emoji: HTMLElement; - if (thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)) { - if (/\d{17,21}/.test(thing.emoji.name)) - thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug - const emo = new Emoji( - thing.emoji as { name: string; id: string; animated: boolean }, - this.guild - ); - emoji = emo.getHTML(false); - } else { - emoji = document.createElement("p"); - emoji.textContent = thing.emoji.name; - } - const count = document.createElement("p"); - count.textContent = "" + thing.count; - count.classList.add("reactionCount"); - reaction.append(count); - reaction.append(emoji); - reactdiv.append(reaction); + const time = document.createElement("span"); + time.textContent = " " + formatTime(new Date(this.timestamp)); + time.classList.add("timestamp"); + texttxt.append(time); + div.classList.add("topMessage"); + } + const reactions = document.createElement("div"); + reactions.classList.add("flexltr", "reactiondiv"); + this.reactdiv = new WeakRef(reactions); + this.updateReactions(); + div.append(reactions); + this.bindButtonEvent(); + return div; + } + bindButtonEvent() { + if (this.div) { + let buttons: HTMLDivElement | undefined; + this.div.onmouseenter = (_) => { + if (buttons) { + buttons.remove(); + buttons = undefined; + } + if (this.div) { + buttons = document.createElement("div"); + buttons.classList.add("messageButtons", "flexltr"); + if (this.channel.hasPermission("SEND_MESSAGES")) { + const container = document.createElement("div"); + const reply = document.createElement("span"); + reply.classList.add("svgtheme", "svg-reply", "svgicon"); + container.append(reply); + buttons.append(container); + container.onclick = (_) => { + this.channel.setReplying(this); + }; + } + if (this.author === this.localuser.user) { + const container = document.createElement("div"); + const edit = document.createElement("span"); + edit.classList.add("svgtheme", "svg-edit", "svgicon"); + container.append(edit); + buttons.append(container); + container.onclick = (_) => { + this.setEdit(); + }; + } + if (this.canDelete()) { + const container = document.createElement("div"); + const reply = document.createElement("span"); + reply.classList.add("svgtheme", "svg-delete", "svgicon"); + container.append(reply); + buttons.append(container); + container.onclick = (_) => { + if (_.shiftKey) { + this.delete(); + return; + } + const diaolog = new Dialog([ + "hdiv", + ["title", "are you sure you want to delete this?"], + [ + "button", + "", + "yes", + () => { + this.delete(); + diaolog.hide(); + }, + ], + [ + "button", + "", + "no", + () => { + diaolog.hide(); + }, + ], + ]); + diaolog.show(); + }; + } + if (buttons.childNodes.length !== 0) { + this.div.append(buttons); + } + } + }; + this.div.onmouseleave = (_) => { + if (buttons) { + buttons.remove(); + buttons = undefined; + } + }; + } + } + updateReactions() { + const reactdiv = this.reactdiv.deref(); + if (!reactdiv) return; + const func = this.channel.infinite.snapBottom(); + reactdiv.innerHTML = ""; + for (const thing of this.reactions) { + const reaction = document.createElement("div"); + reaction.classList.add("reaction"); + if (thing.me) { + reaction.classList.add("meReacted"); + } + let emoji: HTMLElement; + if (thing.emoji.id || /\d{17,21}/.test(thing.emoji.name)) { + if (/\d{17,21}/.test(thing.emoji.name)) + thing.emoji.id = thing.emoji.name; //Should stop being a thing once the server fixes this bug + const emo = new Emoji( + thing.emoji as { name: string; id: string; animated: boolean }, + this.guild + ); + emoji = emo.getHTML(false); + } else { + emoji = document.createElement("p"); + emoji.textContent = thing.emoji.name; + } + const count = document.createElement("p"); + count.textContent = "" + thing.count; + count.classList.add("reactionCount"); + reaction.append(count); + reaction.append(emoji); + reactdiv.append(reaction); - reaction.onclick = (_) => { - this.reactionToggle(thing.emoji.name); - }; - } - func(); - } - reactionAdd(data: { name: string }, member: Member | { id: string }) { - for (const thing of this.reactions) { - if (thing.emoji.name === data.name) { - thing.count++; - if (member.id === this.localuser.user.id) { - thing.me = true; - this.updateReactions(); - return; - } - } - } - this.reactions.push({ - count: 1, - emoji: data, - me: member.id === this.localuser.user.id, - }); - this.updateReactions(); - } - reactionRemove(data: { name: string }, id: string) { - console.log("test"); - for (const i in this.reactions) { - const thing = this.reactions[i]; - console.log(thing, data); - if (thing.emoji.name === data.name) { - thing.count--; - if (thing.count === 0) { - this.reactions.splice(Number(i), 1); - this.updateReactions(); - return; - } - if (id === this.localuser.user.id) { - thing.me = false; - this.updateReactions(); - return; - } - } - } - } - reactionRemoveAll() { - this.reactions = []; - this.updateReactions(); - } - reactionRemoveEmoji(emoji: Emoji) { - for (const i in this.reactions) { - const reaction = this.reactions[i]; - if ( - (reaction.emoji.id && reaction.emoji.id == emoji.id) || - (!reaction.emoji.id && reaction.emoji.name == emoji.name) - ) { - this.reactions.splice(Number(i), 1); - this.updateReactions(); - break; - } - } - } - buildhtml(premessage?: Message | undefined): HTMLElement { - if (this.div) { - console.error(`HTML for ${this.id} already exists, aborting`); - return this.div; - } - try { - const div = document.createElement("div"); - this.div = div; - this.messageevents(div); - return this.generateMessage(premessage) as HTMLElement; - } catch (e) { - console.error(e); - } - return this.div as HTMLElement; - } -} -let now: string; -let yesterdayStr: string; + reaction.onclick = (_) => { + this.reactionToggle(thing.emoji.name); + }; + } + func(); + } + reactionAdd(data: { name: string }, member: Member | { id: string }) { + for (const thing of this.reactions) { + if (thing.emoji.name === data.name) { + thing.count++; + if (member.id === this.localuser.user.id) { + thing.me = true; + this.updateReactions(); + return; + } + } + } + this.reactions.push({ + count: 1, + emoji: data, + me: member.id === this.localuser.user.id, + }); + this.updateReactions(); + } + reactionRemove(data: { name: string }, id: string) { + console.log("test"); + for (const i in this.reactions) { + const thing = this.reactions[i]; + console.log(thing, data); + if (thing.emoji.name === data.name) { + thing.count--; + if (thing.count === 0) { + this.reactions.splice(Number(i), 1); + this.updateReactions(); + return; + } + if (id === this.localuser.user.id) { + thing.me = false; + this.updateReactions(); + return; + } + } + } + } + reactionRemoveAll() { + this.reactions = []; + this.updateReactions(); + } + reactionRemoveEmoji(emoji: Emoji) { + for (const i in this.reactions) { + const reaction = this.reactions[i]; + if ( + (reaction.emoji.id && reaction.emoji.id == emoji.id) || + (!reaction.emoji.id && reaction.emoji.name == emoji.name) + ) { + this.reactions.splice(Number(i), 1); + this.updateReactions(); + break; + } + } + } + buildhtml(premessage?: Message | undefined): HTMLElement { + if (this.div) { + console.error(`HTML for ${this.id} already exists, aborting`); + return this.div; + } + try { + const div = document.createElement("div"); + this.div = div; + this.messageevents(div); + return this.generateMessage(premessage) as HTMLElement; + } catch (e) { + console.error(e); + } + return this.div as HTMLElement; + } + } + let now: string; + let yesterdayStr: string; -function formatTime(date: Date) { - updateTimes(); - const datestring = date.toLocaleDateString(); - const formatTime = (date: Date) => - date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); + function formatTime(date: Date) { + updateTimes(); + const datestring = date.toLocaleDateString(); + const formatTime = (date: Date) => + date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); - if (datestring === now) { - return `Today at ${formatTime(date)}`; - } else if (datestring === yesterdayStr) { - return `Yesterday at ${formatTime(date)}`; - } else { - return `${date.toLocaleDateString()} at ${formatTime(date)}`; - } -} -let tomorrow = 0; -updateTimes(); -function updateTimes() { - if (tomorrow < Date.now()) { - const d = new Date(); - tomorrow = d.setHours(24, 0, 0, 0); - now = new Date().toLocaleDateString(); - const yesterday = new Date(now); - yesterday.setDate(new Date().getDate() - 1); - yesterdayStr = yesterday.toLocaleDateString(); - } -} -Message.setup(); -export { Message }; + if (datestring === now) { + return `Today at ${formatTime(date)}`; + } else if (datestring === yesterdayStr) { + return `Yesterday at ${formatTime(date)}`; + } else { + return `${date.toLocaleDateString()} at ${formatTime(date)}`; + } + } + let tomorrow = 0; + updateTimes(); + function updateTimes() { + if (tomorrow < Date.now()) { + const d = new Date(); + tomorrow = d.setHours(24, 0, 0, 0); + now = new Date().toLocaleDateString(); + const yesterday = new Date(now); + yesterday.setDate(new Date().getDate() - 1); + yesterdayStr = yesterday.toLocaleDateString(); + } + } + Message.setup(); + export { Message }; diff --git a/src/webpage/permissions.ts b/src/webpage/permissions.ts index 308ffab..b6216d1 100644 --- a/src/webpage/permissions.ts +++ b/src/webpage/permissions.ts @@ -1,347 +1,347 @@ class Permissions { - allow: bigint; - deny: bigint; - readonly hasDeny: boolean; - constructor(allow: string, deny: string = "") { - this.hasDeny = Boolean(deny); - try { - this.allow = BigInt(allow); - this.deny = BigInt(deny); - } catch { - this.allow = 0n; - this.deny = 0n; - console.error( - `Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` - ); - } - } - getPermissionbit(b: number, big: bigint): boolean { - return Boolean((big >> BigInt(b)) & 1n); - } - setPermissionbit(b: number, state: boolean, big: bigint): bigint { - const bit = 1n << BigInt(b); - return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 - } - static map: { - [key: number | string]: - | { name: string; readableName: string; description: string } - | number; - }; - static info: { name: string; readableName: string; description: string }[]; - static makeMap() { - Permissions.info = [ - //for people in the future, do not reorder these, the creation of the map realize on the order - { - name: "CREATE_INSTANT_INVITE", - readableName: "Create invite", - description: "Allows the user to create invites for the guild", - }, - { - name: "KICK_MEMBERS", - readableName: "Kick members", - description: "Allows the user to kick members from the guild", - }, - { - name: "BAN_MEMBERS", - readableName: "Ban members", - description: "Allows the user to ban members from the guild", - }, - { - name: "ADMINISTRATOR", - readableName: "Administrator", - description: - "Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", - }, - { - name: "MANAGE_CHANNELS", - readableName: "Manage channels", - description: "Allows the user to manage and edit channels", - }, - { - name: "MANAGE_GUILD", - readableName: "Manage guild", - description: "Allows management and editing of the guild", - }, - { - name: "ADD_REACTIONS", - readableName: "Add reactions", - description: "Allows user to add reactions to messages", - }, - { - name: "VIEW_AUDIT_LOG", - readableName: "View audit log", - description: "Allows the user to view the audit log", - }, - { - name: "PRIORITY_SPEAKER", - readableName: "Priority speaker", - description: "Allows for using priority speaker in a voice channel", - }, - { - name: "STREAM", - readableName: "Video", - description: "Allows the user to stream", - }, - { - name: "VIEW_CHANNEL", - readableName: "View channels", - description: "Allows the user to view the channel", - }, - { - name: "SEND_MESSAGES", - readableName: "Send messages", - description: "Allows user to send messages", - }, - { - name: "SEND_TTS_MESSAGES", - readableName: "Send text-to-speech messages", - description: "Allows the user to send text-to-speech messages", - }, - { - name: "MANAGE_MESSAGES", - readableName: "Manage messages", - description: "Allows the user to delete messages that aren't their own", - }, - { - name: "EMBED_LINKS", - readableName: "Embed links", - description: "Allow links sent by this user to auto-embed", - }, - { - name: "ATTACH_FILES", - readableName: "Attach files", - description: "Allows the user to attach files", - }, - { - name: "READ_MESSAGE_HISTORY", - readableName: "Read message history", - description: "Allows user to read the message history", - }, - { - name: "MENTION_EVERYONE", - readableName: "Mention @everyone, @here and all roles", - description: "Allows the user to mention everyone", - }, - { - name: "USE_EXTERNAL_EMOJIS", - readableName: "Use external emojis", - description: "Allows the user to use external emojis", - }, - { - name: "VIEW_GUILD_INSIGHTS", - readableName: "View guild insights", - description: "Allows the user to see guild insights", - }, - { - name: "CONNECT", - readableName: "Connect", - description: "Allows the user to connect to a voice channel", - }, - { - name: "SPEAK", - readableName: "Speak", - description: "Allows the user to speak in a voice channel", - }, - { - name: "MUTE_MEMBERS", - readableName: "Mute members", - description: "Allows user to mute other members", - }, - { - name: "DEAFEN_MEMBERS", - readableName: "Deafen members", - description: "Allows user to deafen other members", - }, - { - name: "MOVE_MEMBERS", - readableName: "Move members", - description: "Allows the user to move members between voice channels", - }, - { - name: "USE_VAD", - readableName: "Use voice activity detection", - description: - "Allows users to speak in a voice channel by simply talking", - }, - { - name: "CHANGE_NICKNAME", - readableName: "Change nickname", - description: "Allows the user to change their own nickname", - }, - { - name: "MANAGE_NICKNAMES", - readableName: "Manage nicknames", - description: "Allows user to change nicknames of other members", - }, - { - name: "MANAGE_ROLES", - readableName: "Manage roles", - description: "Allows user to edit and manage roles", - }, - { - name: "MANAGE_WEBHOOKS", - readableName: "Manage webhooks", - description: "Allows management and editing of webhooks", - }, - { - name: "MANAGE_GUILD_EXPRESSIONS", - readableName: "Manage expressions", - description: "Allows for managing emoji, stickers, and soundboards", - }, - { - name: "USE_APPLICATION_COMMANDS", - readableName: "Use application commands", - description: "Allows the user to use application commands", - }, - { - name: "REQUEST_TO_SPEAK", - readableName: "Request to speak", - description: "Allows user to request to speak in stage channel", - }, - { - name: "MANAGE_EVENTS", - readableName: "Manage events", - description: "Allows user to edit and manage events", - }, - { - name: "MANAGE_THREADS", - readableName: "Manage threads", - description: - "Allows the user to delete and archive threads and view all private threads", - }, - { - name: "CREATE_PUBLIC_THREADS", - readableName: "Create public threads", - description: "Allows the user to create public threads", - }, - { - name: "CREATE_PRIVATE_THREADS", - readableName: "Create private threads", - description: "Allows the user to create private threads", - }, - { - name: "USE_EXTERNAL_STICKERS", - readableName: "Use external stickers", - description: "Allows user to use external stickers", - }, - { - name: "SEND_MESSAGES_IN_THREADS", - readableName: "Send messages in threads", - description: "Allows the user to send messages in threads", - }, - { - name: "USE_EMBEDDED_ACTIVITIES", - readableName: "Use activities", - description: "Allows the user to use embedded activities", - }, - { - name: "MODERATE_MEMBERS", - readableName: "Timeout members", - 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", - }, - { - name: "VIEW_CREATOR_MONETIZATION_ANALYTICS", - readableName: "View creator monetization analytics", - description: "Allows for viewing role subscription insights", - }, - { - name: "USE_SOUNDBOARD", - readableName: "Use soundboard", - description: "Allows for using soundboard in a voice channel", - }, - { - name: "CREATE_GUILD_EXPRESSIONS", - readableName: "Create expressions", - description: - "Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", - }, - { - name: "CREATE_EVENTS", - readableName: "Create events", - description: - "Allows for creating scheduled events, and editing and deleting those created by the current user.", - }, - { - name: "USE_EXTERNAL_SOUNDS", - readableName: "Use external sounds", - description: - "Allows the usage of custom soundboard sounds from other servers", - }, - { - name: "SEND_VOICE_MESSAGES", - readableName: "Send voice messages", - description: "Allows sending voice messages", - }, - { - name: "SEND_POLLS", - readableName: "Create polls", - description: "Allows sending polls", - }, - { - name: "USE_EXTERNAL_APPS", - readableName: "Use external apps", - description: - "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. " + - "This only applies to apps not also installed to the server.", - }, - ]; - Permissions.map = {}; - let i = 0; - for (const thing of Permissions.info) { - Permissions.map[i] = thing; - Permissions.map[thing.name] = i; - i++; - } - } - getPermission(name: string): number { - if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) { - return 1; - } else if ( - this.getPermissionbit(Permissions.map[name] as number, this.deny) - ) { - return -1; - } else { - return 0; - } - } - hasPermission(name: string): boolean { - if (this.deny) { - console.warn( - "This function may of been used in error, think about using getPermision instead" - ); - } - if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) - return true; - if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR"); - return false; - } - setPermission(name: string, setto: number): void { - const bit = Permissions.map[name] as number; - if (!bit) { - return console.error( - "Tried to set permission to " + - setto + - " for " + - name + - " but it doesn't exist" - ); - } +allow: bigint; +deny: bigint; +readonly hasDeny: boolean; +constructor(allow: string, deny: string = "") { +this.hasDeny = Boolean(deny); +try { +this.allow = BigInt(allow); +this.deny = BigInt(deny); +} catch { +this.allow = 0n; +this.deny = 0n; +console.error( +`Something really stupid happened with a permission with allow being ${allow} and deny being, ${deny}, execution will still happen, but something really stupid happened, please report if you know what caused this.` +); +} +} +getPermissionbit(b: number, big: bigint): boolean { +return Boolean((big >> BigInt(b)) & 1n); +} +setPermissionbit(b: number, state: boolean, big: bigint): bigint { +const bit = 1n << BigInt(b); +return (big & ~bit) | (BigInt(state) << BigInt(b)); //thanks to geotale for this code :3 +} +static map: { +[key: number | string]: +| { name: string; readableName: string; description: string } +| number; +}; +static info: { name: string; readableName: string; description: string }[]; +static makeMap() { +Permissions.info = [ +//for people in the future, do not reorder these, the creation of the map realize on the order +{ +name: "CREATE_INSTANT_INVITE", +readableName: "Create invite", +description: "Allows the user to create invites for the guild", +}, +{ +name: "KICK_MEMBERS", +readableName: "Kick members", +description: "Allows the user to kick members from the guild", +}, +{ +name: "BAN_MEMBERS", +readableName: "Ban members", +description: "Allows the user to ban members from the guild", +}, +{ +name: "ADMINISTRATOR", +readableName: "Administrator", +description: +"Allows all permissions and bypasses channel permission overwrites. This is a dangerous permission!", +}, +{ +name: "MANAGE_CHANNELS", +readableName: "Manage channels", +description: "Allows the user to manage and edit channels", +}, +{ +name: "MANAGE_GUILD", +readableName: "Manage guild", +description: "Allows management and editing of the guild", +}, +{ +name: "ADD_REACTIONS", +readableName: "Add reactions", +description: "Allows user to add reactions to messages", +}, +{ +name: "VIEW_AUDIT_LOG", +readableName: "View audit log", +description: "Allows the user to view the audit log", +}, +{ +name: "PRIORITY_SPEAKER", +readableName: "Priority speaker", +description: "Allows for using priority speaker in a voice channel", +}, +{ +name: "STREAM", +readableName: "Video", +description: "Allows the user to stream", +}, +{ +name: "VIEW_CHANNEL", +readableName: "View channels", +description: "Allows the user to view the channel", +}, +{ +name: "SEND_MESSAGES", +readableName: "Send messages", +description: "Allows user to send messages", +}, +{ +name: "SEND_TTS_MESSAGES", +readableName: "Send text-to-speech messages", +description: "Allows the user to send text-to-speech messages", +}, +{ +name: "MANAGE_MESSAGES", +readableName: "Manage messages", +description: "Allows the user to delete messages that aren't their own", +}, +{ +name: "EMBED_LINKS", +readableName: "Embed links", +description: "Allow links sent by this user to auto-embed", +}, +{ +name: "ATTACH_FILES", +readableName: "Attach files", +description: "Allows the user to attach files", +}, +{ +name: "READ_MESSAGE_HISTORY", +readableName: "Read message history", +description: "Allows user to read the message history", +}, +{ +name: "MENTION_EVERYONE", +readableName: "Mention @everyone, @here and all roles", +description: "Allows the user to mention everyone", +}, +{ +name: "USE_EXTERNAL_EMOJIS", +readableName: "Use external emojis", +description: "Allows the user to use external emojis", +}, +{ +name: "VIEW_GUILD_INSIGHTS", +readableName: "View guild insights", +description: "Allows the user to see guild insights", +}, +{ +name: "CONNECT", +readableName: "Connect", +description: "Allows the user to connect to a voice channel", +}, +{ +name: "SPEAK", +readableName: "Speak", +description: "Allows the user to speak in a voice channel", +}, +{ +name: "MUTE_MEMBERS", +readableName: "Mute members", +description: "Allows user to mute other members", +}, +{ +name: "DEAFEN_MEMBERS", +readableName: "Deafen members", +description: "Allows user to deafen other members", +}, +{ +name: "MOVE_MEMBERS", +readableName: "Move members", +description: "Allows the user to move members between voice channels", +}, +{ +name: "USE_VAD", +readableName: "Use voice activity detection", +description: +"Allows users to speak in a voice channel by simply talking", +}, +{ +name: "CHANGE_NICKNAME", +readableName: "Change nickname", +description: "Allows the user to change their own nickname", +}, +{ +name: "MANAGE_NICKNAMES", +readableName: "Manage nicknames", +description: "Allows user to change nicknames of other members", +}, +{ +name: "MANAGE_ROLES", +readableName: "Manage roles", +description: "Allows user to edit and manage roles", +}, +{ +name: "MANAGE_WEBHOOKS", +readableName: "Manage webhooks", +description: "Allows management and editing of webhooks", +}, +{ +name: "MANAGE_GUILD_EXPRESSIONS", +readableName: "Manage expressions", +description: "Allows for managing emoji, stickers, and soundboards", +}, +{ +name: "USE_APPLICATION_COMMANDS", +readableName: "Use application commands", +description: "Allows the user to use application commands", +}, +{ +name: "REQUEST_TO_SPEAK", +readableName: "Request to speak", +description: "Allows user to request to speak in stage channel", +}, +{ +name: "MANAGE_EVENTS", +readableName: "Manage events", +description: "Allows user to edit and manage events", +}, +{ +name: "MANAGE_THREADS", +readableName: "Manage threads", +description: +"Allows the user to delete and archive threads and view all private threads", +}, +{ +name: "CREATE_PUBLIC_THREADS", +readableName: "Create public threads", +description: "Allows the user to create public threads", +}, +{ +name: "CREATE_PRIVATE_THREADS", +readableName: "Create private threads", +description: "Allows the user to create private threads", +}, +{ +name: "USE_EXTERNAL_STICKERS", +readableName: "Use external stickers", +description: "Allows user to use external stickers", +}, +{ +name: "SEND_MESSAGES_IN_THREADS", +readableName: "Send messages in threads", +description: "Allows the user to send messages in threads", +}, +{ +name: "USE_EMBEDDED_ACTIVITIES", +readableName: "Use activities", +description: "Allows the user to use embedded activities", +}, +{ +name: "MODERATE_MEMBERS", +readableName: "Timeout members", +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", +}, +{ +name: "VIEW_CREATOR_MONETIZATION_ANALYTICS", +readableName: "View creator monetization analytics", +description: "Allows for viewing role subscription insights", +}, +{ +name: "USE_SOUNDBOARD", +readableName: "Use soundboard", +description: "Allows for using soundboard in a voice channel", +}, +{ +name: "CREATE_GUILD_EXPRESSIONS", +readableName: "Create expressions", +description: +"Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.", +}, +{ +name: "CREATE_EVENTS", +readableName: "Create events", +description: +"Allows for creating scheduled events, and editing and deleting those created by the current user.", +}, +{ +name: "USE_EXTERNAL_SOUNDS", +readableName: "Use external sounds", +description: +"Allows the usage of custom soundboard sounds from other servers", +}, +{ +name: "SEND_VOICE_MESSAGES", +readableName: "Send voice messages", +description: "Allows sending voice messages", +}, +{ +name: "SEND_POLLS", +readableName: "Create polls", +description: "Allows sending polls", +}, +{ +name: "USE_EXTERNAL_APPS", +readableName: "Use external apps", +description: +"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. " + +"This only applies to apps not also installed to the server.", +}, +]; +Permissions.map = {}; +let i = 0; +for (const thing of Permissions.info) { +Permissions.map[i] = thing; +Permissions.map[thing.name] = i; +i++; +} +} +getPermission(name: string): number { +if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) { +return 1; +} else if ( +this.getPermissionbit(Permissions.map[name] as number, this.deny) +) { +return -1; +} else { +return 0; +} +} +hasPermission(name: string): boolean { +if (this.deny) { +console.warn( +"This function may of been used in error, think about using getPermision instead" +); +} +if (this.getPermissionbit(Permissions.map[name] as number, this.allow)) +return true; +if (name != "ADMINISTRATOR") return this.hasPermission("ADMINISTRATOR"); +return false; +} +setPermission(name: string, setto: number): void { +const bit = Permissions.map[name] as number; +if (!bit) { +return console.error( +"Tried to set permission to " + +setto + +" for " + +name + +" but it doesn't exist" +); +} - if (setto === 0) { - this.deny = this.setPermissionbit(bit, false, this.deny); - this.allow = this.setPermissionbit(bit, false, this.allow); - } else if (setto === 1) { - this.deny = this.setPermissionbit(bit, false, this.deny); - this.allow = this.setPermissionbit(bit, true, this.allow); - } else if (setto === -1) { - this.deny = this.setPermissionbit(bit, true, this.deny); - this.allow = this.setPermissionbit(bit, false, this.allow); - } else { - console.error("invalid number entered:" + setto); - } - } +if (setto === 0) { +this.deny = this.setPermissionbit(bit, false, this.deny); +this.allow = this.setPermissionbit(bit, false, this.allow); +} else if (setto === 1) { +this.deny = this.setPermissionbit(bit, false, this.deny); +this.allow = this.setPermissionbit(bit, true, this.allow); +} else if (setto === -1) { +this.deny = this.setPermissionbit(bit, true, this.deny); +this.allow = this.setPermissionbit(bit, false, this.allow); +} else { +console.error("invalid number entered:" + setto); +} +} } Permissions.makeMap(); export { Permissions }; diff --git a/src/webpage/register.ts b/src/webpage/register.ts index f36f46d..f08c224 100644 --- a/src/webpage/register.ts +++ b/src/webpage/register.ts @@ -2,149 +2,149 @@ import { checkInstance, adduser } from "./login.js"; const registerElement = document.getElementById("register"); if (registerElement) { - registerElement.addEventListener("submit", registertry); +registerElement.addEventListener("submit", registertry); } async function registertry(e: Event) { - e.preventDefault(); - const elements = (e.target as HTMLFormElement) - .elements as HTMLFormControlsCollection; - const email = (elements[1] as HTMLInputElement).value; - const username = (elements[2] as HTMLInputElement).value; - const password = (elements[3] as HTMLInputElement).value; - const confirmPassword = (elements[4] as HTMLInputElement).value; - const dateofbirth = (elements[5] as HTMLInputElement).value; - const consent = (elements[6] as HTMLInputElement).checked; - const captchaKey = (elements[7] as HTMLInputElement)?.value; +e.preventDefault(); +const elements = (e.target as HTMLFormElement) +.elements as HTMLFormControlsCollection; +const email = (elements[1] as HTMLInputElement).value; +const username = (elements[2] as HTMLInputElement).value; +const password = (elements[3] as HTMLInputElement).value; +const confirmPassword = (elements[4] as HTMLInputElement).value; +const dateofbirth = (elements[5] as HTMLInputElement).value; +const consent = (elements[6] as HTMLInputElement).checked; +const captchaKey = (elements[7] as HTMLInputElement)?.value; - if (password !== confirmPassword) { - (document.getElementById("wrong") as HTMLElement).textContent = - "Passwords don't match"; - return; - } +if (password !== confirmPassword) { +(document.getElementById("wrong") as HTMLElement).textContent = +"Passwords don't match"; +return; +} - const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); - const apiurl = new URL(instanceInfo.api); +const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); +const apiurl = new URL(instanceInfo.api); - try { - const response = await fetch(apiurl + "/auth/register", { - body: JSON.stringify({ - date_of_birth: dateofbirth, - email, - username, - password, - consent, - captcha_key: captchaKey, - }), - headers: { - "content-type": "application/json", - }, - method: "POST", - }); +try { +const response = await fetch(apiurl + "/auth/register", { +body: JSON.stringify({ +date_of_birth: dateofbirth, +email, +username, +password, +consent, +captcha_key: captchaKey, +}), +headers: { +"content-type": "application/json", +}, +method: "POST", +}); - const data = await response.json(); +const data = await response.json(); - if (data.captcha_sitekey) { - const capt = document.getElementById("h-captcha"); - if (capt && !capt.children.length) { - const capty = document.createElement("div"); - capty.classList.add("h-captcha"); - capty.setAttribute("data-sitekey", data.captcha_sitekey); - const script = document.createElement("script"); - script.src = "https://js.hcaptcha.com/1/api.js"; - capt.append(script); - capt.append(capty); - } else { - eval("hcaptcha.reset()"); - } - return; - } +if (data.captcha_sitekey) { +const capt = document.getElementById("h-captcha"); +if (capt && !capt.children.length) { +const capty = document.createElement("div"); +capty.classList.add("h-captcha"); +capty.setAttribute("data-sitekey", data.captcha_sitekey); +const script = document.createElement("script"); +script.src = "https://js.hcaptcha.com/1/api.js"; +capt.append(script); +capt.append(capty); +} else { +eval("hcaptcha.reset()"); +} +return; +} - if (!data.token) { - handleErrors(data.errors, elements); - } else { - adduser({ - serverurls: instanceInfo, - email, - token: data.token, - }).username = username; - localStorage.setItem("token", data.token); - const redir = new URLSearchParams(window.location.search).get("goback"); - window.location.href = redir ? redir : "/channels/@me"; - } - } catch (error) { - console.error("Registration failed:", error); - } +if (!data.token) { +handleErrors(data.errors, elements); +} else { +adduser({ +serverurls: instanceInfo, +email, +token: data.token, +}).username = username; +localStorage.setItem("token", data.token); +const redir = new URLSearchParams(window.location.search).get("goback"); +window.location.href = redir ? redir : "/channels/@me"; +} +} catch (error) { +console.error("Registration failed:", error); +} } function handleErrors(errors: any, elements: HTMLFormControlsCollection) { - if (errors.consent) { - showError(elements[6] as HTMLElement, errors.consent._errors[0].message); - } else if (errors.password) { - showError( - elements[3] as HTMLElement, - "Password: " + errors.password._errors[0].message - ); - } else if (errors.username) { - showError( - elements[2] as HTMLElement, - "Username: " + errors.username._errors[0].message - ); - } else if (errors.email) { - showError( - elements[1] as HTMLElement, - "Email: " + errors.email._errors[0].message - ); - } else if (errors.date_of_birth) { - showError( - elements[5] as HTMLElement, - "Date of Birth: " + errors.date_of_birth._errors[0].message - ); - } else { - (document.getElementById("wrong") as HTMLElement).textContent = - errors[Object.keys(errors)[0]]._errors[0].message; - } +if (errors.consent) { +showError(elements[6] as HTMLElement, errors.consent._errors[0].message); +} else if (errors.password) { +showError( +elements[3] as HTMLElement, +"Password: " + errors.password._errors[0].message +); +} else if (errors.username) { +showError( +elements[2] as HTMLElement, +"Username: " + errors.username._errors[0].message +); +} else if (errors.email) { +showError( +elements[1] as HTMLElement, +"Email: " + errors.email._errors[0].message +); +} else if (errors.date_of_birth) { +showError( +elements[5] as HTMLElement, +"Date of Birth: " + errors.date_of_birth._errors[0].message +); +} else { +(document.getElementById("wrong") as HTMLElement).textContent = +errors[Object.keys(errors)[0]]._errors[0].message; +} } function showError(element: HTMLElement, message: string) { - const parent = element.parentElement!; - let errorElement = parent.getElementsByClassName( - "suberror" - )[0] as HTMLElement; - if (!errorElement) { - const div = document.createElement("div"); - div.classList.add("suberror", "suberrora"); - parent.append(div); - errorElement = div; - } else { - errorElement.classList.remove("suberror"); - setTimeout(() => { - errorElement.classList.add("suberror"); - }, 100); - } - errorElement.textContent = message; +const parent = element.parentElement!; +let errorElement = parent.getElementsByClassName( +"suberror" +)[0] as HTMLElement; +if (!errorElement) { +const div = document.createElement("div"); +div.classList.add("suberror", "suberrora"); +parent.append(div); +errorElement = div; +} else { +errorElement.classList.remove("suberror"); +setTimeout(() => { +errorElement.classList.add("suberror"); +}, 100); +} +errorElement.textContent = message; } let TOSa = document.getElementById("TOSa") as HTMLAnchorElement | null; async function tosLogic() { - const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); - const apiurl = new URL(instanceInfo.api); - const response = await fetch(apiurl.toString() + "/ping"); - const data = await response.json(); - const tosPage = data.instance.tosPage; +const instanceInfo = JSON.parse(localStorage.getItem("instanceinfo") ?? "{}"); +const apiurl = new URL(instanceInfo.api); +const response = await fetch(apiurl.toString() + "/ping"); +const data = await response.json(); +const tosPage = data.instance.tosPage; - if (tosPage) { - document.getElementById("TOSbox")!.innerHTML = - 'I agree to the Terms of Service:'; - TOSa = document.getElementById("TOSa") as HTMLAnchorElement; - TOSa.href = tosPage; - } else { - document.getElementById("TOSbox")!.textContent = - "This instance has no Terms of Service, accept ToS anyways:"; - TOSa = null; - } - console.log(tosPage); +if (tosPage) { +document.getElementById("TOSbox")!.innerHTML = +'I agree to the Terms of Service:'; +TOSa = document.getElementById("TOSa") as HTMLAnchorElement; +TOSa.href = tosPage; +} else { +document.getElementById("TOSbox")!.textContent = +"This instance has no Terms of Service, accept ToS anyways:"; +TOSa = null; +} +console.log(tosPage); } tosLogic(); diff --git a/src/webpage/role.ts b/src/webpage/role.ts index a024aae..43be79c 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -4,177 +4,177 @@ import { Guild } from "./guild.js"; import { SnowFlake } from "./snowflake.js"; import { rolesjson } from "./jsontypes.js"; class Role extends SnowFlake { - permissions: Permissions; - owner: Guild; - color!: number; - name!: string; - info: Guild["info"]; - hoist!: boolean; - icon!: string; - mentionable!: boolean; - unicode_emoji!: string; - headers: Guild["headers"]; - constructor(json: rolesjson, owner: Guild) { - super(json.id); - this.headers = owner.headers; - this.info = owner.info; - for (const thing of Object.keys(json)) { - if (thing === "id") { - continue; - } - (this as any)[thing] = (json as any)[thing]; - } - this.permissions = new Permissions(json.permissions); - this.owner = owner; - } - get guild(): Guild { - return this.owner; - } - get localuser(): Localuser { - return this.guild.localuser; - } - getColor(): string | null { - if (this.color === 0) { - return null; - } - return `#${this.color.toString(16)}`; - } +permissions: Permissions; +owner: Guild; +color!: number; +name!: string; +info: Guild["info"]; +hoist!: boolean; +icon!: string; +mentionable!: boolean; +unicode_emoji!: string; +headers: Guild["headers"]; +constructor(json: rolesjson, owner: Guild) { +super(json.id); +this.headers = owner.headers; +this.info = owner.info; +for (const thing of Object.keys(json)) { +if (thing === "id") { +continue; +} +(this as any)[thing] = (json as any)[thing]; +} +this.permissions = new Permissions(json.permissions); +this.owner = owner; +} +get guild(): Guild { +return this.owner; +} +get localuser(): Localuser { +return this.guild.localuser; +} +getColor(): string | null { +if (this.color === 0) { +return null; +} +return `#${this.color.toString(16)}`; +} } export { Role }; import { Options } from "./settings.js"; class PermissionToggle implements OptionsElement { - readonly rolejson: { - name: string; - readableName: string; - description: string; - }; - permissions: Permissions; - owner: Options; - value!: number; - constructor( - roleJSON: PermissionToggle["rolejson"], - permissions: Permissions, - owner: Options - ) { - this.rolejson = roleJSON; - this.permissions = permissions; - this.owner = owner; - } - watchForChange() {} - generateHTML(): HTMLElement { - const div = document.createElement("div"); - div.classList.add("setting"); - const name = document.createElement("span"); - name.textContent = this.rolejson.readableName; - name.classList.add("settingsname"); - div.append(name); + readonly rolejson: { + name: string; + readableName: string; + description: string; + }; + permissions: Permissions; + owner: Options; + value!: number; + constructor( + roleJSON: PermissionToggle["rolejson"], + permissions: Permissions, + owner: Options + ) { + this.rolejson = roleJSON; + this.permissions = permissions; + this.owner = owner; + } + watchForChange() {} + generateHTML(): HTMLElement { + const div = document.createElement("div"); + div.classList.add("setting"); + const name = document.createElement("span"); + name.textContent = this.rolejson.readableName; + name.classList.add("settingsname"); + div.append(name); - div.append(this.generateCheckbox()); - const p = document.createElement("p"); - p.textContent = this.rolejson.description; - div.appendChild(p); - return div; - } - generateCheckbox(): HTMLElement { - const div = document.createElement("div"); - div.classList.add("tritoggle"); - const state = this.permissions.getPermission(this.rolejson.name); + div.append(this.generateCheckbox()); + const p = document.createElement("p"); + p.textContent = this.rolejson.description; + div.appendChild(p); + return div; + } + generateCheckbox(): HTMLElement { + const div = document.createElement("div"); + div.classList.add("tritoggle"); + const state = this.permissions.getPermission(this.rolejson.name); - const on = document.createElement("input"); - on.type = "radio"; - on.name = this.rolejson.name; - div.append(on); - if (state === 1) { - on.checked = true; - } - on.onclick = (_) => { - this.permissions.setPermission(this.rolejson.name, 1); - this.owner.changed(); - }; + const on = document.createElement("input"); + on.type = "radio"; + on.name = this.rolejson.name; + div.append(on); + if (state === 1) { + on.checked = true; + } + on.onclick = (_) => { + this.permissions.setPermission(this.rolejson.name, 1); + this.owner.changed(); + }; - const no = document.createElement("input"); - no.type = "radio"; - no.name = this.rolejson.name; - div.append(no); - if (state === 0) { - no.checked = true; - } - no.onclick = (_) => { - this.permissions.setPermission(this.rolejson.name, 0); - this.owner.changed(); - }; - if (this.permissions.hasDeny) { - const off = document.createElement("input"); - off.type = "radio"; - off.name = this.rolejson.name; - div.append(off); - if (state === -1) { - off.checked = true; - } - off.onclick = (_) => { - this.permissions.setPermission(this.rolejson.name, -1); - this.owner.changed(); - }; - } - return div; - } - submit() {} -} -import { OptionsElement, Buttons } from "./settings.js"; -class RoleList extends Buttons { - readonly permissions: [Role, Permissions][]; - permission: Permissions; - readonly guild: Guild; - readonly channel: boolean; - declare readonly buttons: [string, string][]; - readonly options: Options; - onchange: Function; - curid!: string; - constructor( - permissions: [Role, Permissions][], - guild: Guild, - onchange: Function, - channel = false - ) { - super("Roles"); - this.guild = guild; - this.permissions = permissions; - this.channel = channel; - this.onchange = onchange; - const options = new Options("", this); - if (channel) { - this.permission = new Permissions("0", "0"); - } else { - this.permission = new Permissions("0"); - } - for (const thing of Permissions.info) { - options.options.push( - new PermissionToggle(thing, this.permission, options) - ); - } - for (const i of permissions) { - console.log(i); - this.buttons.push([i[0].name, i[0].id]); - } - this.options = options; - } - handleString(str: string): HTMLElement { - this.curid = str; - const arr = this.permissions.find((_) => _[0].id === str); - if (arr) { - const perm = arr[1]; - this.permission.deny = perm.deny; - this.permission.allow = perm.allow; - const role = this.permissions.find((e) => e[0].id === str); - if (role) { - this.options.name = role[0].name; - this.options.haschanged = false; - } - } - return this.options.generateHTML(); - } - save() { - this.onchange(this.curid, this.permission); - } -} -export { RoleList }; + const no = document.createElement("input"); + no.type = "radio"; + no.name = this.rolejson.name; + div.append(no); + if (state === 0) { + no.checked = true; + } + no.onclick = (_) => { + this.permissions.setPermission(this.rolejson.name, 0); + this.owner.changed(); + }; + if (this.permissions.hasDeny) { + const off = document.createElement("input"); + off.type = "radio"; + off.name = this.rolejson.name; + div.append(off); + if (state === -1) { + off.checked = true; + } + off.onclick = (_) => { + this.permissions.setPermission(this.rolejson.name, -1); + this.owner.changed(); + }; + } + return div; + } + submit() {} + } + import { OptionsElement, Buttons } from "./settings.js"; + class RoleList extends Buttons { + readonly permissions: [Role, Permissions][]; + permission: Permissions; + readonly guild: Guild; + readonly channel: boolean; + declare readonly buttons: [string, string][]; + readonly options: Options; + onchange: Function; + curid!: string; + constructor( + permissions: [Role, Permissions][], + guild: Guild, + onchange: Function, + channel = false + ) { + super("Roles"); + this.guild = guild; + this.permissions = permissions; + this.channel = channel; + this.onchange = onchange; + const options = new Options("", this); + if (channel) { + this.permission = new Permissions("0", "0"); + } else { + this.permission = new Permissions("0"); + } + for (const thing of Permissions.info) { + options.options.push( + new PermissionToggle(thing, this.permission, options) + ); + } + for (const i of permissions) { + console.log(i); + this.buttons.push([i[0].name, i[0].id]); + } + this.options = options; + } + handleString(str: string): HTMLElement { + this.curid = str; + const arr = this.permissions.find((_) => _[0].id === str); + if (arr) { + const perm = arr[1]; + this.permission.deny = perm.deny; + this.permission.allow = perm.allow; + const role = this.permissions.find((e) => e[0].id === str); + if (role) { + this.options.name = role[0].name; + this.options.haschanged = false; + } + } + return this.options.generateHTML(); + } + save() { + this.onchange(this.curid, this.permission); + } + } + export { RoleList }; diff --git a/src/webpage/service.ts b/src/webpage/service.ts index 67256c6..d711670 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -1,96 +1,96 @@ function deleteoldcache() { - caches.delete("cache"); - console.log("this ran :P"); +caches.delete("cache"); +console.log("this ran :P"); } async function putInCache(request: URL | RequestInfo, response: Response) { - console.log(request, response); - const cache = await caches.open("cache"); - console.log("Grabbed"); - try { - console.log(await cache.put(request, response)); - } catch (error) { - console.error(error); - } +console.log(request, response); +const cache = await caches.open("cache"); +console.log("Grabbed"); +try { +console.log(await cache.put(request, response)); +} catch (error) { +console.error(error); +} } console.log("test"); let lastcache: string; self.addEventListener("activate", async () => { - console.log("test2"); - checkCache(); +console.log("test2"); +checkCache(); }); async function checkCache() { - if (checkedrecently) { - return; - } - const promise = await caches.match("/getupdates"); - if (promise) { - lastcache = await promise.text(); - } - console.log(lastcache); - fetch("/getupdates").then(async (data) => { - const text = await data.clone().text(); - console.log(text, lastcache); - if (lastcache !== text) { - deleteoldcache(); - putInCache("/getupdates", data.clone()); - } - checkedrecently = true; - setTimeout((_: any) => { - checkedrecently = false; - }, 1000 * 60 * 30); - }); +if (checkedrecently) { +return; +} +const promise = await caches.match("/getupdates"); +if (promise) { +lastcache = await promise.text(); +} +console.log(lastcache); +fetch("/getupdates").then(async (data) => { +const text = await data.clone().text(); +console.log(text, lastcache); +if (lastcache !== text) { +deleteoldcache(); +putInCache("/getupdates", data.clone()); +} +checkedrecently = true; +setTimeout((_: any) => { +checkedrecently = false; +}, 1000 * 60 * 30); +}); } var checkedrecently = false; function samedomain(url: string | URL) { - return new URL(url).origin === self.origin; +return new URL(url).origin === self.origin; } function isindexhtml(url: string | URL) { - console.log(url); - if (new URL(url).pathname.startsWith("/channels")) { - return true; - } - return false; +console.log(url); +if (new URL(url).pathname.startsWith("/channels")) { +return true; +} +return false; } async function getfile(event: { - request: { url: URL | RequestInfo; clone: () => string | URL | Request }; +request: { url: URL | RequestInfo; clone: () => string | URL | Request }; }) { - checkCache(); - if (!samedomain(event.request.url.toString())) { - return await fetch(event.request.clone()); - } - const responseFromCache = await caches.match(event.request.url); - console.log(responseFromCache, caches); - if (responseFromCache) { - console.log("cache hit"); - return responseFromCache; - } - if (isindexhtml(event.request.url.toString())) { - console.log("is index.html"); - const responseFromCache = await caches.match("/index.html"); - if (responseFromCache) { - console.log("cache hit"); - return responseFromCache; - } - const responseFromNetwork = await fetch("/index.html"); - await putInCache("/index.html", responseFromNetwork.clone()); - return responseFromNetwork; - } - const responseFromNetwork = await fetch(event.request.clone()); - console.log(event.request.clone()); - await putInCache(event.request.clone(), responseFromNetwork.clone()); - try { - return responseFromNetwork; - } catch (e) { - console.error(e); - return e; - } +checkCache(); +if (!samedomain(event.request.url.toString())) { +return await fetch(event.request.clone()); +} +const responseFromCache = await caches.match(event.request.url); +console.log(responseFromCache, caches); +if (responseFromCache) { +console.log("cache hit"); +return responseFromCache; +} +if (isindexhtml(event.request.url.toString())) { +console.log("is index.html"); +const responseFromCache = await caches.match("/index.html"); +if (responseFromCache) { +console.log("cache hit"); +return responseFromCache; +} +const responseFromNetwork = await fetch("/index.html"); +await putInCache("/index.html", responseFromNetwork.clone()); +return responseFromNetwork; +} +const responseFromNetwork = await fetch(event.request.clone()); +console.log(event.request.clone()); +await putInCache(event.request.clone(), responseFromNetwork.clone()); +try { +return responseFromNetwork; +} catch (e) { +console.error(e); +return e; +} } self.addEventListener("fetch", (event: any) => { - try { - event.respondWith(getfile(event)); - } catch (e) { - console.error(e); - } +try { +event.respondWith(getfile(event)); +} catch (e) { +console.error(e); +} }); diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 83e66d2..6aa3425 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -1,1113 +1,1113 @@ interface OptionsElement { - // - generateHTML(): HTMLElement; - submit: () => void; - readonly watchForChange: (func: (arg1: x) => void) => void; - value: x; -} -//future me stuff -class Buttons implements OptionsElement { - readonly name: string; - readonly buttons: [string, Options | string][]; - buttonList!: HTMLDivElement; - warndiv!: HTMLElement; - value: unknown; - constructor(name: string) { - this.buttons = []; - this.name = name; - } - add(name: string, thing?: Options | undefined) { - if (!thing) { - thing = new Options(name, this); - } - this.buttons.push([name, thing]); - return thing; - } - generateHTML() { - const buttonList = document.createElement("div"); - buttonList.classList.add("Buttons"); - buttonList.classList.add("flexltr"); - this.buttonList = buttonList; - const htmlarea = document.createElement("div"); - htmlarea.classList.add("flexgrow"); - const buttonTable = document.createElement("div"); - buttonTable.classList.add("flexttb", "settingbuttons"); - for (const thing of this.buttons) { - const button = document.createElement("button"); - button.classList.add("SettingsButton"); - button.textContent = thing[0]; - button.onclick = (_) => { - this.generateHTMLArea(thing[1], htmlarea); - if (this.warndiv) { - this.warndiv.remove(); - } - }; - buttonTable.append(button); - } - this.generateHTMLArea(this.buttons[0][1], htmlarea); - buttonList.append(buttonTable); - buttonList.append(htmlarea); - return buttonList; - } - handleString(str: string): HTMLElement { - const div = document.createElement("span"); - div.textContent = str; - return div; - } - private generateHTMLArea( - buttonInfo: Options | string, - htmlarea: HTMLElement - ) { - let html: HTMLElement; - if (buttonInfo instanceof Options) { - buttonInfo.subOptions = undefined; - html = buttonInfo.generateHTML(); - } else { - html = this.handleString(buttonInfo); - } - htmlarea.innerHTML = ""; - htmlarea.append(html); - } - changed(html: HTMLElement) { - this.warndiv = html; - this.buttonList.append(html); - } - watchForChange() {} - save() {} - submit() {} -} + // + generateHTML(): HTMLElement; + submit: () => void; + readonly watchForChange: (func: (arg1: x) => void) => void; + value: x; + } + //future me stuff + class Buttons implements OptionsElement { + readonly name: string; + readonly buttons: [string, Options | string][]; + buttonList!: HTMLDivElement; + warndiv!: HTMLElement; + value: unknown; + constructor(name: string) { + this.buttons = []; + this.name = name; + } + add(name: string, thing?: Options | undefined) { + if (!thing) { + thing = new Options(name, this); + } + this.buttons.push([name, thing]); + return thing; + } + generateHTML() { + const buttonList = document.createElement("div"); + buttonList.classList.add("Buttons"); + buttonList.classList.add("flexltr"); + this.buttonList = buttonList; + const htmlarea = document.createElement("div"); + htmlarea.classList.add("flexgrow"); + const buttonTable = document.createElement("div"); + buttonTable.classList.add("flexttb", "settingbuttons"); + for (const thing of this.buttons) { + const button = document.createElement("button"); + button.classList.add("SettingsButton"); + button.textContent = thing[0]; + button.onclick = (_) => { + this.generateHTMLArea(thing[1], htmlarea); + if (this.warndiv) { + this.warndiv.remove(); + } + }; + buttonTable.append(button); + } + this.generateHTMLArea(this.buttons[0][1], htmlarea); + buttonList.append(buttonTable); + buttonList.append(htmlarea); + return buttonList; + } + handleString(str: string): HTMLElement { + const div = document.createElement("span"); + div.textContent = str; + return div; + } + private generateHTMLArea( + buttonInfo: Options | string, + htmlarea: HTMLElement + ) { + let html: HTMLElement; + if (buttonInfo instanceof Options) { + buttonInfo.subOptions = undefined; + html = buttonInfo.generateHTML(); + } else { + html = this.handleString(buttonInfo); + } + htmlarea.innerHTML = ""; + htmlarea.append(html); + } + changed(html: HTMLElement) { + this.warndiv = html; + this.buttonList.append(html); + } + watchForChange() {} + save() {} + submit() {} + } -class TextInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: string) => void; - value: string; - input!: WeakRef; - password: boolean; - constructor( - label: string, - onSubmit: (str: string) => void, - owner: Options, - { initText = "", password = false } = {} - ) { - this.label = label; - this.value = initText; - this.owner = owner; - this.onSubmit = onSubmit; - this.password = password; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.value = this.value; - input.type = this.password ? "password" : "text"; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - private onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.value as string; - this.onchange(value); - this.value = value; - } - } - onchange: (str: string) => void = (_) => {}; - watchForChange(func: (str: string) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.value); - } -} + class TextInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: string) => void; + value: string; + input!: WeakRef; + password: boolean; + constructor( + label: string, + onSubmit: (str: string) => void, + owner: Options, + { initText = "", password = false } = {} + ) { + this.label = label; + this.value = initText; + this.owner = owner; + this.onSubmit = onSubmit; + this.password = password; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.value = this.value; + input.type = this.password ? "password" : "text"; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + private onChange() { + this.owner.changed(); + const input = this.input.deref(); + if (input) { + const value = input.value as string; + this.onchange(value); + this.value = value; + } + } + onchange: (str: string) => void = (_) => {}; + watchForChange(func: (str: string) => void) { + this.onchange = func; + } + submit() { + this.onSubmit(this.value); + } + } -class SettingsText implements OptionsElement { - readonly onSubmit!: (str: string) => void; - value!: void; - readonly text: string; - constructor(text: string) { - this.text = text; - } - generateHTML(): HTMLSpanElement { - const span = document.createElement("span"); - span.innerText = this.text; - return span; - } - watchForChange() {} - submit() {} -} -class SettingsTitle implements OptionsElement { - readonly onSubmit!: (str: string) => void; - value!: void; - readonly text: string; - constructor(text: string) { - this.text = text; - } - generateHTML(): HTMLSpanElement { - const span = document.createElement("h2"); - span.innerText = this.text; - return span; - } - watchForChange() {} - submit() {} -} -class CheckboxInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: boolean) => void; - value: boolean; - input!: WeakRef; - constructor( - label: string, - onSubmit: (str: boolean) => void, - owner: Options, - { initState = false } = {} - ) { - this.label = label; - this.value = initState; - this.owner = owner; - this.onSubmit = onSubmit; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.type = "checkbox"; - input.checked = this.value; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - private onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.checked as boolean; - this.onchange(value); - this.value = value; - } - } - onchange: (str: boolean) => void = (_) => {}; - watchForChange(func: (str: boolean) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.value); - } -} + class SettingsText implements OptionsElement { + readonly onSubmit!: (str: string) => void; + value!: void; + readonly text: string; + constructor(text: string) { + this.text = text; + } + generateHTML(): HTMLSpanElement { + const span = document.createElement("span"); + span.innerText = this.text; + return span; + } + watchForChange() {} + submit() {} + } + class SettingsTitle implements OptionsElement { + readonly onSubmit!: (str: string) => void; + value!: void; + readonly text: string; + constructor(text: string) { + this.text = text; + } + generateHTML(): HTMLSpanElement { + const span = document.createElement("h2"); + span.innerText = this.text; + return span; + } + watchForChange() {} + submit() {} + } + class CheckboxInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: boolean) => void; + value: boolean; + input!: WeakRef; + constructor( + label: string, + onSubmit: (str: boolean) => void, + owner: Options, + { initState = false } = {} + ) { + this.label = label; + this.value = initState; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.type = "checkbox"; + input.checked = this.value; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + private onChange() { + this.owner.changed(); + const input = this.input.deref(); + if (input) { + const value = input.checked as boolean; + this.onchange(value); + this.value = value; + } + } + onchange: (str: boolean) => void = (_) => {}; + watchForChange(func: (str: boolean) => void) { + this.onchange = func; + } + submit() { + this.onSubmit(this.value); + } + } -class ButtonInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onClick: () => void; - textContent: string; - value!: void; - constructor( - label: string, - textContent: string, - onClick: () => void, - owner: Options, - {} = {} - ) { - this.label = label; - this.owner = owner; - this.onClick = onClick; - this.textContent = textContent; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const button = document.createElement("button"); - button.textContent = this.textContent; - button.onclick = this.onClickEvent.bind(this); - div.append(button); - return div; - } - private onClickEvent() { - this.onClick(); - } - watchForChange() {} - submit() {} -} + class ButtonInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onClick: () => void; + textContent: string; + value!: void; + constructor( + label: string, + textContent: string, + onClick: () => void, + owner: Options, + {} = {} + ) { + this.label = label; + this.owner = owner; + this.onClick = onClick; + this.textContent = textContent; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const button = document.createElement("button"); + button.textContent = this.textContent; + button.onclick = this.onClickEvent.bind(this); + div.append(button); + return div; + } + private onClickEvent() { + this.onClick(); + } + watchForChange() {} + submit() {} + } -class ColorInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: string) => void; - colorContent: string; - input!: WeakRef; - value!: string; - constructor( - label: string, - onSubmit: (str: string) => void, - owner: Options, - { initColor = "" } = {} - ) { - this.label = label; - this.colorContent = initColor; - this.owner = owner; - this.onSubmit = onSubmit; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.value = this.colorContent; - input.type = "color"; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - private onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.value as string; - this.value = value; - this.onchange(value); - this.colorContent = value; - } - } - onchange: (str: string) => void = (_) => {}; - watchForChange(func: (str: string) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.colorContent); - } -} + class ColorInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: string) => void; + colorContent: string; + input!: WeakRef; + value!: string; + constructor( + label: string, + onSubmit: (str: string) => void, + owner: Options, + { initColor = "" } = {} + ) { + this.label = label; + this.colorContent = initColor; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.value = this.colorContent; + input.type = "color"; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + private onChange() { + this.owner.changed(); + const input = this.input.deref(); + if (input) { + const value = input.value as string; + this.value = value; + this.onchange(value); + this.colorContent = value; + } + } + onchange: (str: string) => void = (_) => {}; + watchForChange(func: (str: string) => void) { + this.onchange = func; + } + submit() { + this.onSubmit(this.colorContent); + } + } -class SelectInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: number) => void; - options: string[]; - index: number; - select!: WeakRef; - get value() { - return this.index; - } - constructor( - label: string, - onSubmit: (str: number) => void, - options: string[], - owner: Options, - { defaultIndex = 0 } = {} - ) { - this.label = label; - this.index = defaultIndex; - this.owner = owner; - this.onSubmit = onSubmit; - this.options = options; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const select = document.createElement("select"); + class SelectInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: number) => void; + options: string[]; + index: number; + select!: WeakRef; + get value() { + return this.index; + } + constructor( + label: string, + onSubmit: (str: number) => void, + options: string[], + owner: Options, + { defaultIndex = 0 } = {} + ) { + this.label = label; + this.index = defaultIndex; + this.owner = owner; + this.onSubmit = onSubmit; + this.options = options; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const select = document.createElement("select"); - select.onchange = this.onChange.bind(this); - for (const thing of this.options) { - const option = document.createElement("option"); - option.textContent = thing; - select.appendChild(option); - } - this.select = new WeakRef(select); - select.selectedIndex = this.index; - div.append(select); - return div; - } - private onChange() { - this.owner.changed(); - const select = this.select.deref(); - if (select) { - const value = select.selectedIndex; - this.onchange(value); - this.index = value; - } - } - onchange: (str: number) => void = (_) => {}; - watchForChange(func: (str: number) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.index); - } -} -class MDInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: string) => void; - value: string; - input!: WeakRef; - constructor( - label: string, - onSubmit: (str: string) => void, - owner: Options, - { initText = "" } = {} - ) { - this.label = label; - this.value = initText; - this.owner = owner; - this.onSubmit = onSubmit; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - div.append(document.createElement("br")); - const input = document.createElement("textarea"); - input.value = this.value; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - return div; - } - onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - const value = input.value as string; - this.onchange(value); - this.value = value; - } - } - onchange: (str: string) => void = (_) => {}; - watchForChange(func: (str: string) => void) { - this.onchange = func; - } - submit() { - this.onSubmit(this.value); - } -} -class FileInput implements OptionsElement { - readonly label: string; - readonly owner: Options; - readonly onSubmit: (str: FileList | null) => void; - input!: WeakRef; - value!: FileList | null; - clear: boolean; - constructor( - label: string, - onSubmit: (str: FileList | null) => void, - owner: Options, - { clear = false } = {} - ) { - this.label = label; - this.owner = owner; - this.onSubmit = onSubmit; - this.clear = clear; - } - generateHTML(): HTMLDivElement { - const div = document.createElement("div"); - const span = document.createElement("span"); - span.textContent = this.label; - div.append(span); - const input = document.createElement("input"); - input.type = "file"; - input.oninput = this.onChange.bind(this); - this.input = new WeakRef(input); - div.append(input); - if (this.clear) { - const button = document.createElement("button"); - button.textContent = "Clear"; - button.onclick = (_) => { - if (this.onchange) { - this.onchange(null); - } - this.value = null; - this.owner.changed(); - }; - div.append(button); - } - return div; - } - onChange() { - this.owner.changed(); - const input = this.input.deref(); - if (input) { - this.value = input.files; - if (this.onchange) { - this.onchange(input.files); - } - } - } - onchange: ((str: FileList | null) => void) | null = null; - watchForChange(func: (str: FileList | null) => void) { - this.onchange = func; - } - submit() { - const input = this.input.deref(); - if (input) { - this.onSubmit(input.files); - } - } -} + select.onchange = this.onChange.bind(this); + for (const thing of this.options) { + const option = document.createElement("option"); + option.textContent = thing; + select.appendChild(option); + } + this.select = new WeakRef(select); + select.selectedIndex = this.index; + div.append(select); + return div; + } + private onChange() { + this.owner.changed(); + const select = this.select.deref(); + if (select) { + const value = select.selectedIndex; + this.onchange(value); + this.index = value; + } + } + onchange: (str: number) => void = (_) => {}; + watchForChange(func: (str: number) => void) { + this.onchange = func; + } + submit() { + this.onSubmit(this.index); + } + } + class MDInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: string) => void; + value: string; + input!: WeakRef; + constructor( + label: string, + onSubmit: (str: string) => void, + owner: Options, + { initText = "" } = {} + ) { + this.label = label; + this.value = initText; + this.owner = owner; + this.onSubmit = onSubmit; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + div.append(document.createElement("br")); + const input = document.createElement("textarea"); + input.value = this.value; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + return div; + } + onChange() { + this.owner.changed(); + const input = this.input.deref(); + if (input) { + const value = input.value as string; + this.onchange(value); + this.value = value; + } + } + onchange: (str: string) => void = (_) => {}; + watchForChange(func: (str: string) => void) { + this.onchange = func; + } + submit() { + this.onSubmit(this.value); + } + } + class FileInput implements OptionsElement { + readonly label: string; + readonly owner: Options; + readonly onSubmit: (str: FileList | null) => void; + input!: WeakRef; + value!: FileList | null; + clear: boolean; + constructor( + label: string, + onSubmit: (str: FileList | null) => void, + owner: Options, + { clear = false } = {} + ) { + this.label = label; + this.owner = owner; + this.onSubmit = onSubmit; + this.clear = clear; + } + generateHTML(): HTMLDivElement { + const div = document.createElement("div"); + const span = document.createElement("span"); + span.textContent = this.label; + div.append(span); + const input = document.createElement("input"); + input.type = "file"; + input.oninput = this.onChange.bind(this); + this.input = new WeakRef(input); + div.append(input); + if (this.clear) { + const button = document.createElement("button"); + button.textContent = "Clear"; + button.onclick = (_) => { + if (this.onchange) { + this.onchange(null); + } + this.value = null; + this.owner.changed(); + }; + div.append(button); + } + return div; + } + onChange() { + this.owner.changed(); + const input = this.input.deref(); + if (input) { + this.value = input.files; + if (this.onchange) { + this.onchange(input.files); + } + } + } + onchange: ((str: FileList | null) => void) | null = null; + watchForChange(func: (str: FileList | null) => void) { + this.onchange = func; + } + submit() { + const input = this.input.deref(); + if (input) { + this.onSubmit(input.files); + } + } + } -class HtmlArea implements OptionsElement { - submit: () => void; - html: (() => HTMLElement) | HTMLElement; - value!: void; - constructor(html: (() => HTMLElement) | HTMLElement, submit: () => void) { - this.submit = submit; - this.html = html; - } - generateHTML(): HTMLElement { - if (this.html instanceof Function) { - return this.html(); - } else { - return this.html; - } - } - watchForChange() {} -} -class Options implements OptionsElement { - name: string; - haschanged = false; - readonly options: OptionsElement[]; - readonly owner: Buttons | Options | Form; - readonly ltr: boolean; - value!: void; - readonly html: WeakMap, WeakRef> = - new WeakMap(); - container: WeakRef = new WeakRef( - document.createElement("div") - ); - constructor( - name: string, - owner: Buttons | Options | Form, - { ltr = false } = {} - ) { - this.name = name; - this.options = []; - this.owner = owner; - this.ltr = ltr; - } - removeAll() { - while (this.options.length) { - this.options.pop(); - } - const container = this.container.deref(); - if (container) { - container.innerHTML = ""; - } - } - watchForChange() {} - addOptions(name: string, { ltr = false } = {}) { - const options = new Options(name, this, { ltr }); - this.options.push(options); - this.generate(options); - return options; - } - subOptions: Options | Form | undefined; - addSubOptions(name: string, { ltr = false } = {}) { - const options = new Options(name, this, { ltr }); - this.subOptions = options; - const container = this.container.deref(); - if (container) { - this.generateContainter(); - } else { - throw new Error( - "Tried to make a subOptions when the options weren't rendered" - ); - } - return options; - } - addSubForm( - name: string, - onSubmit: (arg1: object) => void, - { - ltr = false, - submitText = "Submit", - fetchURL = "", - headers = {}, - method = "POST", - traditionalSubmit = false, - } = {} - ) { - const options = new Form(name, this, onSubmit, { - ltr, - submitText, - fetchURL, - headers, - method, - traditionalSubmit, - }); - this.subOptions = options; - const container = this.container.deref(); - if (container) { - this.generateContainter(); - } else { - throw new Error( - "Tried to make a subForm when the options weren't rendered" - ); - } - return options; - } - returnFromSub() { - this.subOptions = undefined; - this.generateContainter(); - } - addSelect( - label: string, - onSubmit: (str: number) => void, - selections: string[], - { defaultIndex = 0 } = {} - ) { - const select = new SelectInput(label, onSubmit, selections, this, { - defaultIndex, - }); - this.options.push(select); - this.generate(select); - return select; - } - addFileInput( - label: string, - onSubmit: (files: FileList | null) => void, - { clear = false } = {} - ) { - const FI = new FileInput(label, onSubmit, this, { clear }); - this.options.push(FI); - this.generate(FI); - return FI; - } - addTextInput( - label: string, - onSubmit: (str: string) => void, - { initText = "", password = false } = {} - ) { - const textInput = new TextInput(label, onSubmit, this, { - initText, - password, - }); - this.options.push(textInput); - this.generate(textInput); - return textInput; - } - addColorInput( - label: string, - onSubmit: (str: string) => void, - { initColor = "" } = {} - ) { - const colorInput = new ColorInput(label, onSubmit, this, { initColor }); - this.options.push(colorInput); - this.generate(colorInput); - return colorInput; - } - addMDInput( - label: string, - onSubmit: (str: string) => void, - { initText = "" } = {} - ) { - const mdInput = new MDInput(label, onSubmit, this, { initText }); - this.options.push(mdInput); - this.generate(mdInput); - return mdInput; - } - addHTMLArea( - html: (() => HTMLElement) | HTMLElement, - submit: () => void = () => {} - ) { - const htmlarea = new HtmlArea(html, submit); - this.options.push(htmlarea); - this.generate(htmlarea); - return htmlarea; - } - addButtonInput(label: string, textContent: string, onSubmit: () => void) { - const button = new ButtonInput(label, textContent, onSubmit, this); - this.options.push(button); - this.generate(button); - return button; - } - addCheckboxInput( - label: string, - onSubmit: (str: boolean) => void, - { initState = false } = {} - ) { - const box = new CheckboxInput(label, onSubmit, this, { initState }); - this.options.push(box); - this.generate(box); - return box; - } - addText(str: string) { - const text = new SettingsText(str); - this.options.push(text); - this.generate(text); - return text; - } - addTitle(str: string) { - const text = new SettingsTitle(str); - this.options.push(text); - this.generate(text); - return text; - } - addForm( - name: string, - onSubmit: (arg1: object) => void, - { - ltr = false, - submitText = "Submit", - fetchURL = "", - headers = {}, - method = "POST", - traditionalSubmit = false, - } = {} - ) { - const options = new Form(name, this, onSubmit, { - ltr, - submitText, - fetchURL, - headers, - method, - traditionalSubmit, - }); - this.options.push(options); - this.generate(options); - return options; - } - generate(elm: OptionsElement) { - const container = this.container.deref(); - if (container) { - const div = document.createElement("div"); - if (!(elm instanceof Options)) { - div.classList.add("optionElement"); - } - const html = elm.generateHTML(); - div.append(html); - this.html.set(elm, new WeakRef(div)); - container.append(div); - } - } - title: WeakRef = new WeakRef(document.createElement("h2")); - generateHTML(): HTMLElement { - const div = document.createElement("div"); - div.classList.add("titlediv"); - const title = document.createElement("h2"); - title.textContent = this.name; - div.append(title); - if (this.name !== "") title.classList.add("settingstitle"); - this.title = new WeakRef(title); - const container = document.createElement("div"); - this.container = new WeakRef(container); - container.classList.add(this.ltr ? "flexltr" : "flexttb", "flexspace"); - this.generateContainter(); - div.append(container); - return div; - } - generateContainter() { - const container = this.container.deref(); - if (container) { - const title = this.title.deref(); - if (title) title.innerHTML = ""; - container.innerHTML = ""; - if (this.subOptions) { - container.append(this.subOptions.generateHTML()); //more code needed, though this is enough for now - if (title) { - const name = document.createElement("span"); - name.innerText = this.name; - name.classList.add("clickable"); - name.onclick = () => { - this.returnFromSub(); - }; - title.append(name, " > ", this.subOptions.name); - } - } else { - for (const thing of this.options) { - this.generate(thing); - } - if (title) { - title.innerText = this.name; - } - } - if (title && title.innerText !== "") { - title.classList.add("settingstitle"); - } else if (title) { - title.classList.remove("settingstitle"); - } - } else { - console.warn("tried to generate container, but it did not exist"); - } - } - changed() { - if (this.owner instanceof Options || this.owner instanceof Form) { - this.owner.changed(); - return; - } - if (!this.haschanged) { - const div = document.createElement("div"); - div.classList.add("flexltr", "savediv"); - const span = document.createElement("span"); - div.append(span); - span.textContent = "Careful, you have unsaved changes"; - const button = document.createElement("button"); - button.textContent = "Save changes"; - div.append(button); - this.haschanged = true; - this.owner.changed(div); + class HtmlArea implements OptionsElement { + submit: () => void; + html: (() => HTMLElement) | HTMLElement; + value!: void; + constructor(html: (() => HTMLElement) | HTMLElement, submit: () => void) { + this.submit = submit; + this.html = html; + } + generateHTML(): HTMLElement { + if (this.html instanceof Function) { + return this.html(); + } else { + return this.html; + } + } + watchForChange() {} + } + class Options implements OptionsElement { + name: string; + haschanged = false; + readonly options: OptionsElement[]; + readonly owner: Buttons | Options | Form; + readonly ltr: boolean; + value!: void; + readonly html: WeakMap, WeakRef> = + new WeakMap(); + container: WeakRef = new WeakRef( + document.createElement("div") + ); + constructor( + name: string, + owner: Buttons | Options | Form, + { ltr = false } = {} + ) { + this.name = name; + this.options = []; + this.owner = owner; + this.ltr = ltr; + } + removeAll() { + while (this.options.length) { + this.options.pop(); + } + const container = this.container.deref(); + if (container) { + container.innerHTML = ""; + } + } + watchForChange() {} + addOptions(name: string, { ltr = false } = {}) { + const options = new Options(name, this, { ltr }); + this.options.push(options); + this.generate(options); + return options; + } + subOptions: Options | Form | undefined; + addSubOptions(name: string, { ltr = false } = {}) { + const options = new Options(name, this, { ltr }); + this.subOptions = options; + const container = this.container.deref(); + if (container) { + this.generateContainter(); + } else { + throw new Error( + "Tried to make a subOptions when the options weren't rendered" + ); + } + return options; + } + addSubForm( + name: string, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ) { + const options = new Form(name, this, onSubmit, { + ltr, + submitText, + fetchURL, + headers, + method, + traditionalSubmit, + }); + this.subOptions = options; + const container = this.container.deref(); + if (container) { + this.generateContainter(); + } else { + throw new Error( + "Tried to make a subForm when the options weren't rendered" + ); + } + return options; + } + returnFromSub() { + this.subOptions = undefined; + this.generateContainter(); + } + addSelect( + label: string, + onSubmit: (str: number) => void, + selections: string[], + { defaultIndex = 0 } = {} + ) { + const select = new SelectInput(label, onSubmit, selections, this, { + defaultIndex, + }); + this.options.push(select); + this.generate(select); + return select; + } + addFileInput( + label: string, + onSubmit: (files: FileList | null) => void, + { clear = false } = {} + ) { + const FI = new FileInput(label, onSubmit, this, { clear }); + this.options.push(FI); + this.generate(FI); + return FI; + } + addTextInput( + label: string, + onSubmit: (str: string) => void, + { initText = "", password = false } = {} + ) { + const textInput = new TextInput(label, onSubmit, this, { + initText, + password, + }); + this.options.push(textInput); + this.generate(textInput); + return textInput; + } + addColorInput( + label: string, + onSubmit: (str: string) => void, + { initColor = "" } = {} + ) { + const colorInput = new ColorInput(label, onSubmit, this, { initColor }); + this.options.push(colorInput); + this.generate(colorInput); + return colorInput; + } + addMDInput( + label: string, + onSubmit: (str: string) => void, + { initText = "" } = {} + ) { + const mdInput = new MDInput(label, onSubmit, this, { initText }); + this.options.push(mdInput); + this.generate(mdInput); + return mdInput; + } + addHTMLArea( + html: (() => HTMLElement) | HTMLElement, + submit: () => void = () => {} + ) { + const htmlarea = new HtmlArea(html, submit); + this.options.push(htmlarea); + this.generate(htmlarea); + return htmlarea; + } + addButtonInput(label: string, textContent: string, onSubmit: () => void) { + const button = new ButtonInput(label, textContent, onSubmit, this); + this.options.push(button); + this.generate(button); + return button; + } + addCheckboxInput( + label: string, + onSubmit: (str: boolean) => void, + { initState = false } = {} + ) { + const box = new CheckboxInput(label, onSubmit, this, { initState }); + this.options.push(box); + this.generate(box); + return box; + } + addText(str: string) { + const text = new SettingsText(str); + this.options.push(text); + this.generate(text); + return text; + } + addTitle(str: string) { + const text = new SettingsTitle(str); + this.options.push(text); + this.generate(text); + return text; + } + addForm( + name: string, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ) { + const options = new Form(name, this, onSubmit, { + ltr, + submitText, + fetchURL, + headers, + method, + traditionalSubmit, + }); + this.options.push(options); + this.generate(options); + return options; + } + generate(elm: OptionsElement) { + const container = this.container.deref(); + if (container) { + const div = document.createElement("div"); + if (!(elm instanceof Options)) { + div.classList.add("optionElement"); + } + const html = elm.generateHTML(); + div.append(html); + this.html.set(elm, new WeakRef(div)); + container.append(div); + } + } + title: WeakRef = new WeakRef(document.createElement("h2")); + generateHTML(): HTMLElement { + const div = document.createElement("div"); + div.classList.add("titlediv"); + const title = document.createElement("h2"); + title.textContent = this.name; + div.append(title); + if (this.name !== "") title.classList.add("settingstitle"); + this.title = new WeakRef(title); + const container = document.createElement("div"); + this.container = new WeakRef(container); + container.classList.add(this.ltr ? "flexltr" : "flexttb", "flexspace"); + this.generateContainter(); + div.append(container); + return div; + } + generateContainter() { + const container = this.container.deref(); + if (container) { + const title = this.title.deref(); + if (title) title.innerHTML = ""; + container.innerHTML = ""; + if (this.subOptions) { + container.append(this.subOptions.generateHTML()); //more code needed, though this is enough for now + if (title) { + const name = document.createElement("span"); + name.innerText = this.name; + name.classList.add("clickable"); + name.onclick = () => { + this.returnFromSub(); + }; + title.append(name, " > ", this.subOptions.name); + } + } else { + for (const thing of this.options) { + this.generate(thing); + } + if (title) { + title.innerText = this.name; + } + } + if (title && title.innerText !== "") { + title.classList.add("settingstitle"); + } else if (title) { + title.classList.remove("settingstitle"); + } + } else { + console.warn("tried to generate container, but it did not exist"); + } + } + changed() { + if (this.owner instanceof Options || this.owner instanceof Form) { + this.owner.changed(); + return; + } + if (!this.haschanged) { + const div = document.createElement("div"); + div.classList.add("flexltr", "savediv"); + const span = document.createElement("span"); + div.append(span); + span.textContent = "Careful, you have unsaved changes"; + const button = document.createElement("button"); + button.textContent = "Save changes"; + div.append(button); + this.haschanged = true; + this.owner.changed(div); - button.onclick = (_) => { - if (this.owner instanceof Buttons) { - this.owner.save(); - } - div.remove(); - this.submit(); - }; - } - } - submit() { - this.haschanged = false; - for (const thing of this.options) { - thing.submit(); - } - } -} -class FormError extends Error { - elem: OptionsElement; - message: string; - constructor(elem: OptionsElement, message: string) { - super(message); - this.message = message; - this.elem = elem; - } -} -export { FormError }; -class Form implements OptionsElement { - name: string; - readonly options: Options; - readonly owner: Options; - readonly ltr: boolean; - readonly names: Map> = new Map(); - readonly required: WeakSet> = new WeakSet(); - readonly submitText: string; - readonly fetchURL: string; - readonly headers = {}; - readonly method: string; - value!: object; - traditionalSubmit: boolean; - values: { [key: string]: any } = {}; - constructor( - name: string, - owner: Options, - onSubmit: (arg1: object) => void, - { - ltr = false, - submitText = "Submit", - fetchURL = "", - headers = {}, - method = "POST", - traditionalSubmit = false, - } = {} - ) { - this.traditionalSubmit = traditionalSubmit; - this.name = name; - this.method = method; - this.submitText = submitText; - this.options = new Options("", this, { ltr }); - this.owner = owner; - this.fetchURL = fetchURL; - this.headers = headers; - this.ltr = ltr; - this.onSubmit = onSubmit; - } - setValue(key: string, value: any) { - //the value can't really be anything, but I don't care enough to fix this - this.values[key] = value; - } - addSelect( - label: string, - formName: string, - selections: string[], - { defaultIndex = 0, required = false } = {} - ) { - const select = this.options.addSelect(label, (_) => {}, selections, { - defaultIndex, - }); - this.names.set(formName, select); - if (required) { - this.required.add(select); - } - return select; - } - readonly fileOptions: Map = new Map(); - addFileInput( - label: string, - formName: string, - { required = false, files = "one", clear = false } = {} - ) { - const FI = this.options.addFileInput(label, (_) => {}, { clear }); - if (files !== "one" && files !== "multi") - throw new Error("files should equal one or multi"); - this.fileOptions.set(FI, { files }); - this.names.set(formName, FI); - if (required) { - this.required.add(FI); - } - return FI; - } + button.onclick = (_) => { + if (this.owner instanceof Buttons) { + this.owner.save(); + } + div.remove(); + this.submit(); + }; + } + } + submit() { + this.haschanged = false; + for (const thing of this.options) { + thing.submit(); + } + } + } + class FormError extends Error { + elem: OptionsElement; + message: string; + constructor(elem: OptionsElement, message: string) { + super(message); + this.message = message; + this.elem = elem; + } + } + export { FormError }; + class Form implements OptionsElement { + name: string; + readonly options: Options; + readonly owner: Options; + readonly ltr: boolean; + readonly names: Map> = new Map(); + readonly required: WeakSet> = new WeakSet(); + readonly submitText: string; + readonly fetchURL: string; + readonly headers = {}; + readonly method: string; + value!: object; + traditionalSubmit: boolean; + values: { [key: string]: any } = {}; + constructor( + name: string, + owner: Options, + onSubmit: (arg1: object) => void, + { + ltr = false, + submitText = "Submit", + fetchURL = "", + headers = {}, + method = "POST", + traditionalSubmit = false, + } = {} + ) { + this.traditionalSubmit = traditionalSubmit; + this.name = name; + this.method = method; + this.submitText = submitText; + this.options = new Options("", this, { ltr }); + this.owner = owner; + this.fetchURL = fetchURL; + this.headers = headers; + this.ltr = ltr; + this.onSubmit = onSubmit; + } + setValue(key: string, value: any) { + //the value can't really be anything, but I don't care enough to fix this + this.values[key] = value; + } + addSelect( + label: string, + formName: string, + selections: string[], + { defaultIndex = 0, required = false } = {} + ) { + const select = this.options.addSelect(label, (_) => {}, selections, { + defaultIndex, + }); + this.names.set(formName, select); + if (required) { + this.required.add(select); + } + return select; + } + readonly fileOptions: Map = new Map(); + addFileInput( + label: string, + formName: string, + { required = false, files = "one", clear = false } = {} + ) { + const FI = this.options.addFileInput(label, (_) => {}, { clear }); + if (files !== "one" && files !== "multi") + throw new Error("files should equal one or multi"); + this.fileOptions.set(FI, { files }); + this.names.set(formName, FI); + if (required) { + this.required.add(FI); + } + return FI; + } - addTextInput( - label: string, - formName: string, - { initText = "", required = false, password = false } = {} - ) { - const textInput = this.options.addTextInput(label, (_) => {}, { - initText, - password, - }); - this.names.set(formName, textInput); - if (required) { - this.required.add(textInput); - } - return textInput; - } - addColorInput( - label: string, - formName: string, - { initColor = "", required = false } = {} - ) { - const colorInput = this.options.addColorInput(label, (_) => {}, { - initColor, - }); - this.names.set(formName, colorInput); - if (required) { - this.required.add(colorInput); - } - return colorInput; - } + addTextInput( + label: string, + formName: string, + { initText = "", required = false, password = false } = {} + ) { + const textInput = this.options.addTextInput(label, (_) => {}, { + initText, + password, + }); + this.names.set(formName, textInput); + if (required) { + this.required.add(textInput); + } + return textInput; + } + addColorInput( + label: string, + formName: string, + { initColor = "", required = false } = {} + ) { + const colorInput = this.options.addColorInput(label, (_) => {}, { + initColor, + }); + this.names.set(formName, colorInput); + if (required) { + this.required.add(colorInput); + } + return colorInput; + } - addMDInput( - label: string, - formName: string, - { initText = "", required = false } = {} - ) { - const mdInput = this.options.addMDInput(label, (_) => {}, { initText }); - this.names.set(formName, mdInput); - if (required) { - this.required.add(mdInput); - } - return mdInput; - } + addMDInput( + label: string, + formName: string, + { initText = "", required = false } = {} + ) { + const mdInput = this.options.addMDInput(label, (_) => {}, { initText }); + this.names.set(formName, mdInput); + if (required) { + this.required.add(mdInput); + } + return mdInput; + } - addCheckboxInput( - label: string, - formName: string, - { initState = false, required = false } = {} - ) { - const box = this.options.addCheckboxInput(label, (_) => {}, { initState }); - this.names.set(formName, box); - if (required) { - this.required.add(box); - } - return box; - } - addText(str: string) { - this.options.addText(str); - } - addTitle(str: string) { - this.options.addTitle(str); - } - generateHTML(): HTMLElement { - const div = document.createElement("div"); - div.append(this.options.generateHTML()); - div.classList.add("FormSettings"); - if (!this.traditionalSubmit) { - const button = document.createElement("button"); - button.onclick = (_) => { - this.submit(); - }; - button.textContent = this.submitText; - div.append(button); - } - return div; - } - onSubmit: (arg1: object) => void; - watchForChange(func: (arg1: object) => void) { - this.onSubmit = func; - } - changed() { - if (this.traditionalSubmit) { - this.owner.changed(); - } - } - async submit() { - const build = {}; - for (const key of Object.keys(this.values)) { - const thing = this.values[key]; - if (thing instanceof Function) { - try { - (build as any)[key] = thing(); - } catch (e: any) { - if (e instanceof FormError) { - const elm = this.options.html.get(e.elem); - if (elm) { - const html = elm.deref(); - if (html) { - this.makeError(html, e.message); - } - } - } - return; - } - } else { - (build as any)[thing] = thing; - } - } - const promises: Promise[] = []; - for (const thing of this.names.keys()) { - if (thing === "") continue; - const input = this.names.get(thing) as OptionsElement; - if (input instanceof SelectInput) { - (build as any)[thing] = input.options[input.value]; - continue; - } else if (input instanceof FileInput) { - const options = this.fileOptions.get(input); - if (!options) { - throw new Error( - "FileInput without its options is in this form, this should never happen." - ); - } - if (options.files === "one") { - console.log(input.value); - if (input.value) { - const reader = new FileReader(); - reader.readAsDataURL(input.value[0]); - const promise = new Promise((res) => { - reader.onload = () => { - (build as any)[thing] = reader.result; - res(); - }; - }); - promises.push(promise); - } - } else { - console.error(options.files + " is not currently implemented"); - } - } - (build as any)[thing] = input.value; - } - await Promise.allSettled(promises); - if (this.fetchURL !== "") { - fetch(this.fetchURL, { - method: this.method, - body: JSON.stringify(build), - headers: this.headers, - }) - .then((_) => _.json()) - .then((json) => { - if (json.errors && this.errors(json.errors)) return; - this.onSubmit(json); - }); - } else { - this.onSubmit(build); - } - console.warn("needs to be implemented"); - } - errors(errors: { - code: number; - message: string; - errors: { [key: string]: { _errors: { message: string; code: string } } }; - }) { - if (!(errors instanceof Object)) { - return; - } - for (const error of Object.keys(errors)) { - const elm = this.names.get(error); - if (elm) { - const ref = this.options.html.get(elm); - if (ref && ref.deref()) { - const html = ref.deref() as HTMLDivElement; - this.makeError(html, errors["errors"][error]._errors.message); - return true; - } - } - } - return false; - } - error(formElm: string, errorMessage: string) { - const elm = this.names.get(formElm); - if (elm) { - const htmlref = this.options.html.get(elm); - if (htmlref) { - const html = htmlref.deref(); - if (html) { - this.makeError(html, errorMessage); - } - } - } else { - console.warn(formElm + " is not a valid form property"); - } - } - makeError(e: HTMLDivElement, message: string) { - let element = e.getElementsByClassName("suberror")[0] as HTMLElement; - if (!element) { - const div = document.createElement("div"); - div.classList.add("suberror", "suberrora"); - e.append(div); - element = div; - } else { - element.classList.remove("suberror"); - setTimeout((_) => { - element.classList.add("suberror"); - }, 100); - } - element.textContent = message; - } -} -class Settings extends Buttons { - static readonly Buttons = Buttons; - static readonly Options = Options; - html!: HTMLElement | null; - constructor(name: string) { - super(name); - } - addButton(name: string, { ltr = false } = {}): Options { - const options = new Options(name, this, { ltr }); - this.add(name, options); - return options; - } - show() { - const background = document.createElement("div"); - background.classList.add("background"); + addCheckboxInput( + label: string, + formName: string, + { initState = false, required = false } = {} + ) { + const box = this.options.addCheckboxInput(label, (_) => {}, { initState }); + this.names.set(formName, box); + if (required) { + this.required.add(box); + } + return box; + } + addText(str: string) { + this.options.addText(str); + } + addTitle(str: string) { + this.options.addTitle(str); + } + generateHTML(): HTMLElement { + const div = document.createElement("div"); + div.append(this.options.generateHTML()); + div.classList.add("FormSettings"); + if (!this.traditionalSubmit) { + const button = document.createElement("button"); + button.onclick = (_) => { + this.submit(); + }; + button.textContent = this.submitText; + div.append(button); + } + return div; + } + onSubmit: (arg1: object) => void; + watchForChange(func: (arg1: object) => void) { + this.onSubmit = func; + } + changed() { + if (this.traditionalSubmit) { + this.owner.changed(); + } + } + async submit() { + const build = {}; + for (const key of Object.keys(this.values)) { + const thing = this.values[key]; + if (thing instanceof Function) { + try { + (build as any)[key] = thing(); + } catch (e: any) { + if (e instanceof FormError) { + const elm = this.options.html.get(e.elem); + if (elm) { + const html = elm.deref(); + if (html) { + this.makeError(html, e.message); + } + } + } + return; + } + } else { + (build as any)[thing] = thing; + } + } + const promises: Promise[] = []; + for (const thing of this.names.keys()) { + if (thing === "") continue; + const input = this.names.get(thing) as OptionsElement; + if (input instanceof SelectInput) { + (build as any)[thing] = input.options[input.value]; + continue; + } else if (input instanceof FileInput) { + const options = this.fileOptions.get(input); + if (!options) { + throw new Error( + "FileInput without its options is in this form, this should never happen." + ); + } + if (options.files === "one") { + console.log(input.value); + if (input.value) { + const reader = new FileReader(); + reader.readAsDataURL(input.value[0]); + const promise = new Promise((res) => { + reader.onload = () => { + (build as any)[thing] = reader.result; + res(); + }; + }); + promises.push(promise); + } + } else { + console.error(options.files + " is not currently implemented"); + } + } + (build as any)[thing] = input.value; + } + await Promise.allSettled(promises); + if (this.fetchURL !== "") { + fetch(this.fetchURL, { + method: this.method, + body: JSON.stringify(build), + headers: this.headers, + }) + .then((_) => _.json()) + .then((json) => { + if (json.errors && this.errors(json.errors)) return; + this.onSubmit(json); + }); + } else { + this.onSubmit(build); + } + console.warn("needs to be implemented"); + } + errors(errors: { + code: number; + message: string; + errors: { [key: string]: { _errors: { message: string; code: string } } }; + }) { + if (!(errors instanceof Object)) { + return; + } + for (const error of Object.keys(errors)) { + const elm = this.names.get(error); + if (elm) { + const ref = this.options.html.get(elm); + if (ref && ref.deref()) { + const html = ref.deref() as HTMLDivElement; + this.makeError(html, errors["errors"][error]._errors.message); + return true; + } + } + } + return false; + } + error(formElm: string, errorMessage: string) { + const elm = this.names.get(formElm); + if (elm) { + const htmlref = this.options.html.get(elm); + if (htmlref) { + const html = htmlref.deref(); + if (html) { + this.makeError(html, errorMessage); + } + } + } else { + console.warn(formElm + " is not a valid form property"); + } + } + makeError(e: HTMLDivElement, message: string) { + let element = e.getElementsByClassName("suberror")[0] as HTMLElement; + if (!element) { + const div = document.createElement("div"); + div.classList.add("suberror", "suberrora"); + e.append(div); + element = div; + } else { + element.classList.remove("suberror"); + setTimeout((_) => { + element.classList.add("suberror"); + }, 100); + } + element.textContent = message; + } + } + class Settings extends Buttons { + static readonly Buttons = Buttons; + static readonly Options = Options; + html!: HTMLElement | null; + constructor(name: string) { + super(name); + } + addButton(name: string, { ltr = false } = {}): Options { + const options = new Options(name, this, { ltr }); + this.add(name, options); + return options; + } + show() { + const background = document.createElement("div"); + background.classList.add("background"); - const title = document.createElement("h2"); - title.textContent = this.name; - title.classList.add("settingstitle"); - background.append(title); + const title = document.createElement("h2"); + title.textContent = this.name; + title.classList.add("settingstitle"); + background.append(title); - background.append(this.generateHTML()); + background.append(this.generateHTML()); - const exit = document.createElement("span"); - exit.textContent = "✖"; - exit.classList.add("exitsettings"); - background.append(exit); - exit.onclick = (_) => { - this.hide(); - }; - document.body.append(background); - this.html = background; - } - hide() { - if (this.html) { - this.html.remove(); - this.html = null; - } - } -} + const exit = document.createElement("span"); + exit.textContent = "✖"; + exit.classList.add("exitsettings"); + background.append(exit); + exit.onclick = (_) => { + this.hide(); + }; + document.body.append(background); + this.html = background; + } + hide() { + if (this.html) { + this.html.remove(); + this.html = null; + } + } + } -export { Settings, OptionsElement, Buttons, Options }; + export { Settings, OptionsElement, Buttons, Options }; diff --git a/src/webpage/snowflake.ts b/src/webpage/snowflake.ts index 516ec67..8e23a81 100644 --- a/src/webpage/snowflake.ts +++ b/src/webpage/snowflake.ts @@ -1,20 +1,20 @@ abstract class SnowFlake { - public readonly id: string; - constructor(id: string) { - this.id = id; - } - getUnixTime(): number { - return SnowFlake.stringToUnixTime(this.id); - } - static stringToUnixTime(str: string) { - try { - return Number((BigInt(str) >> 22n) + 1420070400000n); - } catch { - console.error( - `The ID is corrupted, it's ${str} when it should be some number.` - ); - return 0; - } - } +public readonly id: string; +constructor(id: string) { +this.id = id; +} +getUnixTime(): number { +return SnowFlake.stringToUnixTime(this.id); +} +static stringToUnixTime(str: string) { +try { +return Number((BigInt(str) >> 22n) + 1420070400000n); +} catch { +console.error( +`The ID is corrupted, it's ${str} when it should be some number.` +); +return 0; +} +} } export { SnowFlake }; diff --git a/src/webpage/user.ts b/src/webpage/user.ts index d3fcff4..8197f1d 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -7,483 +7,483 @@ import { SnowFlake } from "./snowflake.js"; import { presencejson, userjson } from "./jsontypes.js"; class User extends SnowFlake { - owner: Localuser; - hypotheticalpfp!: boolean; - avatar!: string | null; - username!: string; - nickname: string | null = null; - relationshipType: 0 | 1 | 2 | 3 | 4 = 0; - bio!: MarkDown; - discriminator!: string; - pronouns!: string; - bot!: boolean; - public_flags!: number; - accent_color!: number; - banner: string | undefined; - hypotheticalbanner!: boolean; - premium_since!: string; - premium_type!: number; - theme_colors!: string; - badge_ids!: string[]; - members: WeakMap> = - new WeakMap(); - private status!: string; - resolving: false | Promise = false; +owner: Localuser; +hypotheticalpfp!: boolean; +avatar!: string | null; +username!: string; +nickname: string | null = null; +relationshipType: 0 | 1 | 2 | 3 | 4 = 0; +bio!: MarkDown; +discriminator!: string; +pronouns!: string; +bot!: boolean; +public_flags!: number; +accent_color!: number; +banner: string | undefined; +hypotheticalbanner!: boolean; +premium_since!: string; +premium_type!: number; +theme_colors!: string; +badge_ids!: string[]; +members: WeakMap> = + new WeakMap(); + private status!: string; + resolving: false | Promise = false; - constructor(userjson: userjson, owner: Localuser, dontclone = false) { - super(userjson.id); - this.owner = owner; - if (!owner) { - console.error("missing localuser"); - } - if (dontclone) { - for (const key of Object.keys(userjson)) { - if (key === "bio") { - this.bio = new MarkDown(userjson[key], this.localuser); - continue; - } - if (key === "id") { - continue; - } - (this as any)[key] = (userjson as any)[key]; - } - this.hypotheticalpfp = false; - } else { - return User.checkuser(userjson, owner); - } - } + constructor(userjson: userjson, owner: Localuser, dontclone = false) { + super(userjson.id); + this.owner = owner; + if (!owner) { + console.error("missing localuser"); + } + if (dontclone) { + for (const key of Object.keys(userjson)) { + if (key === "bio") { + this.bio = new MarkDown(userjson[key], this.localuser); + continue; + } + if (key === "id") { + continue; + } + (this as any)[key] = (userjson as any)[key]; + } + this.hypotheticalpfp = false; + } else { + return User.checkuser(userjson, owner); + } + } - clone(): User { - return new User( - { - username: this.username, - id: this.id + "#clone", - public_flags: this.public_flags, - discriminator: this.discriminator, - avatar: this.avatar, - accent_color: this.accent_color, - banner: this.banner, - bio: this.bio.rawString, - premium_since: this.premium_since, - premium_type: this.premium_type, - bot: this.bot, - theme_colors: this.theme_colors, - pronouns: this.pronouns, - badge_ids: this.badge_ids, - }, - this.owner - ); - } + clone(): User { + return new User( + { + username: this.username, + id: this.id + "#clone", + public_flags: this.public_flags, + discriminator: this.discriminator, + avatar: this.avatar, + accent_color: this.accent_color, + banner: this.banner, + bio: this.bio.rawString, + premium_since: this.premium_since, + premium_type: this.premium_type, + bot: this.bot, + theme_colors: this.theme_colors, + pronouns: this.pronouns, + badge_ids: this.badge_ids, + }, + this.owner + ); + } - public getPresence(presence: presencejson | undefined): void { - if (presence) { - this.setstatus(presence.status); - } else { - this.setstatus("offline"); - } - } + public getPresence(presence: presencejson | undefined): void { + if (presence) { + this.setstatus(presence.status); + } else { + this.setstatus("offline"); + } + } - setstatus(status: string): void { - this.status = status; - } + setstatus(status: string): void { + this.status = status; + } - async getStatus(): Promise { - return this.status || "offline"; - } + async getStatus(): Promise { + return this.status || "offline"; + } - static contextmenu = new Contextmenu("User Menu"); + static contextmenu = new Contextmenu("User Menu"); - static setUpContextMenu(): void { - this.contextmenu.addbutton("Copy user id", function (this: User) { - navigator.clipboard.writeText(this.id); - }); - this.contextmenu.addbutton("Message user", function (this: User) { - fetch(this.info.api + "/users/@me/channels", { - method: "POST", - body: JSON.stringify({ recipients: [this.id] }), - headers: this.localuser.headers, - }) - .then((res) => res.json()) - .then((json) => { - this.localuser.goToChannel(json.id); - }); - }); - this.contextmenu.addbutton( - "Block user", - function (this: User) { - this.block(); - }, - null, - function () { - return this.relationshipType !== 2; - } - ); + static setUpContextMenu(): void { + this.contextmenu.addbutton("Copy user id", function (this: User) { + navigator.clipboard.writeText(this.id); + }); + this.contextmenu.addbutton("Message user", function (this: User) { + fetch(this.info.api + "/users/@me/channels", { + method: "POST", + body: JSON.stringify({ recipients: [this.id] }), + headers: this.localuser.headers, + }) + .then((res) => res.json()) + .then((json) => { + this.localuser.goToChannel(json.id); + }); + }); + this.contextmenu.addbutton( + "Block user", + function (this: User) { + this.block(); + }, + null, + function () { + return this.relationshipType !== 2; + } + ); - this.contextmenu.addbutton( - "Unblock user", - function (this: User) { - this.unblock(); - }, - null, - function () { - return this.relationshipType === 2; - } - ); - this.contextmenu.addbutton("Friend request", function (this: User) { - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "PUT", - headers: this.owner.headers, - body: JSON.stringify({ - type: 1, - }), - }); - }); - this.contextmenu.addbutton( - "Kick member", - function (this: User, member: Member | undefined) { - member?.kick(); - }, - null, - (member) => { - if (!member) return false; - const us = member.guild.member; - if (member.id === us.id) { - return false; - } - if (member.id === member.guild.properties.owner_id) { - return false; - } - return us.hasPermission("KICK_MEMBERS") || false; - } - ); - this.contextmenu.addbutton( - "Ban member", - function (this: User, member: Member | undefined) { - member?.ban(); - }, - null, - (member) => { - if (!member) return false; - const us = member.guild.member; - if (member.id === us.id) { - return false; - } - if (member.id === member.guild.properties.owner_id) { - return false; - } - return us.hasPermission("BAN_MEMBERS") || false; - } - ); - } + this.contextmenu.addbutton( + "Unblock user", + function (this: User) { + this.unblock(); + }, + null, + function () { + return this.relationshipType === 2; + } + ); + this.contextmenu.addbutton("Friend request", function (this: User) { + fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "PUT", + headers: this.owner.headers, + body: JSON.stringify({ + type: 1, + }), + }); + }); + this.contextmenu.addbutton( + "Kick member", + function (this: User, member: Member | undefined) { + member?.kick(); + }, + null, + (member) => { + if (!member) return false; + const us = member.guild.member; + if (member.id === us.id) { + return false; + } + if (member.id === member.guild.properties.owner_id) { + return false; + } + return us.hasPermission("KICK_MEMBERS") || false; + } + ); + this.contextmenu.addbutton( + "Ban member", + function (this: User, member: Member | undefined) { + member?.ban(); + }, + null, + (member) => { + if (!member) return false; + const us = member.guild.member; + if (member.id === us.id) { + return false; + } + if (member.id === member.guild.properties.owner_id) { + return false; + } + return us.hasPermission("BAN_MEMBERS") || false; + } + ); + } - static checkuser(user: User | userjson, owner: Localuser): User { - if (owner.userMap.has(user.id)) { - return owner.userMap.get(user.id) as User; - } else { - const tempuser = new User(user as userjson, owner, true); - owner.userMap.set(user.id, tempuser); - return tempuser; - } - } + static checkuser(user: User | userjson, owner: Localuser): User { + if (owner.userMap.has(user.id)) { + return owner.userMap.get(user.id) as User; + } else { + const tempuser = new User(user as userjson, owner, true); + owner.userMap.set(user.id, tempuser); + return tempuser; + } + } - get info() { - return this.owner.info; - } + get info() { + return this.owner.info; + } - get localuser() { - return this.owner; - } + get localuser() { + return this.owner; + } - get name() { - return this.username; - } + get name() { + return this.username; + } - async resolvemember(guild: Guild): Promise { - return await Member.resolveMember(this, guild); - } + async resolvemember(guild: Guild): Promise { + return await Member.resolveMember(this, guild); + } - async getUserProfile(): Promise { - return await fetch( - `${this.info.api}/users/${this.id.replace( - "#clone", - "" - )}/profile?with_mutual_guilds=true&with_mutual_friends=true`, - { - headers: this.localuser.headers, - } - ).then((res) => res.json()); - } + async getUserProfile(): Promise { + return await fetch( + `${this.info.api}/users/${this.id.replace( + "#clone", + "" + )}/profile?with_mutual_guilds=true&with_mutual_friends=true`, + { + headers: this.localuser.headers, + } + ).then((res) => res.json()); + } - async getBadge(id: string): Promise { - if (this.localuser.badges.has(id)) { - return this.localuser.badges.get(id); - } else { - if (this.resolving) { - await this.resolving; - return this.localuser.badges.get(id); - } + async getBadge(id: string): Promise { + if (this.localuser.badges.has(id)) { + return this.localuser.badges.get(id); + } else { + if (this.resolving) { + await this.resolving; + return this.localuser.badges.get(id); + } - const prom = await this.getUserProfile(); - this.resolving = prom; - const badges = prom.badges; - this.resolving = false; - for (const badge of badges) { - this.localuser.badges.set(badge.id, badge); - } - return this.localuser.badges.get(id); - } - } + const prom = await this.getUserProfile(); + this.resolving = prom; + const badges = prom.badges; + this.resolving = false; + for (const badge of badges) { + this.localuser.badges.set(badge.id, badge); + } + return this.localuser.badges.get(id); + } + } - buildpfp(): HTMLImageElement { - const pfp = document.createElement("img"); - pfp.loading = "lazy"; - pfp.src = this.getpfpsrc(); - pfp.classList.add("pfp"); - pfp.classList.add("userid:" + this.id); - return pfp; - } + buildpfp(): HTMLImageElement { + const pfp = document.createElement("img"); + pfp.loading = "lazy"; + pfp.src = this.getpfpsrc(); + pfp.classList.add("pfp"); + pfp.classList.add("userid:" + this.id); + return pfp; + } - async buildstatuspfp(): Promise { - const div = document.createElement("div"); - div.style.position = "relative"; - const pfp = this.buildpfp(); - div.append(pfp); - const status = document.createElement("div"); - status.classList.add("statusDiv"); - switch (await this.getStatus()) { - case "offline": - status.classList.add("offlinestatus"); - break; - case "online": - default: - status.classList.add("onlinestatus"); - break; - } - div.append(status); - return div; - } + async buildstatuspfp(): Promise { + const div = document.createElement("div"); + div.style.position = "relative"; + const pfp = this.buildpfp(); + div.append(pfp); + const status = document.createElement("div"); + status.classList.add("statusDiv"); + switch (await this.getStatus()) { + case "offline": + status.classList.add("offlinestatus"); + break; + case "online": + default: + status.classList.add("onlinestatus"); + break; + } + div.append(status); + return div; + } - userupdate(json: userjson): void { - if (json.avatar !== this.avatar) { - this.changepfp(json.avatar); - } - } + userupdate(json: userjson): void { + if (json.avatar !== this.avatar) { + this.changepfp(json.avatar); + } + } - bind(html: HTMLElement, guild: Guild | null = null, error = true): void { - if (guild && guild.id !== "@me") { - Member.resolveMember(this, guild) - .then((member) => { - User.contextmenu.bindContextmenu(html, this, member); - if (member === undefined && error) { - const errorSpan = document.createElement("span"); - errorSpan.textContent = "!"; - errorSpan.classList.add("membererror"); - html.after(errorSpan); - return; - } - if (member) { - member.bind(html); - } - }) - .catch((err) => { - console.log(err); - }); - } - if (guild) { - this.profileclick(html, guild); - } else { - this.profileclick(html); - } - } + bind(html: HTMLElement, guild: Guild | null = null, error = true): void { + if (guild && guild.id !== "@me") { + Member.resolveMember(this, guild) + .then((member) => { + User.contextmenu.bindContextmenu(html, this, member); + if (member === undefined && error) { + const errorSpan = document.createElement("span"); + errorSpan.textContent = "!"; + errorSpan.classList.add("membererror"); + html.after(errorSpan); + return; + } + if (member) { + member.bind(html); + } + }) + .catch((err) => { + console.log(err); + }); + } + if (guild) { + this.profileclick(html, guild); + } else { + this.profileclick(html); + } + } - static async resolve(id: string, localuser: Localuser): Promise { - const json = await fetch( - localuser.info.api.toString() + "/users/" + id + "/profile", - { headers: localuser.headers } - ).then((res) => res.json()); - return new User(json, localuser); - } + static async resolve(id: string, localuser: Localuser): Promise { + const json = await fetch( + localuser.info.api.toString() + "/users/" + id + "/profile", + { headers: localuser.headers } + ).then((res) => res.json()); + return new User(json, localuser); + } - changepfp(update: string | null): void { - this.avatar = update; - this.hypotheticalpfp = false; - const src = this.getpfpsrc(); - Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( - (element) => { - (element as HTMLImageElement).src = src; - } - ); - } + changepfp(update: string | null): void { + this.avatar = update; + this.hypotheticalpfp = false; + const src = this.getpfpsrc(); + Array.from(document.getElementsByClassName("userid:" + this.id)).forEach( + (element) => { + (element as HTMLImageElement).src = src; + } + ); + } - block(): void { - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "PUT", - headers: this.owner.headers, - body: JSON.stringify({ - type: 2, - }), - }); - this.relationshipType = 2; - const channel = this.localuser.channelfocus; - if (channel) { - for (const message of channel.messages) { - message[1].generateMessage(); - } - } - } + block(): void { + fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "PUT", + headers: this.owner.headers, + body: JSON.stringify({ + type: 2, + }), + }); + this.relationshipType = 2; + const channel = this.localuser.channelfocus; + if (channel) { + for (const message of channel.messages) { + message[1].generateMessage(); + } + } + } - unblock(): void { - fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { - method: "DELETE", - headers: this.owner.headers, - }); - this.relationshipType = 0; - const channel = this.localuser.channelfocus; - if (channel) { - for (const message of channel.messages) { - message[1].generateMessage(); - } - } - } + unblock(): void { + fetch(`${this.info.api}/users/@me/relationships/${this.id}`, { + method: "DELETE", + headers: this.owner.headers, + }); + this.relationshipType = 0; + const channel = this.localuser.channelfocus; + if (channel) { + for (const message of channel.messages) { + message[1].generateMessage(); + } + } + } - getpfpsrc(): string { - if (this.hypotheticalpfp && this.avatar) { - return this.avatar; - } - if (this.avatar !== null) { - return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ - this.avatar - }.png`; - } else { - const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); - return `${this.info.cdn}/embed/avatars/${int}.png`; - } - } + getpfpsrc(): string { + if (this.hypotheticalpfp && this.avatar) { + return this.avatar; + } + if (this.avatar !== null) { + return `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ + this.avatar + }.png`; + } else { + const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n); + return `${this.info.cdn}/embed/avatars/${int}.png`; + } + } - async buildprofile( - x: number, - y: number, - guild: Guild | null = null - ): Promise { - if (Contextmenu.currentmenu != "") { - Contextmenu.currentmenu.remove(); - } + async buildprofile( + x: number, + y: number, + guild: Guild | null = null + ): Promise { + if (Contextmenu.currentmenu != "") { + Contextmenu.currentmenu.remove(); + } - const div = document.createElement("div"); + const div = document.createElement("div"); - if (this.accent_color) { - div.style.setProperty( - "--accent_color", - `#${this.accent_color.toString(16).padStart(6, "0")}` - ); - } else { - div.style.setProperty("--accent_color", "transparent"); - } - if (this.banner) { - const banner = document.createElement("img"); - let src: string; - if (!this.hypotheticalbanner) { - src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ - this.banner - }.png`; - } else { - src = this.banner; - } - banner.src = src; - banner.classList.add("banner"); - div.append(banner); - } - if (x !== -1) { - div.style.left = `${x}px`; - div.style.top = `${y}px`; - div.classList.add("profile", "flexttb"); - } else { - this.setstatus("online"); - div.classList.add("hypoprofile", "flexttb"); - } - const badgediv = document.createElement("div"); - badgediv.classList.add("badges"); - (async () => { - if (!this.badge_ids) return; - for (const id of this.badge_ids) { - const badgejson = await this.getBadge(id); - if (badgejson) { - const badge = document.createElement(badgejson.link ? "a" : "div"); - badge.classList.add("badge"); - const img = document.createElement("img"); - img.src = badgejson.icon; - badge.append(img); - const span = document.createElement("span"); - span.textContent = badgejson.description; - badge.append(span); - if (badge instanceof HTMLAnchorElement) { - badge.href = badgejson.link; - } - badgediv.append(badge); - } - } - })(); - const pfp = await this.buildstatuspfp(); - div.appendChild(pfp); - const userbody = document.createElement("div"); - userbody.classList.add("infosection"); - div.appendChild(userbody); - const usernamehtml = document.createElement("h2"); - usernamehtml.textContent = this.username; - userbody.appendChild(usernamehtml); - userbody.appendChild(badgediv); - const discrimatorhtml = document.createElement("h3"); - discrimatorhtml.classList.add("tag"); - discrimatorhtml.textContent = `${this.username}#${this.discriminator}`; - userbody.appendChild(discrimatorhtml); + if (this.accent_color) { + div.style.setProperty( + "--accent_color", + `#${this.accent_color.toString(16).padStart(6, "0")}` + ); + } else { + div.style.setProperty("--accent_color", "transparent"); + } + if (this.banner) { + const banner = document.createElement("img"); + let src: string; + if (!this.hypotheticalbanner) { + src = `${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${ + this.banner + }.png`; + } else { + src = this.banner; + } + banner.src = src; + banner.classList.add("banner"); + div.append(banner); + } + if (x !== -1) { + div.style.left = `${x}px`; + div.style.top = `${y}px`; + div.classList.add("profile", "flexttb"); + } else { + this.setstatus("online"); + div.classList.add("hypoprofile", "flexttb"); + } + const badgediv = document.createElement("div"); + badgediv.classList.add("badges"); + (async () => { + if (!this.badge_ids) return; + for (const id of this.badge_ids) { + const badgejson = await this.getBadge(id); + if (badgejson) { + const badge = document.createElement(badgejson.link ? "a" : "div"); + badge.classList.add("badge"); + const img = document.createElement("img"); + img.src = badgejson.icon; + badge.append(img); + const span = document.createElement("span"); + span.textContent = badgejson.description; + badge.append(span); + if (badge instanceof HTMLAnchorElement) { + badge.href = badgejson.link; + } + badgediv.append(badge); + } + } + })(); + const pfp = await this.buildstatuspfp(); + div.appendChild(pfp); + const userbody = document.createElement("div"); + userbody.classList.add("infosection"); + div.appendChild(userbody); + const usernamehtml = document.createElement("h2"); + usernamehtml.textContent = this.username; + userbody.appendChild(usernamehtml); + userbody.appendChild(badgediv); + const discrimatorhtml = document.createElement("h3"); + discrimatorhtml.classList.add("tag"); + discrimatorhtml.textContent = `${this.username}#${this.discriminator}`; + userbody.appendChild(discrimatorhtml); - const pronounshtml = document.createElement("p"); - pronounshtml.textContent = this.pronouns; - pronounshtml.classList.add("pronouns"); - userbody.appendChild(pronounshtml); + const pronounshtml = document.createElement("p"); + pronounshtml.textContent = this.pronouns; + pronounshtml.classList.add("pronouns"); + userbody.appendChild(pronounshtml); - const rule = document.createElement("hr"); - userbody.appendChild(rule); - const biohtml = this.bio.makeHTML(); - userbody.appendChild(biohtml); - if (guild) { - Member.resolveMember(this, guild).then((member) => { - if (!member) return; - const roles = document.createElement("div"); - roles.classList.add("rolesbox"); - for (const role of member.roles) { - const roleDiv = document.createElement("div"); - roleDiv.classList.add("rolediv"); - const color = document.createElement("div"); - roleDiv.append(color); - color.style.setProperty( - "--role-color", - `#${role.color.toString(16).padStart(6, "0")}` - ); - color.classList.add("colorrolediv"); - const span = document.createElement("span"); - roleDiv.append(span); - span.textContent = role.name; - roles.append(roleDiv); - } - userbody.append(roles); - }); - } - if (x !== -1) { - Contextmenu.currentmenu = div; - document.body.appendChild(div); - Contextmenu.keepOnScreen(div); - } - return div; - } + const rule = document.createElement("hr"); + userbody.appendChild(rule); + const biohtml = this.bio.makeHTML(); + userbody.appendChild(biohtml); + if (guild) { + Member.resolveMember(this, guild).then((member) => { + if (!member) return; + const roles = document.createElement("div"); + roles.classList.add("rolesbox"); + for (const role of member.roles) { + const roleDiv = document.createElement("div"); + roleDiv.classList.add("rolediv"); + const color = document.createElement("div"); + roleDiv.append(color); + color.style.setProperty( + "--role-color", + `#${role.color.toString(16).padStart(6, "0")}` + ); + color.classList.add("colorrolediv"); + const span = document.createElement("span"); + roleDiv.append(span); + span.textContent = role.name; + roles.append(roleDiv); + } + userbody.append(roles); + }); + } + if (x !== -1) { + Contextmenu.currentmenu = div; + document.body.appendChild(div); + Contextmenu.keepOnScreen(div); + } + return div; + } - profileclick(obj: HTMLElement, guild?: Guild): void { - obj.onclick = (e: MouseEvent) => { - this.buildprofile(e.clientX, e.clientY, guild); - e.stopPropagation(); - }; - } -} + profileclick(obj: HTMLElement, guild?: Guild): void { + obj.onclick = (e: MouseEvent) => { + this.buildprofile(e.clientX, e.clientY, guild); + e.stopPropagation(); + }; + } + } -User.setUpContextMenu(); -export { User }; + User.setUpContextMenu(); + export { User };