🎉 standard client finished
This commit is contained in:
parent
38d66df4e2
commit
bb9a76a2a1
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"sourceMaps": true,
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"program": "${workspaceFolder}/dist/index.js",
|
||||||
|
"preLaunchTask": "tsc: build - tsconfig.json",
|
||||||
|
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug current file",
|
||||||
|
"program": "${file}",
|
||||||
|
"request": "launch",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"type": "node"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
82
client/index.html
Normal file
82
client/index.html
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Discord Open Source Client</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app-mount"></div>
|
||||||
|
<script>
|
||||||
|
window.__OVERLAY__ = /overlay/.test(location.pathname);
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
window.__BILLING_STANDALONE__ = /^\/billing/.test(location.pathname);
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
window.GLOBAL_ENV = {
|
||||||
|
API_ENDPOINT: "/api",
|
||||||
|
WEBAPP_ENDPOINT: "",
|
||||||
|
CDN_HOST: "cdn.discordapp.com",
|
||||||
|
ASSET_ENDPOINT: "",
|
||||||
|
MEDIA_PROXY_ENDPOINT: "https://media.discordapp.net",
|
||||||
|
WIDGET_ENDPOINT: "//discord.com/widget",
|
||||||
|
INVITE_HOST: "discord.gg",
|
||||||
|
GUILD_TEMPLATE_HOST: "discord.new",
|
||||||
|
GIFT_CODE_HOST: "discord.gift",
|
||||||
|
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: "//latency.discord.media/rtc",
|
||||||
|
PROJECT_ENV: "production",
|
||||||
|
REMOTE_AUTH_ENDPOINT: "//remote-auth-gateway.discord.gg",
|
||||||
|
SENTRY_TAGS: { buildId: "75e36d9", buildType: "normal" },
|
||||||
|
MIGRATION_SOURCE_ORIGIN: "https://discordapp.com",
|
||||||
|
MIGRATION_DESTINATION_ORIGIN: "https://discord.com",
|
||||||
|
HTML_TIMESTAMP: Date.now(),
|
||||||
|
ALGOLIA_KEY: "aca0d7082e4e63af5ba5917d5e96bed0",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
!(function () {
|
||||||
|
if (null != window.WebSocket) {
|
||||||
|
var n = function (n) {
|
||||||
|
try {
|
||||||
|
var e = localStorage.getItem(n);
|
||||||
|
return null == e ? null : JSON.parse(e);
|
||||||
|
} catch (n) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
e = n("token"),
|
||||||
|
o = n("gatewayURL");
|
||||||
|
if (e && o && !window.__OVERLAY__) {
|
||||||
|
var r = null != window.DiscordNative || null != window.require ? "etf" : "json",
|
||||||
|
t = o + "/?encoding=" + r + "&v=8&compress=zlib-stream";
|
||||||
|
console.log("[FAST CONNECT] " + t + ", encoding: " + r + ", version: 8");
|
||||||
|
var a = new WebSocket(t);
|
||||||
|
a.binaryType = "arraybuffer";
|
||||||
|
var s = Date.now(),
|
||||||
|
i = { open: !1, gateway: t, messages: [] };
|
||||||
|
(a.onopen = function () {
|
||||||
|
console.log("[FAST CONNECT] connected in " + (Date.now() - s) + "ms"), (i.open = !0);
|
||||||
|
}),
|
||||||
|
(a.onclose = a.onerror = function () {
|
||||||
|
window._ws = null;
|
||||||
|
}),
|
||||||
|
(a.onmessage = function (n) {
|
||||||
|
i.messages.push(n);
|
||||||
|
}),
|
||||||
|
(window._ws = { ws: a, state: i });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<script src="/assets/2b51ccc483c24624b4e4.js"></script>
|
||||||
|
<script src="/assets/f70adb19750694f9b703.js"></script>
|
||||||
|
<script src="/assets/d850ccc09f143ad7daa1.js"></script>
|
||||||
|
<script src="/assets/55bf39c3af597194d853.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1233
package-lock.json
generated
Normal file
1233
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "discord-server-opensource",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Trenite/discord-server-opensource.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Trenite/discord-server-opensource/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Trenite/discord-server-opensource#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^14.14.10",
|
||||||
|
"typescript": "^4.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^4.17.9",
|
||||||
|
"@types/node-fetch": "^2.5.7",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"express-cache-middleware": "^1.0.1",
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
|
}
|
||||||
|
}
|
72
src/Server.ts
Normal file
72
src/Server.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import express, { Application, Router } from "express";
|
||||||
|
import { traverseDirectory } from "./Utils";
|
||||||
|
import { Server as HTTPServer } from "http";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
|
||||||
|
export type ServerOptions = {
|
||||||
|
port: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Server {
|
||||||
|
private app: Application;
|
||||||
|
private http: HTTPServer;
|
||||||
|
private options: ServerOptions;
|
||||||
|
private routes: Router[];
|
||||||
|
private initalized: Promise<any>;
|
||||||
|
|
||||||
|
constructor(opts: ServerOptions = { port: 8080 }) {
|
||||||
|
this.options = opts;
|
||||||
|
|
||||||
|
this.app = express();
|
||||||
|
|
||||||
|
this.initalized = this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
// recursively loads files in routes/
|
||||||
|
this.routes = await this.registerRoutes(__dirname + "/routes/");
|
||||||
|
// const indexHTML = await (await fetch("https://discord.com/app")).buffer();
|
||||||
|
const indexHTML = await fs.readFile(__dirname + "/../client/index.html");
|
||||||
|
|
||||||
|
this.app.get("*", (req, res) => {
|
||||||
|
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24);
|
||||||
|
res.set("content-type", "text/html");
|
||||||
|
res.send(indexHTML);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.initalized;
|
||||||
|
await new Promise<void>((res) => this.app.listen(this.options.port, () => res()));
|
||||||
|
console.log(`[Server] started on ${this.options.port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerRoutes(root: string) {
|
||||||
|
return await traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root));
|
||||||
|
}
|
||||||
|
|
||||||
|
registerRoute(root: string, file: string): any {
|
||||||
|
if (root.endsWith("/") || root.endsWith("\\")) root = root.slice(0, -1); // removes slash at the end of the root dir
|
||||||
|
let path = file.replace(root, ""); // remove root from path and
|
||||||
|
path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
|
||||||
|
if (path.endsWith("/index")) path = path.slice(0, -6); // delete index from path
|
||||||
|
|
||||||
|
try {
|
||||||
|
var router = require(file);
|
||||||
|
if (router.router) router = router.router;
|
||||||
|
if (router.default) router = router.default;
|
||||||
|
if (!router || router.prototype.constructor.name !== "router")
|
||||||
|
throw `File doesn't export any default router`;
|
||||||
|
this.app.use(path, <Router>router);
|
||||||
|
console.log(`[Server] Route ${path} registerd`);
|
||||||
|
return router;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(new Error(`[Server] Failed to register route ${file}: ${error}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
return new Promise<void>((res) => this.http.close(() => res()));
|
||||||
|
}
|
||||||
|
}
|
47
src/Utils.ts
Normal file
47
src/Utils.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Array<T> {
|
||||||
|
flat(): T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.prototype.flat = function () {
|
||||||
|
return this.reduce((acc, val) => (Array.isArray(val) ? acc.concat(val.flat()) : acc.concat(val)), []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface traverseDirectoryOptions {
|
||||||
|
dirname: string;
|
||||||
|
filter?: RegExp;
|
||||||
|
excludeDirs?: RegExp;
|
||||||
|
recursive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_EXCLUDE_DIR = /^\./;
|
||||||
|
const DEFAULT_FILTER = /^([^\.].*)\.js$/;
|
||||||
|
|
||||||
|
export async function traverseDirectory<T>(
|
||||||
|
options: traverseDirectoryOptions,
|
||||||
|
action: (path: string) => T
|
||||||
|
): Promise<T[]> {
|
||||||
|
if (!options.filter) options.filter = DEFAULT_FILTER;
|
||||||
|
if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR;
|
||||||
|
|
||||||
|
const routes = await fs.readdir(options.dirname);
|
||||||
|
const promises = <Promise<T | T[] | undefined>[]>routes.map(async (file) => {
|
||||||
|
const path = options.dirname + file;
|
||||||
|
const stat = await fs.lstat(path);
|
||||||
|
if (path.match(<RegExp>options.excludeDirs)) return;
|
||||||
|
|
||||||
|
if (stat.isFile() && path.match(<RegExp>options.filter)) {
|
||||||
|
return action(path);
|
||||||
|
} else if (options.recursive && stat.isDirectory()) {
|
||||||
|
return traverseDirectory({ ...options, dirname: path + "/" }, action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const result = await Promise.all(promises);
|
||||||
|
|
||||||
|
const t = <(T | undefined)[]>result.flat();
|
||||||
|
|
||||||
|
return <T[]>t.filter((x) => x != undefined);
|
||||||
|
}
|
4
src/index.ts
Normal file
4
src/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Server } from "./Server";
|
||||||
|
|
||||||
|
const server = new Server();
|
||||||
|
server.start().catch(console.error);
|
30
src/routes/assets/index.ts
Normal file
30
src/routes/assets/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
const cache = new Map();
|
||||||
|
const assetEndpoint = "https://discord.com/assets/";
|
||||||
|
|
||||||
|
export async function getCache(key: string): Promise<Response> {
|
||||||
|
let cachedRessource = cache.get(key);
|
||||||
|
|
||||||
|
if (!cachedRessource) {
|
||||||
|
const res = await fetch(assetEndpoint + key);
|
||||||
|
// @ts-ignore
|
||||||
|
res.bufferResponse = await res.buffer();
|
||||||
|
cache.set(key, res);
|
||||||
|
cachedRessource = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedRessource;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get("/:hash", async (req, res) => {
|
||||||
|
res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24);
|
||||||
|
const cache = await getCache(req.params.hash);
|
||||||
|
res.set("content-type", <string>cache.headers.get("content-type"));
|
||||||
|
// @ts-ignore
|
||||||
|
res.send(cache.bufferResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
69
tsconfig.json
Normal file
69
tsconfig.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||||
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||||
|
"lib": ["ES2015", "DOM"] /* Specify library files to be included in the compilation. */,
|
||||||
|
"allowJs": true /* Allow javascript files to be compiled. */,
|
||||||
|
"checkJs": true /* Report errors in .js files. */,
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
"declaration": true /* Generates corresponding '.d.ts' file. */,
|
||||||
|
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
|
||||||
|
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./dist/" /* Redirect output structure to the directory. */,
|
||||||
|
"rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
"strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */,
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
"types": ["node"] /* Type declaration files to be included in compilation. */,
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user