diff --git a/.gitignore b/.gitignore
index a582a2f3..8631a69d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,5 @@ yarn.lock
dbconf.json
migrations.db
+
+assets/cache_src/
diff --git a/assets/fosscord-login.css b/assets/fosscord-login.css
deleted file mode 100644
index 975bf908..00000000
--- a/assets/fosscord-login.css
+++ /dev/null
@@ -1,68 +0,0 @@
-/* replace tos acceptance popup */
-#app-mount > div:nth-child(7) > div > div > div.tooltipContent-bqVLWK {
- visibility: hidden;
-}
-#app-mount > div:nth-child(7) > div > div > div.tooltipContent-bqVLWK::after {
- visibility: visible;
- display: block;
- content: "You need to agree to this instance's rules to continue";
- margin-top: -32px;
-}
-/* replace login header */
-#app-mount > div.app-1q1i1E > div > div > div > div > form > div > div > div.mainLoginContainer-1ddwnR > h3 {
- visibility: hidden;
-}
-h3.title-jXR8lp.marginBottom8-AtZOdT.base-1x0h_U.size24-RIRrxO::after {
- margin-top: -32px;
- content: "Welcome to Fosscord!";
- visibility: visible;
- display: block;
-}
-
-/* Logo in top left when bg removed */
-#app-mount > div.app-1q1i1E > div > a {
- /* replace me: original dimensions: 130x36 */
- background: url(https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Gradient.svg);
- width: 130px;
- height: 23px;
- background-size: contain;
-}
-
-/* replace TOS text */
-
-#app-mount
- > div.app-1q1i1E
- > div
- > div
- > div
- > form
- > div
- > div
- > div.flex-1xMQg5.flex-1O1GKY.horizontal-1ae9ci.horizontal-2EEEnY.flex-1O1GKY.directionRow-3v3tfG.justifyStart-2NDFzi.alignCenter-1dQNNs.noWrap-3jynv6.marginTop20-3TxNs6
- > label
- > div.label-cywgfr.labelClickable-11AuB8.labelForward-1wfipV
- > * {
- visibility: hidden;
-}
-
-#app-mount
- > div.app-1q1i1E
- > div
- > div
- > div
- > form
- > div
- > div
- > div.flex-1xMQg5.flex-1O1GKY.horizontal-1ae9ci.horizontal-2EEEnY.flex-1O1GKY.directionRow-3v3tfG.justifyStart-2NDFzi.alignCenter-1dQNNs.noWrap-3jynv6.marginTop20-3TxNs6
- > label
- > div.label-cywgfr.labelClickable-11AuB8.labelForward-1wfipV::after {
- visibility: visible;
- content: "I have read and agree with the rules set by this instance.";
- display: block;
- margin-top: -16px;
-}
-
-/* shrink login box to same size as register */
-.authBoxExpanded-2jqaBe {
- width: 480px !important;
-}
diff --git a/assets/fosscord.css b/assets/fosscord.css
index fa503d39..ba54b1cf 100644
--- a/assets/fosscord.css
+++ b/assets/fosscord.css
@@ -1,92 +1,4 @@
/* loading spinner */
-#app-mount > div.app-1q1i1E > div.container-16j22k.fixClipping-3qAKRb > div.content-1-zrf2 > video {
- filter: opacity(1);
- background: url("http://www.clipartbest.com/cliparts/7ca/6Rr/7ca6RrLAi.gif");
- background-size: contain;
- /* width: 64px;
- height: 64px; */
- padding-bottom: 64px;
- background-repeat: no-repeat;
-}
-
-/* home button icon */
-#app-mount
- > div.app-1q1i1E
- > div
- > div.layers-3iHuyZ.layers-3q14ss
- > div
- > div
- > nav
- > ul
- > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih
- > div.tutorialContainer-2sGCg9
- > div
- > div.listItemWrapper-KhRmzM
- > div
- > svg
- > foreignObject
- > div
- > div {
- background-image: url(https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Icon-Rounded-Subtract.svg);
- background-size: contain;
- border-radius: 50%;
-}
-
-#app-mount
- > div.app-1q1i1E
- > div
- > div.layers-3iHuyZ.layers-3q14ss
- > div
- > div
- > nav
- > ul
- > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih
- > div.tutorialContainer-2sGCg9
- > div
- > div.listItemWrapper-KhRmzM
- > div
- > svg
- > foreignObject
- > div
- > div,
-#app-mount
- > div.app-1q1i1E
- > div
- > div.layers-3iHuyZ.layers-3q14ss
- > div
- > div
- > nav
- > ul
- > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih
- > div.tutorialContainer-2sGCg9
- > div
- > div.listItemWrapper-KhRmzM
- > div
- > svg
- > foreignObject
- > div
- > div:hover {
- background-color: white;
-}
-/* Login QR */
-#app-mount > div.app-1q1i1E > div > div > div > div > form > div > div > div.transitionGroup-aR7y1d.qrLogin-1AOZMt,
-#app-mount > div.app-1q1i1E > div > div > div > div > form > div > div > div.verticalSeparator-3huAjp,
-/* Remove login bg */
-#app-mount > div.app-1q1i1E > div > svg,
-/* Download bar */
-#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > div > div.notice-3bPHh-.colorDefault-22HBa0,
-/* Connection problem links */
-#app-mount > div.app-1q1i1E > div.container-16j22k.fixClipping-3qAKRb > div.problems-3mgf6w.slideIn-sCvzGz > div:nth-child(2),
-/* Downloads button */
-#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > nav > ul > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih > div:nth-child(7) > div.listItemWrapper-KhRmzM > div > svg > foreignObject > div,
-#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > nav > ul > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih > div:nth-child(6) > div,
-/* help button */
-#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > div > div.content-98HsJk > div.chat-3bRxxu > section > div.toolbar-1t6TWx > a,
-/* download button start of guild */
-#chat-messages-899316648933185083 > div > div > div:nth-child(5),
-/* Thread permissions etc popups */
-#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > div > div.content-98HsJk > div.sidebar-2K8pFh.hasNotice-1XRy4h > nav > div.container-3O_wAf,
-/* home button icon */
-#app-mount > div.app-1q1i1E > div > div.layers-3iHuyZ.layers-3q14ss > div > div > nav > ul > div.scroller-1Bvpku.none-2Eo-qx.scrollerBase-289Jih > div.tutorialContainer-2sGCg9 > div > div.listItemWrapper-KhRmzM > div > svg > foreignObject > div > div > svg {
- display: none;
-}
+:root {
+ --brand-hue: 22;
+}
\ No newline at end of file
diff --git a/assets/index.html b/assets/index.html
index 4513d0d2..0cd90c8e 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Discord Test Client</title>
<link rel="stylesheet" href="/assets/fosscord.css" />
- <link id="logincss" rel="stylesheet" href="/assets/fosscord-login.css" />
<link id="customcss" rel="stylesheet" href="/assets/user.css" />
<!-- inline plugin marker -->
<!-- preload plugin marker -->
@@ -28,20 +27,21 @@
INVITE_HOST: `${location.hostname}/invite`,
GUILD_TEMPLATE_HOST: "${location.host}",
GIFT_CODE_HOST: "${location.hostname}",
- RELEASE_CHANNEL: "stable",
MARKETING_ENDPOINT: "//discord.com",
BRAINTREE_KEY: "production_5st77rrc_49pp2rp4phym7387",
STRIPE_KEY: "pk_live_CUQtlpQUF0vufWpnpUmQvcdi",
NETWORKING_ENDPOINT: "//router.discordapp.net",
RTC_LATENCY_ENDPOINT: "//${location.hostname}/rtc",
ACTIVITY_APPLICATION_HOST: "discordsays.com",
- PROJECT_ENV: "production",
REMOTE_AUTH_ENDPOINT: "//localhost:3020",
SENTRY_TAGS: { buildId: "75e36d9", buildType: "normal" },
MIGRATION_SOURCE_ORIGIN: "https://${location.hostname}",
MIGRATION_DESTINATION_ORIGIN: "https://${location.hostname}",
HTML_TIMESTAMP: Date.now(),
- ALGOLIA_KEY: "aca0d7082e4e63af5ba5917d5e96bed0"
+ ALGOLIA_KEY: "aca0d7082e4e63af5ba5917d5e96bed0",
+ SENTRY_TAGS: { instance: document.location.host },
+ PROJECT_ENV: "development",
+ RELEASE_CHANNEL: "staging",
};
GLOBAL_ENV.MEDIA_PROXY_ENDPOINT = location.protocol + "//" + GLOBAL_ENV.CDN_HOST;
const localStorage = window.localStorage;
diff --git a/assets/private/icons/custom/.gitkeep b/assets/private/icons/custom/.gitkeep
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/assets/private/icons/custom/.gitkeep
diff --git a/assets/private/icons/homeIcon.path b/assets/private/icons/homeIcon.path
new file mode 100644
index 00000000..b4b062ca
--- /dev/null
+++ b/assets/private/icons/homeIcon.path
@@ -0,0 +1 @@
+M 0,0 47.999993,2.7036528e-4 C 48.001796,3.3028172 47.663993,6.5968018 46.991821,9.8301938 43.116101,28.454191 28.452575,43.116441 9.8293509,46.992163 6.5960834,47.664163 3.3023222,48.001868 0,47.999992 Z m 9.8293509,28.735114 v 9.248482 C 22.673599,33.047696 32.857154,22.749268 37.63852,9.829938 H 9.8293509 v 8.679899 H 22.931288 c -3.554489,3.93617 -7.735383,7.257633 -12.373436,9.829938 -0.241031,0.133684 -0.483864,0.265492 -0.7285011,0.395339 z
\ No newline at end of file
diff --git a/fosscord-server.code-workspace b/fosscord-server.code-workspace
index 56450f85..a9b15856 100644
--- a/fosscord-server.code-workspace
+++ b/fosscord-server.code-workspace
@@ -16,6 +16,8 @@
"settings": {
"files.exclude": {
"*.ansi": true,
+ "**/cache": true,
+ "**/cache_src": true
}
},
"launch": {
@@ -32,6 +34,12 @@
"name": "Run Fosscord with debugger (kitty)",
"request": "launch",
"type": "node-terminal"
+ },
+ {
+ "command": "[ \"$(basename $PWD)\" != \"fosscord-server\" ] && cd ..; $(ps -o comm= $PPID) assets/cache",
+ "name": "Open testclient patch workspace",
+ "request": "launch",
+ "type": "node-terminal"
}
]
}
diff --git a/scripts/patches/applyPatches.js b/scripts/patches/applyPatches.js
new file mode 100644
index 00000000..66524ede
--- /dev/null
+++ b/scripts/patches/applyPatches.js
@@ -0,0 +1,24 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+//apply patches
+const patchDir = path.join(__dirname, "..", "..", "assets", "testclient_patches");
+const targetDir = path.join(__dirname, "..", "..", "assets", "cache");
+const files = fs.readdirSync(patchDir);
+files.forEach((file) => {
+ const filePath = path.join(patchDir, file);
+ const stats = fs.statSync(filePath);
+ if (stats.isFile()) {
+ const ext = path.extname(file);
+ if (ext === ".patch") {
+ execSync(`git apply ${filePath}`, {
+ cwd: targetDir,
+ maxBuffer: 1024 * 1024 * 10,
+ });
+ console.log(`Applied patch ${file} to ${newFilePath}`);
+ }
+ }
+});
\ No newline at end of file
diff --git a/scripts/patches/mkPatches.js b/scripts/patches/mkPatches.js
new file mode 100644
index 00000000..1e1e0475
--- /dev/null
+++ b/scripts/patches/mkPatches.js
@@ -0,0 +1,40 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+//generate git patch for each file in assets/cache
+const srcDir = path.join(__dirname, "..", "..", "assets", "cache");
+const destDir = path.join(__dirname, "..", "..", "assets", "cache_src");
+const patchDir = path.join(__dirname, "..", "..", "assets", "testclient_patches");
+if(!fs.existsSync(patchDir)) fs.mkdirSync(patchDir);
+const files = fs.readdirSync(srcDir);
+files.forEach((file) => {
+ const filePath = path.join(srcDir, file);
+ const stats = fs.statSync(filePath);
+ if (stats.isFile()) {
+ const ext = path.extname(file);
+ if (ext === ".js" || ext === ".css") {
+ const newFilePath = path.join(destDir, file);
+ //check if file has been modified
+ let patch;
+ try {
+ let es = execSync(`diff -du --speed-large-files --horizon-lines=0 ${newFilePath} ${filePath}`, {
+ maxBuffer: 1024 * 1024 * 10,
+ }).toString();
+ patch="";
+ } catch (e) {
+ patch = e.stdout.toString().replaceAll(path.join(destDir, file), file).replaceAll(path.join(srcDir, file), file);
+ }
+ if (patch.length > 0) {
+ //generate patch;
+ fs.writeFileSync(path.join(patchDir, file + ".patch"), patch);
+ console.log(`Generated patch for ${file}: ${patch.length} bytes, ${patch.split("\n").length} lines, ${patch.split("\n").filter((x) => x.startsWith("+")).length} additions, ${patch.split("\n").filter((x) => x.startsWith("-")).length} deletions`);
+ }
+ else {
+ //console.log(`No changes for ${file}`);
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/scripts/patches/prepWS.js b/scripts/patches/prepWS.js
new file mode 100644
index 00000000..bc4d0a9a
--- /dev/null
+++ b/scripts/patches/prepWS.js
@@ -0,0 +1,75 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+//copy all js and css files from assets/cache to assets/dist
+const srcDir = path.join(__dirname, "..", "..", "assets", "cache");
+const destDir = path.join(__dirname, "..", "..", "assets", "cache_src");
+if(!fs.existsSync(destDir)) fs.mkdirSync(destDir);
+const files = fs.readdirSync(srcDir);
+files.forEach((file) => {
+ const filePath = path.join(srcDir, file);
+ const stats = fs.statSync(filePath);
+ if (stats.isFile()) {
+ const ext = path.extname(file);
+ if (ext === ".js" || ext === ".css") {
+ const newFilePath = path.join(destDir, file);
+ if(!fs.existsSync(newFilePath)) {
+ fs.copyFileSync(filePath, newFilePath);
+ console.log(`Copied ${file} to ${newFilePath}`);
+ }
+ }
+ }
+});
+if(!fs.existsSync(path.join(srcDir, ".vscode"))) fs.mkdirSync(path.join(srcDir, ".vscode"));
+fs.writeFileSync(path.join(srcDir, ".vscode", "settings.json"), JSON.stringify({
+ "codemetrics.basics.DecorationModeEnabled": false,
+ "codemetrics.basics.CodeLensEnabled": false,
+ "editor.codeLens": false,
+ //"editor.minimap.enabled": false,
+ "codemetrics.basics.MetricsForArrowFunctionsToggled": false,
+ "codemetrics.basics.MetricsForClassDeclarationsToggled": false,
+ "codemetrics.basics.MetricsForConstructorsToggled": false,
+ "codemetrics.basics.MetricsForEnumDeclarationsToggled": false,
+ "codemetrics.basics.MetricsForFunctionExpressionsToggled": false,
+ "codemetrics.basics.MetricsForFunctionDeclarationsToggled": false,
+ "codemetrics.basics.MetricsForMethodDeclarationsToggled": false,
+ "codemetrics.basics.OverviewRulerModeEnabled": false,
+ "editor.fontFamily": "'JetBrainsMono Nerd Font', 'JetBrainsMono', 'Droid Sans Mono', 'monospace', monospace",
+ "editor.accessibilityPageSize": 1,
+ "editor.accessibilitySupport": "off",
+ "editor.autoClosingDelete": "never",
+ //"editor.autoIndent": "none",
+ //"editor.colorDecorators": false,
+ "editor.comments.ignoreEmptyLines": false,
+ "editor.copyWithSyntaxHighlighting": false,
+ "editor.comments.insertSpace": false,
+ "editor.detectIndentation": false,
+ "editor.dragAndDrop": false,
+ "editor.dropIntoEditor.enabled": false,
+ "editor.experimental.pasteActions.enabled": false,
+ "editor.guides.highlightActiveIndentation": false,
+ "color-highlight.enable": false,
+ "gitlens.blame.highlight.locations": [
+ "gutter"
+ ],
+ "todohighlight.isEnable": false,
+ "todohighlight.maxFilesForSearch": 1,
+ "editor.maxTokenizationLineLength": 1200,
+ "editor.minimap.maxColumn": 140,
+ "explorer.openEditors.visible": 0,
+ "editor.fontLigatures": false,
+ "files.exclude": {
+ "*.mp3": true,
+ "*.png": true,
+ "*.svg": true,
+ "*.webm": true,
+ "*.webp": true,
+ "*.woff2": true,
+ "**/.vscode/": true
+ },
+ "editor.guides.bracketPairs": true
+}, null, 4));
+console.log(`Workspace prepared at ${srcDir}!`);
\ No newline at end of file
diff --git a/scripts/patches/resetWS.js b/scripts/patches/resetWS.js
new file mode 100644
index 00000000..d34b74fd
--- /dev/null
+++ b/scripts/patches/resetWS.js
@@ -0,0 +1,24 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+//copy all js and css files from assets/cache_src to assets/cache
+const srcDir = path.join(__dirname, "..", "..", "assets", "cache_src");
+const destDir = path.join(__dirname, "..", "..", "assets", "cache");
+if(!fs.existsSync(destDir)) fs.mkdirSync(destDir);
+const files = fs.readdirSync(srcDir);
+files.forEach((file) => {
+ const filePath = path.join(srcDir, file);
+ const stats = fs.statSync(filePath);
+ if (stats.isFile()) {
+ const ext = path.extname(file);
+ if (ext === ".js" || ext === ".css") {
+ const newFilePath = path.join(destDir, file);
+ fs.rmSync(newFilePath);
+ fs.copyFileSync(filePath, newFilePath);
+ console.log(`Copied ${file} to ${newFilePath}`);
+ }
+ }
+});
\ No newline at end of file
diff --git a/src/api/Server.ts b/src/api/Server.ts
index e92335a5..560014d0 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -11,6 +11,7 @@ import { initRateLimits } from "./middlewares/RateLimit";
import TestClient from "./middlewares/TestClient";
import { initTranslation } from "./middlewares/Translation";
import { initInstance } from "./util/handlers/Instance";
+import fs from "fs";
export interface FosscordServerOptions extends ServerOptions {}
@@ -42,6 +43,8 @@ export class FosscordServer extends Server {
this.app.use(
morgan("combined", {
skip: (req, res) => {
+ if(req.path.endsWith(".map")) return true;
+ if(req.path.includes("/assets/") && !fs.existsSync(path.join(__dirname, "..", "..", "..", "assets", req.path.split("/")[0].split('?')[0]))) return true;
let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
return skip;
diff --git a/src/api/middlewares/TestClient.ts b/src/api/middlewares/TestClient.ts
index 3afd0339..9090840e 100644
--- a/src/api/middlewares/TestClient.ts
+++ b/src/api/middlewares/TestClient.ts
@@ -6,7 +6,9 @@ import path from "path";
import { green } from "picocolors";
import ProxyAgent from "proxy-agent";
import { AssetCacheItem } from "../util/entities/AssetCacheItem";
+import { patchFile } from "..";
+const prettier = require("prettier");
const AssetsPath = path.join(__dirname, "..", "..", "..", "assets");
export default function TestClient(app: Application) {
@@ -39,7 +41,7 @@ export default function TestClient(app: Application) {
let response: FetchResponse;
let buffer: Buffer;
let assetCacheItem: AssetCacheItem = new AssetCacheItem(req.params.file);
- if (newAssetCache.has(req.params.file)) {
+ if (newAssetCache.has(req.params.file) && fs.existsSync(newAssetCache.get(req.params.file)!.FilePath)) {
assetCacheItem = newAssetCache.get(req.params.file)!;
assetCacheItem.Headers.forEach((value: any, name: any) => {
res.set(name, value);
@@ -56,16 +58,21 @@ export default function TestClient(app: Application) {
...req.headers
}
});
-
//set cache info
assetCacheItem.Headers = Object.fromEntries(stripHeaders(response.headers));
- assetCacheItem.FilePath = path.join(assetCacheDir, req.params.file);
assetCacheItem.Key = req.params.file;
//add to cache and save
newAssetCache.set(req.params.file, assetCacheItem);
+
+ if(response.status != 200) {
+ return res.status(404).send("Not found");
+ }
+ assetCacheItem.FilePath = path.join(assetCacheDir, req.params.file);
+ if(!fs.existsSync(assetCacheDir))
+ fs.mkdirSync(assetCacheDir);
fs.writeFileSync(path.join(assetCacheDir, "index.json"), JSON.stringify(Object.fromEntries(newAssetCache), null, 4));
//download file
- fs.writeFileSync(assetCacheItem.FilePath, await response.buffer());
+ fs.writeFileSync(assetCacheItem.FilePath, /.*\.(js|css)/.test(req.params.file) ? patchFile(assetCacheItem.FilePath, (await response.buffer()).toString()) : await response.buffer());
}
assetCacheItem.Headers.forEach((value: string, name: string) => {
diff --git a/src/api/util/TestClientPatcher.ts b/src/api/util/TestClientPatcher.ts
new file mode 100644
index 00000000..2e9bfafe
--- /dev/null
+++ b/src/api/util/TestClientPatcher.ts
@@ -0,0 +1,107 @@
+import path from "path";
+import fs from "fs";
+
+console.log('[TestClient] Loading private assets...');
+
+const privateAssetsRoot = path.join(__dirname, "..", "..", "..", "assets", "private");
+const iconsRoot = path.join(privateAssetsRoot, "icons");
+const icons = new Map<string, Buffer>();
+
+fs.readdirSync(iconsRoot).forEach(file => {
+ const fileName = path.basename(file);
+ //check if dir
+ if(fs.lstatSync(path.join(iconsRoot, file)).isDirectory()){
+ return;
+ }
+ icons.set(fileName,fs.readFileSync(path.join(iconsRoot,file)) as Buffer);
+});
+
+fs.readdirSync(path.join(iconsRoot, "custom")).forEach(file => {
+ const fileName = path.basename(file);
+ if(fs.lstatSync(path.join(iconsRoot,"custom", file)).isDirectory()){
+ return;
+ }
+ icons.set(fileName,fs.readFileSync(path.join(iconsRoot,"custom",file)) as Buffer);
+});
+
+console.log('[TestClient] Patcher ready!');
+
+export function patchFile(filePath: string, content: string): string {
+ console.log(`[TestClient] Patching ${filePath}`);
+ let startTime = Date.now();
+
+ content = prettier(filePath, content);
+ content = autoPatch(filePath, content);
+
+ console.log(`[TestClient] Patched ${filePath} in ${Date.now() - startTime}ms`);
+ return content;
+}
+function prettier(filePath: string, content: string): string{
+ let prettier = require("prettier");
+ let parser;
+ filePath = filePath.toLowerCase().split('?')[0];
+ if(filePath.endsWith(".js")) {
+ parser = "babel";
+ } else if (filePath.endsWith(".ts")){
+ parser = "typescript";
+ } else if(filePath.endsWith(".css")){
+ parser = "css";
+ } else if(filePath.endsWith(".json")){
+ parser = "json";
+ }
+ else {
+ console.log(`[TestClient] Skipping prettier for ${filePath}, unknown file type!`);
+ return content;
+ }
+ content = prettier.format(content, {
+ tabWidth: 4,
+ useTabs: true,
+ printWidth: 140,
+ trailingComma: "none",
+ parser
+ });
+ console.log(`[TestClient] Prettified ${filePath}!`);
+ return content;
+}
+
+function autoPatch(filePath: string, content: string): string{
+ //remove nitro references
+ content = content.replace(/Discord Nitro/g, "Fosscord Premium");
+ content = content.replace(/"Nitro"/g, "\"Premium\"");
+ content = content.replace(/Nitro /g, "Premium ");
+ content = content.replace(/ Nitro/g, " Premium");
+ content = content.replace(/\[Nitro\]/g, "[Premium]");
+ content = content.replace(/\*Nitro\*/g, "*Premium*");
+ content = content.replace(/\"Nitro \. /g, "\"Premium. ");
+
+ //remove discord references
+ content = content.replace(/ Discord /g, " Fosscord ");
+ content = content.replace(/Discord /g, "Fosscord ");
+ content = content.replace(/ Discord/g, " Fosscord");
+ content = content.replace(/Discord Premium/g, "Fosscord Premium");
+ content = content.replace(/Discord Nitro/g, "Fosscord Premium");
+ content = content.replace(/Discord's/g, "Fosscord's");
+ //content = content.replace(/DiscordTag/g, "FosscordTag");
+ content = content.replace(/\*Discord\*/g, "*Fosscord*");
+
+ //change some vars
+ content = content.replace('dsn: "https://fa97a90475514c03a42f80cd36d147c4@sentry.io/140984"', "dsn: (/true/.test(localStorage.sentryOptIn)?'https://6bad92b0175d41a18a037a73d0cff282@sentry.thearcanebrony.net/12':'')");
+ content = content.replace('t.DSN = "https://fa97a90475514c03a42f80cd36d147c4@sentry.io/140984"', "t.DSN = (/true/.test(localStorage.sentryOptIn)?'https://6bad92b0175d41a18a037a73d0cff282@sentry.thearcanebrony.net/12':'')");
+ content = content.replace('--brand-experiment: hsl(235, calc(var(--saturation-factor, 1) * 85.6%), 64.7%);', '--brand-experiment: hsl(var(--brand-hue), calc(var(--saturation-factor, 1) * 85.6%), 50%);');
+ content = content.replaceAll(/--brand-experiment-(\d{1,4}): hsl\(235/g, '--brand-experiment-\$1: hsl(var(--brand-hue)')
+
+ //logos
+ content = content.replace(/d: "M23\.0212.*/, `d: "${icons.get("homeIcon.path")!.toString()}"`);
+ content = content.replace('width: n, height: o, viewBox: "0 0 28 20"', 'width: 48, height: 48, viewBox: "0 0 48 48"');
+
+ //undo webpacking
+ // - booleans
+ content = content.replace(/!0/g, "true");
+ content = content.replace(/!1/g, "false");
+ // - real esmodule defs
+ content = content.replace(/Object.defineProperty\((.), "__esModule", { value: (.*) }\);/g, '\$1.__esModule = \$2;');
+
+
+ console.log(`[TestClient] Autopatched ${path.basename(filePath)}!`);
+ return content;
+}
\ No newline at end of file
diff --git a/src/api/util/index.ts b/src/api/util/index.ts
index d06860cd..f01c2f43 100644
--- a/src/api/util/index.ts
+++ b/src/api/util/index.ts
@@ -8,3 +8,5 @@ export * from "./utility/ipAddress";
export * from "./utility/passwordStrength";
export * from "./utility/RandomInviteID";
export * from "./utility/String";
+export * from "./utility/captcha";
+export * from "./TestClientPatcher";
\ No newline at end of file
|