From 3fc8855eb876e70f1fc032f22f7208092a0d577b Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 14:21:52 +0100 Subject: [PATCH 01/53] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..c7095453 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# discord-cdn +cdn for discord clone From 82e63ef807cad85905df323b09b2ac0fb21bcb1a Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 22:49:01 +0100 Subject: [PATCH 02/53] :sparkles: added crawler to cdn --- .gitignore | 3 + package-lock.json | Bin 0 -> 59210 bytes package.json | 38 ++++++++++++ src/Server.ts | 93 +++++++++++++++++++++++++++++ src/Util.ts | 38 ++++++++++++ src/index.ts | 14 +++++ src/routes/attachments.ts.disabled | 19 ++++++ src/routes/external.ts | 83 +++++++++++++++++++++++++ tsconfig.json | 69 +++++++++++++++++++++ 9 files changed, 357 insertions(+) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/Server.ts create mode 100644 src/Util.ts create mode 100644 src/index.ts create mode 100644 src/routes/attachments.ts.disabled create mode 100644 src/routes/external.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ab03d840 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +node_modules/ +dist/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..9d7a1f511d23e32c87fbecec6194d1039168b5ae GIT binary patch literal 59210 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 00000000..847c688f --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "discord-cdn", + "version": "1.0.0", + "description": "cdn for discord clone", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/discord-open-source/discord-cdn.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/discord-open-source/discord-cdn/issues" + }, + "homepage": "https://github.com/discord-open-source/discord-cdn#readme", + "dependencies": { + "body-parser": "^1.19.0", + "btoa": "^1.2.1", + "cheerio": "^1.0.0-rc.5", + "express": "^4.17.1", + "express-async-errors": "^3.1.1", + "lambert-db": "^1.0.5", + "missing-native-js-functions": "^1.0.8", + "multer": "^1.4.2", + "node-fetch": "^2.6.1" + }, + "devDependencies": { + "@types/btoa": "^1.2.3", + "@types/express": "^4.17.9", + "@types/multer": "^1.4.5", + "@types/node": "^14.14.16", + "@types/node-fetch": "^2.5.7" + } +} diff --git a/src/Server.ts b/src/Server.ts new file mode 100644 index 00000000..7d93c444 --- /dev/null +++ b/src/Server.ts @@ -0,0 +1,93 @@ +import express, { Application, Router, Request, Response, NextFunction } from "express"; +import { MongoDatabase, Database } from "lambert-db"; +import { Server as HTTPServer } from "http"; +import { traverseDirectory } from "./Util"; +import bodyParser from "body-parser"; +import "express-async-errors"; + +const log = console.log; +console.log = (content) => { + log(`[${new Date().toTimeString().split(" ")[0]}]`, content); +}; + +export type ServerOptions = { + db: string; + port: number; + host: string; +}; + +declare global { + namespace Express { + interface Request { + server: Server; + } + } +} + +export class Server { + app: Application; + http: HTTPServer; + db: Database; + routes: Router[]; + options: ServerOptions; + + constructor(options: Partial = { port: 3000, host: "0.0.0.0" }) { + this.app = express(); + this.db = new MongoDatabase(options?.db); + this.options = options as ServerOptions; + } + + async init() { + await this.db.init(); + + console.log("[Database] connected..."); + await new Promise((res, rej) => { + this.http = this.app.listen(this.options.port, this.options.host, () => res(null)); + }); + this.routes = await this.registerRoutes(__dirname + "/routes/"); + } + + async registerRoutes(root: string) { + this.app.use((req, res, next) => { + req.server = this; + next(); + }); + const routes = await traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); + this.app.use((err: string | Error, req: Request, res: Response, next: NextFunction) => { + res.status(400).send(err); + next(err); + }); + return routes; + } + + 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); + console.log(`[Routes] ${path} registerd`); + + return router; + } catch (error) { + console.error(new Error(`[Server] ¯\\_(ツ)_/¯ Failed to register route ${path}: ${error}`)); + } + } + + async destroy() { + await this.db.destroy(); + await new Promise((res, rej) => { + this.http.close((err) => { + if (err) return rej(err); + return res(""); + }); + }); + } +} diff --git a/src/Util.ts b/src/Util.ts new file mode 100644 index 00000000..291372c1 --- /dev/null +++ b/src/Util.ts @@ -0,0 +1,38 @@ +import fs from "fs/promises"; +import "missing-native-js-functions"; + +export interface traverseDirectoryOptions { + dirname: string; + filter?: RegExp; + excludeDirs?: RegExp; + recursive?: boolean; +} + +const DEFAULT_EXCLUDE_DIR = /^\./; +const DEFAULT_FILTER = /^([^\.].*)\.js$/; + +export async function traverseDirectory( + options: traverseDirectoryOptions, + action: (path: string) => T +): Promise { + if (!options.filter) options.filter = DEFAULT_FILTER; + if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; + + const routes = await fs.readdir(options.dirname); + const promises = []>routes.map(async (file) => { + const path = options.dirname + file; + const stat = await fs.lstat(path); + if (path.match(options.excludeDirs)) return; + + if (stat.isFile() && path.match(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.filter((x) => x != undefined); +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..d8025968 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,14 @@ +import { Server } from "./Server"; + +const server = new Server(); +server + .init() + .then(() => { + console.log("[Server] started on :" + server.options.port); + }) + .catch((e) => console.error("[Server] Error starting: ", e)); + +//// server +//// .destroy() +//// .then(() => console.log("[Server] closed.")) +//// .catch((e) => console.log("[Server] Error closing: ", e)); diff --git a/src/routes/attachments.ts.disabled b/src/routes/attachments.ts.disabled new file mode 100644 index 00000000..db1a7efc --- /dev/null +++ b/src/routes/attachments.ts.disabled @@ -0,0 +1,19 @@ +import { Router } from "express"; +import multer from "multer"; +const multer_ = multer(); + +const router = Router(); +router.post("/:file", multer_.single("attachment"), async (req, res) => { + const { buffer } = req.file; + + res.set("Content-Type", "image/png"); + res.send(buffer); +}); +router.get("/:hash/:file", async (req, res) => { + res.send(`${req.params.hash}/${req.params.file}`); +}); +router.delete("/:hash/:file", async (req, res) => { + res.send("remove"); +}); + +export default router; diff --git a/src/routes/external.ts b/src/routes/external.ts new file mode 100644 index 00000000..14980b05 --- /dev/null +++ b/src/routes/external.ts @@ -0,0 +1,83 @@ +import bodyParser from "body-parser"; +import { Router } from "express"; +import fetch from "node-fetch"; +import cheerio from "cheerio"; +import btoa from "btoa"; +import { URL } from "url"; + +const router = Router(); + +type crawled = { + title: string; + type: string; + description: string; + url: string; + image_url: string; +}; + +const DEFAULT_FETCH_OPTIONS: any = { + redirect: "follow", + follow: 1, + headers: { + "user-agent": "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)", + }, + size: 1024 * 1024 * 8, + compress: true, + method: "GET", +}; + +router.post("/", bodyParser.json(), async (req, res) => { + if (!req.body) throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); + + const { db } = req.server; + const { url } = req.body; + + const ID = btoa(url); + + const cache = await db.data.crawler({ id: ID }).get(); + if (cache) return res.send(cache); + + try { + const request = await fetch(url, DEFAULT_FETCH_OPTIONS); + + const text = await request.text(); + const ツ: any = cheerio.load(text); + + const ogTitle = ツ('meta[property="og:title"]').attr("content"); + const ogDescription = ツ('meta[property="og:description"]').attr("content"); + const ogImage = ツ('meta[property="og:image"]').attr("content"); + const ogUrl = ツ('meta[property="og:url"]').attr("content"); + const ogType = ツ('meta[property="og:type"]').attr("content"); + + const filename = new URL(url).host.split(".")[0]; + + const ImageResponse = await fetch(ogImage, DEFAULT_FETCH_OPTIONS); + const ImageType = ImageResponse.headers.get("content-type"); + const ImageExtension = ImageType?.split("/")[1]; + const ImageResponseBuffer = (await ImageResponse.buffer()).toString("base64"); + const cachedImage = `/external/${ID}/${filename}.${ImageExtension}`; + + await db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); + + const new_cache_entry = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; + await db.data.crawler.push(new_cache_entry); + + res.send(new_cache_entry); + } catch (error) { + console.log(error); + + throw new Error("Couldn't fetch website"); + } +}); + +router.get("/:id/:filename", async (req, res) => { + const { db } = req.server; + const { id, filename } = req.params; + const { image, type } = await db.data.externals({ id: id }).get(); + const imageBuffer = Buffer.from(image, "base64"); + + res.set("Content-Type", type); + res.send(imageBuffer); +}); + +export default router; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..26be7b1b --- /dev/null +++ b/tsconfig.json @@ -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"] /* 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": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, + "inlineSourceMap": true /* Emit a single file with source maps instead of having a separate 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. */ + // "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. */ + } +} From 4ad77092d07159ebcf5ada40e49aa17fade1fe04 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:07:11 +0100 Subject: [PATCH 03/53] Update README.md --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index c7095453..3439a95d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ # discord-cdn cdn for discord clone + +## Endpoints: + +### /external +#### POST +``` +Content-Type: application/json + +body: +{"url": URL} // "https://discord.com" +``` +##### Returns: +Content-Type: application/json +```ts +{ + "id": string, // "aHR0cHM6Ly9kaXNjb3JkLmNvbQ==" + "ogTitle": string, // "Discord | Your Place to Talk and Hang Out" + "ogDescription": string, // "Discord is the easiest way to talk over voice, video, and text. Talk, chat, hang out, and stay close with your friends and communities." + "cachedImage": string, // "/external/aHR0cHM6Ly9kaXNjb3JkLmNvbQ==/discord.png" + "ogUrl": string, // "https://discord.com/" + "ogType": string // "website" +} +``` +### /external/:id/:filename +#### GET From 738d78ca5a5d21f6dbbc7e7ebe72ccd883d388c0 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:14:04 +0100 Subject: [PATCH 04/53] Update README.md --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3439a95d..ede9cb5c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ cdn for discord clone ## Endpoints: -### /external +### `/external` #### POST ``` Content-Type: application/json @@ -23,5 +23,17 @@ Content-Type: application/json "ogType": string // "website" } ``` -### /external/:id/:filename +### `/external//` #### GET +``` +url-params: + :id // aHR0cHM6Ly9kaXNjb3JkLmNvbQ== + :filename // discord.png + +/external/aHR0cHM6Ly9kaXNjb3JkLmNvbQ==/discord.png +``` +##### Returns: +``` +Content-Type: image/ +Image +``` From 4edda3503dc1c075b1a50cf94c6e2a5ba6016d90 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:17:45 +0100 Subject: [PATCH 05/53] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ede9cb5c..c6a5f2f9 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ Content-Type: application/json url-params: :id // aHR0cHM6Ly9kaXNjb3JkLmNvbQ== :filename // discord.png - +``` +``` /external/aHR0cHM6Ly9kaXNjb3JkLmNvbQ==/discord.png ``` ##### Returns: From 316847094ea9c5d7d6e0e23aea0313b305cc8659 Mon Sep 17 00:00:00 2001 From: xnacly Date: Wed, 30 Dec 2020 23:18:05 +0100 Subject: [PATCH 06/53] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6a5f2f9..68cc50da 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# discord-cdn +# Discord-CDN cdn for discord clone ## Endpoints: From abf416728ec671a013ff1eec5e656c0f2417af6d Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 11:55:08 +0100 Subject: [PATCH 07/53] updated crawler type and implemented it --- src/routes/external.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/routes/external.ts b/src/routes/external.ts index 14980b05..be680003 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -8,11 +8,12 @@ import { URL } from "url"; const router = Router(); type crawled = { - title: string; - type: string; - description: string; - url: string; - image_url: string; + id: string; + ogTitle: string; + ogType: string; + ogDescription: string; + ogUrl: string; + cachedImage: string; }; const DEFAULT_FETCH_OPTIONS: any = { @@ -59,7 +60,7 @@ router.post("/", bodyParser.json(), async (req, res) => { await db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); - const new_cache_entry = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; + const new_cache_entry: crawled = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; await db.data.crawler.push(new_cache_entry); res.send(new_cache_entry); From 631a1d80a09287995492e461834e6a555b7625dd Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:44:05 +0100 Subject: [PATCH 08/53] finished cdn (POST, GET, DELETE) --- package-lock.json | Bin 59210 -> 59731 bytes package.json | 6 +- src/Snowflake.js | 145 +++++++++++++++++++++++++++++ src/routes/attachments.ts | 50 ++++++++++ src/routes/attachments.ts.disabled | 19 ---- 5 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 src/Snowflake.js create mode 100644 src/routes/attachments.ts delete mode 100644 src/routes/attachments.ts.disabled diff --git a/package-lock.json b/package-lock.json index 9d7a1f511d23e32c87fbecec6194d1039168b5ae..9b5a53109bd9137b7bc58ccdb5c55e70f9f32443 100644 GIT binary patch delta 282 zcmX?gj`{K><_&LzC)WwGPyX;AaPkCB_Q{S9jYCrdEeqYk_0uxa+`Zf^!o5A*T|J^) zE5pLmqAH3)w9TA-JiXkVlA{9BDnc{#wVm=@v$R8<-6MT-iYyDGN+a}*9CJ#F%gT$~ z3kwWz^xE+I8}{!EU|%u{)M7>y<$hr&pN=nWv_2KJ~DH6##m`WrqL& delta 51 zcmV-30L=f>(gVuQ1F-B9v-cGGfRn$Z6_Skwvk8|EOp_m$3X?v6D3eO#5R+RJBa@7l JUbEZdcm<2o7svnr diff --git a/package.json b/package.json index 847c688f..03662356 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,15 @@ "lambert-db": "^1.0.5", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "uuid": "^8.3.2" }, "devDependencies": { "@types/btoa": "^1.2.3", "@types/express": "^4.17.9", "@types/multer": "^1.4.5", "@types/node": "^14.14.16", - "@types/node-fetch": "^2.5.7" + "@types/node-fetch": "^2.5.7", + "@types/uuid": "^8.3.0" } } diff --git a/src/Snowflake.js b/src/Snowflake.js new file mode 100644 index 00000000..feb5eb41 --- /dev/null +++ b/src/Snowflake.js @@ -0,0 +1,145 @@ +// @ts-nocheck + +// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js +"use strict"; + +// Discord epoch (2015-01-01T00:00:00.000Z) +const EPOCH = 1420070400000; +let INCREMENT = 0; + +/** + * A container for useful snowflake-related methods. + */ +class SnowflakeUtil { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z + * ``` + * If we have a snowflake '266241948824764416' we can represent it as binary: + * + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of ms since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ + + /** + * Transforms a snowflake from a decimal string to a bit string. + * @param {Snowflake} num Snowflake to be transformed + * @returns {string} + * @private + */ + static idToBinary(num) { + let bin = ""; + let high = parseInt(num.slice(0, -10)) || 0; + let low = parseInt(num.slice(-10)); + while (low > 0 || high > 0) { + bin = String(low & 1) + bin; + low = Math.floor(low / 2); + if (high > 0) { + low += 5000000000 * (high % 2); + high = Math.floor(high / 2); + } + } + return bin; + } + + /** + * Transforms a snowflake from a bit string to a decimal string. + * @param {string} num Bit string to be transformed + * @returns {Snowflake} + * @private + */ + static binaryToID(num) { + let dec = ""; + + while (num.length > 50) { + const high = parseInt(num.slice(0, -32), 2); + const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); + + dec = (low % 10).toString() + dec; + num = + Math.floor(high / 10).toString(2) + + Math.floor(low / 10) + .toString(2) + .padStart(32, "0"); + } + + num = parseInt(num, 2); + while (num > 0) { + dec = (num % 10).toString() + dec; + num = Math.floor(num / 10); + } + + return dec; + } + + /** + * Generates a Discord snowflake. + * This hardcodes the worker ID as 1 and the process ID as 0. + * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate + * @returns {Snowflake} The generated snowflake + */ + static generate(timestamp = Date.now()) { + if (timestamp instanceof Date) timestamp = timestamp.getTime(); + if (typeof timestamp !== "number" || isNaN(timestamp)) { + throw new TypeError( + `"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})` + ); + } + if (INCREMENT >= 4095) INCREMENT = 0; + const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++) + .toString(2) + .padStart(12, "0")}`; + return SnowflakeUtil.binaryToID(BINARY); + } + + /** + * A deconstructed snowflake. + * @typedef {Object} DeconstructedSnowflake + * @property {number} timestamp Timestamp the snowflake was created + * @property {Date} date Date the snowflake was created + * @property {number} workerID Worker ID in the snowflake + * @property {number} processID Process ID in the snowflake + * @property {number} increment Increment in the snowflake + * @property {string} binary Binary representation of the snowflake + */ + + /** + * Deconstructs a Discord snowflake. + * @param {Snowflake} snowflake Snowflake to deconstruct + * @returns {DeconstructedSnowflake} Deconstructed snowflake + */ + static deconstruct(snowflake) { + const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0"); + const res = { + timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, + workerID: parseInt(BINARY.substring(42, 47), 2), + processID: parseInt(BINARY.substring(47, 52), 2), + increment: parseInt(BINARY.substring(52, 64), 2), + binary: BINARY, + }; + Object.defineProperty(res, "date", { + get: function get() { + return new Date(this.timestamp); + }, + enumerable: true, + }); + return res; + } + + /** + * Discord's epoch value (2015-01-01T00:00:00.000Z). + * @type {number} + * @readonly + */ + static get EPOCH() { + return EPOCH; + } +} + +module.exports = SnowflakeUtil; diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts new file mode 100644 index 00000000..7d09e402 --- /dev/null +++ b/src/routes/attachments.ts @@ -0,0 +1,50 @@ +import { Router } from "express"; +import multer from "multer"; +import Snowflake from "../Snowflake"; + +const multer_ = multer(); +const router = Router(); + +type Attachment = { + filename: string; + file: string; + id: string; + type: string; +}; + +router.post("/:filename", multer_.single("attachment"), async (req, res) => { + const { buffer, mimetype } = req.file; + const { filename } = req.params; + const { db } = req.server; + + const File: Attachment = { + filename, + file: buffer.toString("base64"), + id: Snowflake.generate(), + type: mimetype, + }; + + if (!(await db.data.attachments.push(File))) throw new Error("Error uploading file"); + + return res.status(201).send({ success: true, message: "attachment uploaded", id: File.id, filename }); +}); + +router.get("/:hash/:filename", async (req, res) => { + const { db } = req.server; + const { hash, filename } = req.params; + + const File: Attachment = await db.data.attachments({ id: hash, filename: filename }).get(); + + res.set("Content-Type", File.type); + return res.send(Buffer.from(File.file, "base64")); +}); + +router.delete("/:hash/:filename", async (req, res) => { + const { hash, filename } = req.params; + const { db } = req.server; + + await db.data.attachments({ id: hash, filename: filename }).delete(); + return res.send({ success: true, message: "attachment deleted" }); +}); + +export default router; diff --git a/src/routes/attachments.ts.disabled b/src/routes/attachments.ts.disabled deleted file mode 100644 index db1a7efc..00000000 --- a/src/routes/attachments.ts.disabled +++ /dev/null @@ -1,19 +0,0 @@ -import { Router } from "express"; -import multer from "multer"; -const multer_ = multer(); - -const router = Router(); -router.post("/:file", multer_.single("attachment"), async (req, res) => { - const { buffer } = req.file; - - res.set("Content-Type", "image/png"); - res.send(buffer); -}); -router.get("/:hash/:file", async (req, res) => { - res.send(`${req.params.hash}/${req.params.file}`); -}); -router.delete("/:hash/:file", async (req, res) => { - res.send("remove"); -}); - -export default router; From f1d63918efe1559137e14ee1252bf19152d4925e Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:54:40 +0100 Subject: [PATCH 09/53] Update README.md --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 68cc50da..ec28d6d5 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,64 @@ cdn for discord clone ## Endpoints: +### `/attachments/` +#### POST +``` +Content-Type: form-data +attachment: File +``` +##### Returns: +``` +{ + "success": boolean, // true + "message": string, // "attachment uploaded" + "id": snowflake, // "794183329158135808" + "filename": string // "lakdoiauej.png" +} +``` +### `/attachments//` +#### GET +``` +requests image from database with given and +``` +##### Returns: +``` +Content-Type: image/ +Image +``` +### `/attachments//` +#### DELETE +``` +deletes database entry +``` +##### Returns: +``` +Content-Type: application/json + +{ + "success": true, + "message": "attachment deleted" +} +``` + +
+ +_(endpoints for crawler):_ ### `/external` #### POST ``` +requests crawling of `og:`metadata and the download of the `og:image` property +-------- Content-Type: application/json body: {"url": URL} // "https://discord.com" ``` ##### Returns: +``` Content-Type: application/json -```ts + { "id": string, // "aHR0cHM6Ly9kaXNjb3JkLmNvbQ==" "ogTitle": string, // "Discord | Your Place to Talk and Hang Out" @@ -25,6 +71,7 @@ Content-Type: application/json ``` ### `/external//` #### GET +- requests cached crawled image ``` url-params: :id // aHR0cHM6Ly9kaXNjb3JkLmNvbQ== From b9d40cbca0c91fd3f42d42a47496e0f16e5ebe38 Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:55:28 +0100 Subject: [PATCH 10/53] Update README.md --- README.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec28d6d5..491db0c0 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ cdn for discord clone ## Endpoints: -### `/attachments/` -#### POST +### POST `/attachments/` ``` Content-Type: form-data @@ -18,8 +17,7 @@ attachment: File "filename": string // "lakdoiauej.png" } ``` -### `/attachments//` -#### GET +### GET `/attachments//` ``` requests image from database with given and ``` @@ -28,8 +26,7 @@ requests image from database with given and Content-Type: image/ Image ``` -### `/attachments//` -#### DELETE +### DELETE `/attachments//` ``` deletes database entry ``` @@ -46,8 +43,8 @@ Content-Type: application/json
_(endpoints for crawler):_ -### `/external` -#### POST +### POST `/external` + ``` requests crawling of `og:`metadata and the download of the `og:image` property -------- @@ -69,8 +66,7 @@ Content-Type: application/json "ogType": string // "website" } ``` -### `/external//` -#### GET +### GET `/external//` - requests cached crawled image ``` url-params: From f971d83785ff0bf31568aaa3ba955c4df012216e Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:56:35 +0100 Subject: [PATCH 11/53] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 491db0c0..9747e175 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ cdn for discord clone ``` Content-Type: form-data -attachment: File +attachment: File (binary-data) ``` ##### Returns: ``` From 2e4e7b826d3a5b2eeaa7e12c188dbf2393221093 Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:57:48 +0100 Subject: [PATCH 12/53] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 9747e175..d34bd264 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # Discord-CDN cdn for discord clone +## Run localy: +``` +npm i +node dist/ +``` + ## Endpoints: ### POST `/attachments/` ``` From 7b8b8b649e6f3879dd59d1c26ea65cad160b6d1b Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 31 Dec 2020 13:58:18 +0100 Subject: [PATCH 13/53] dist dir --- .gitignore | 3 +- dist/Server.d.ts | 29 ++++++++ dist/Server.js | 92 ++++++++++++++++++++++++ dist/Snowflake.d.ts | 85 +++++++++++++++++++++++ dist/Snowflake.js | 131 +++++++++++++++++++++++++++++++++++ dist/Util.d.ts | 8 +++ dist/Util.js | 45 ++++++++++++ dist/index.d.ts | 1 + dist/index.js | 15 ++++ dist/routes/attachments.d.ts | 2 + dist/routes/attachments.js | 48 +++++++++++++ dist/routes/external.d.ts | 2 + dist/routes/external.js | 75 ++++++++++++++++++++ 13 files changed, 534 insertions(+), 2 deletions(-) create mode 100644 dist/Server.d.ts create mode 100644 dist/Server.js create mode 100644 dist/Snowflake.d.ts create mode 100644 dist/Snowflake.js create mode 100644 dist/Util.d.ts create mode 100644 dist/Util.js create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/routes/attachments.d.ts create mode 100644 dist/routes/attachments.js create mode 100644 dist/routes/external.d.ts create mode 100644 dist/routes/external.js diff --git a/.gitignore b/.gitignore index ab03d840..49260768 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .vscode/ -node_modules/ -dist/ \ No newline at end of file +node_modules/ \ No newline at end of file diff --git a/dist/Server.d.ts b/dist/Server.d.ts new file mode 100644 index 00000000..f4d12c65 --- /dev/null +++ b/dist/Server.d.ts @@ -0,0 +1,29 @@ +/// +import { Application, Router } from "express"; +import { Database } from "lambert-db"; +import { Server as HTTPServer } from "http"; +import "express-async-errors"; +export declare type ServerOptions = { + db: string; + port: number; + host: string; +}; +declare global { + namespace Express { + interface Request { + server: Server; + } + } +} +export declare class Server { + app: Application; + http: HTTPServer; + db: Database; + routes: Router[]; + options: ServerOptions; + constructor(options?: Partial); + init(): Promise; + registerRoutes(root: string): Promise; + registerRoute(root: string, file: string): any; + destroy(): Promise; +} diff --git a/dist/Server.js b/dist/Server.js new file mode 100644 index 00000000..72046dfc --- /dev/null +++ b/dist/Server.js @@ -0,0 +1,92 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Server = void 0; +const express_1 = __importDefault(require("express")); +const lambert_db_1 = require("lambert-db"); +const Util_1 = require("./Util"); +require("express-async-errors"); +const log = console.log; +console.log = (content) => { + log(`[${new Date().toTimeString().split(" ")[0]}]`, content); +}; +class Server { + constructor(options = { port: 3000, host: "0.0.0.0" }) { + this.app = express_1.default(); + this.db = new lambert_db_1.MongoDatabase(options === null || options === void 0 ? void 0 : options.db); + this.options = options; + } + init() { + return __awaiter(this, void 0, void 0, function* () { + yield this.db.init(); + console.log("[Database] connected..."); + yield new Promise((res, rej) => { + this.http = this.app.listen(this.options.port, this.options.host, () => res(null)); + }); + this.routes = yield this.registerRoutes(__dirname + "/routes/"); + }); + } + registerRoutes(root) { + return __awaiter(this, void 0, void 0, function* () { + this.app.use((req, res, next) => { + req.server = this; + next(); + }); + const routes = yield Util_1.traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); + this.app.use((err, req, res, next) => { + res.status(400).send(err); + next(err); + }); + return routes; + }); + } + registerRoute(root, file) { + var _a, _b; + 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 || ((_b = (_a = router === null || router === void 0 ? void 0 : router.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) !== "router") + throw `File doesn't export any default router`; + this.app.use(path, router); + console.log(`[Routes] ${path} registerd`); + return router; + } + catch (error) { + console.error(new Error(`[Server] ¯\\_(ツ)_/¯ Failed to register route ${path}: ${error}`)); + } + } + destroy() { + return __awaiter(this, void 0, void 0, function* () { + yield this.db.destroy(); + yield new Promise((res, rej) => { + this.http.close((err) => { + if (err) + return rej(err); + return res(""); + }); + }); + }); + } +} +exports.Server = Server; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1NlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7QUFBQSxzREFBd0Y7QUFDeEYsMkNBQXFEO0FBRXJELGlDQUEyQztBQUUzQyxnQ0FBOEI7QUFFOUIsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQztBQUN4QixPQUFPLENBQUMsR0FBRyxHQUFHLENBQUMsT0FBTyxFQUFFLEVBQUU7SUFDekIsR0FBRyxDQUFDLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM5RCxDQUFDLENBQUM7QUFnQkYsTUFBYSxNQUFNO0lBT2xCLFlBQVksVUFBa0MsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUU7UUFDNUUsSUFBSSxDQUFDLEdBQUcsR0FBRyxpQkFBTyxFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDLEVBQUUsR0FBRyxJQUFJLDBCQUFhLENBQUMsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBd0IsQ0FBQztJQUN6QyxDQUFDO0lBRUssSUFBSTs7WUFDVCxNQUFNLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFLENBQUM7WUFFckIsT0FBTyxDQUFDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO1lBQ3ZDLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUU7Z0JBQzlCLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8s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 \ No newline at end of file diff --git a/dist/Snowflake.d.ts b/dist/Snowflake.d.ts new file mode 100644 index 00000000..17effce6 --- /dev/null +++ b/dist/Snowflake.d.ts @@ -0,0 +1,85 @@ +export = SnowflakeUtil; +/** + * A container for useful snowflake-related methods. + */ +declare class SnowflakeUtil { + /** + * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z + * ``` + * If we have a snowflake '266241948824764416' we can represent it as binary: + * + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of ms since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ + /** + * Transforms a snowflake from a decimal string to a bit string. + * @param {Snowflake} num Snowflake to be transformed + * @returns {string} + * @private + */ + private static idToBinary; + /** + * Transforms a snowflake from a bit string to a decimal string. + * @param {string} num Bit string to be transformed + * @returns {Snowflake} + * @private + */ + private static binaryToID; + /** + * Generates a Discord snowflake. + * This hardcodes the worker ID as 1 and the process ID as 0. + * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate + * @returns {Snowflake} The generated snowflake + */ + static generate(timestamp?: number | Date | undefined): string; + /** + * A deconstructed snowflake. + * @typedef {Object} DeconstructedSnowflake + * @property {number} timestamp Timestamp the snowflake was created + * @property {Date} date Date the snowflake was created + * @property {number} workerID Worker ID in the snowflake + * @property {number} processID Process ID in the snowflake + * @property {number} increment Increment in the snowflake + * @property {string} binary Binary representation of the snowflake + */ + /** + * Deconstructs a Discord snowflake. + * @param {Snowflake} snowflake Snowflake to deconstruct + * @returns {DeconstructedSnowflake} Deconstructed snowflake + */ + static deconstruct(snowflake: string): { + /** + * Timestamp the snowflake was created + */ + timestamp: number; + /** + * Date the snowflake was created + */ + date: Date; + /** + * Worker ID in the snowflake + */ + workerID: number; + /** + * Process ID in the snowflake + */ + processID: number; + /** + * Increment in the snowflake + */ + increment: number; + /** + * Binary representation of the snowflake + */ + binary: string; + }; + /** + * Discord's epoch value (2015-01-01T00:00:00.000Z). + * @type {number} + * @readonly + */ + static readonly get EPOCH(): number; +} diff --git a/dist/Snowflake.js b/dist/Snowflake.js new file mode 100644 index 00000000..4a50845e --- /dev/null +++ b/dist/Snowflake.js @@ -0,0 +1,131 @@ +// @ts-nocheck +// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js +"use strict"; +// Discord epoch (2015-01-01T00:00:00.000Z) +const EPOCH = 1420070400000; +let INCREMENT = 0; +/** + * A container for useful snowflake-related methods. + */ +class SnowflakeUtil { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + /** + * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z + * ``` + * If we have a snowflake '266241948824764416' we can represent it as binary: + * + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of ms since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ + /** + * Transforms a snowflake from a decimal string to a bit string. + * @param {Snowflake} num Snowflake to be transformed + * @returns {string} + * @private + */ + static idToBinary(num) { + let bin = ""; + let high = parseInt(num.slice(0, -10)) || 0; + let low = parseInt(num.slice(-10)); + while (low > 0 || high > 0) { + bin = String(low & 1) + bin; + low = Math.floor(low / 2); + if (high > 0) { + low += 5000000000 * (high % 2); + high = Math.floor(high / 2); + } + } + return bin; + } + /** + * Transforms a snowflake from a bit string to a decimal string. + * @param {string} num Bit string to be transformed + * @returns {Snowflake} + * @private + */ + static binaryToID(num) { + let dec = ""; + while (num.length > 50) { + const high = parseInt(num.slice(0, -32), 2); + const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); + dec = (low % 10).toString() + dec; + num = + Math.floor(high / 10).toString(2) + + Math.floor(low / 10) + .toString(2) + .padStart(32, "0"); + } + num = parseInt(num, 2); + while (num > 0) { + dec = (num % 10).toString() + dec; + num = Math.floor(num / 10); + } + return dec; + } + /** + * Generates a Discord snowflake. + * This hardcodes the worker ID as 1 and the process ID as 0. + * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate + * @returns {Snowflake} The generated snowflake + */ + static generate(timestamp = Date.now()) { + if (timestamp instanceof Date) + timestamp = timestamp.getTime(); + if (typeof timestamp !== "number" || isNaN(timestamp)) { + throw new TypeError(`"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})`); + } + if (INCREMENT >= 4095) + INCREMENT = 0; + const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++) + .toString(2) + .padStart(12, "0")}`; + return SnowflakeUtil.binaryToID(BINARY); + } + /** + * A deconstructed snowflake. + * @typedef {Object} DeconstructedSnowflake + * @property {number} timestamp Timestamp the snowflake was created + * @property {Date} date Date the snowflake was created + * @property {number} workerID Worker ID in the snowflake + * @property {number} processID Process ID in the snowflake + * @property {number} increment Increment in the snowflake + * @property {string} binary Binary representation of the snowflake + */ + /** + * Deconstructs a Discord snowflake. + * @param {Snowflake} snowflake Snowflake to deconstruct + * @returns {DeconstructedSnowflake} Deconstructed snowflake + */ + static deconstruct(snowflake) { + const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0"); + const res = { + timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, + workerID: parseInt(BINARY.substring(42, 47), 2), + processID: parseInt(BINARY.substring(47, 52), 2), + increment: parseInt(BINARY.substring(52, 64), 2), + binary: BINARY, + }; + Object.defineProperty(res, "date", { + get: function get() { + return new Date(this.timestamp); + }, + enumerable: true, + }); + return res; + } + /** + * Discord's epoch value (2015-01-01T00:00:00.000Z). + * @type {number} + * @readonly + */ + static get EPOCH() { + return EPOCH; + } +} +module.exports = SnowflakeUtil; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU25vd2ZsYWtlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1Nub3dmbGFrZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjO0FBRWQsb0VBQW9FO0FBQ3BFLFlBQVksQ0FBQztBQUViLDJDQUEyQztBQUMzQyxNQUFNLEtBQUssR0FBRyxhQUFhLENBQUM7QUFDNUIsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFDO0FBRWxCOztHQUVHO0FBQ0gsTUFBTSxhQUFhO0lBQ2xCO1FBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxpQ0FBaUMsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBRUg7Ozs7O09BS0c7SUFDSCxNQUFNLENBQUMsVUFBVSxDQUFDLEdBQUc7UUFDcEIsSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDO1FBQ2IsSUFBSSxJQUFJLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUMsSUFBSSxHQUFHLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ25DLE9BQU8sR0FBRyxHQUFHLENBQUMsSUFBSSxJQUFJLEdBQUcsQ0FBQyxFQUFFO1lBQzNCLEdBQUcsR0FBRyxNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQztZQUM1QixHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDMUIsSUFBSSxJQUFJLEdBQUcsQ0FBQyxFQUFFO2dCQUNiLEdBQUcsSUFBSSxVQUFVLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQy9CLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElB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 \ No newline at end of file diff --git a/dist/Util.d.ts b/dist/Util.d.ts new file mode 100644 index 00000000..c0a33362 --- /dev/null +++ b/dist/Util.d.ts @@ -0,0 +1,8 @@ +import "missing-native-js-functions"; +export interface traverseDirectoryOptions { + dirname: string; + filter?: RegExp; + excludeDirs?: RegExp; + recursive?: boolean; +} +export declare function traverseDirectory(options: traverseDirectoryOptions, action: (path: string) => T): Promise; diff --git a/dist/Util.js b/dist/Util.js new file mode 100644 index 00000000..45421484 --- /dev/null +++ b/dist/Util.js @@ -0,0 +1,45 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.traverseDirectory = void 0; +const promises_1 = __importDefault(require("fs/promises")); +require("missing-native-js-functions"); +const DEFAULT_EXCLUDE_DIR = /^\./; +const DEFAULT_FILTER = /^([^\.].*)\.js$/; +function traverseDirectory(options, action) { + return __awaiter(this, void 0, void 0, function* () { + if (!options.filter) + options.filter = DEFAULT_FILTER; + if (!options.excludeDirs) + options.excludeDirs = DEFAULT_EXCLUDE_DIR; + const routes = yield promises_1.default.readdir(options.dirname); + const promises = routes.map((file) => __awaiter(this, void 0, void 0, function* () { + const path = options.dirname + file; + const stat = yield promises_1.default.lstat(path); + if (path.match(options.excludeDirs)) + return; + if (stat.isFile() && path.match(options.filter)) { + return action(path); + } + else if (options.recursive && stat.isDirectory()) { + return traverseDirectory(Object.assign(Object.assign({}, options), { dirname: path + "/" }), action); + } + })); + const result = yield Promise.all(promises); + const t = result.flat(); + return t.filter((x) => x != undefined); + }); +} +exports.traverseDirectory = traverseDirectory; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9VdGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDJEQUE2QjtBQUM3Qix1Q0FBcUM7QUFTckMsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLENBQUM7QUFDbEMsTUFBTSxjQUFjLEdBQUcsaUJBQWlCLENBQUM7QUFFekMsU0FBc0IsaUJBQWlCLENBQ3RDLE9BQWlDLEVBQ2pDLE1BQTJCOztRQUUzQixJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU07WUFBRSxPQUFPLENBQUMsTUFBTSxHQUFHLGNBQWMsQ0FBQztRQUNyRCxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVc7WUFBRSxPQUFPLENBQUMsV0FBVyxHQUFHLG1CQUFtQixDQUFDO1FBRXBFLE1BQU0sTUFBTSxHQUFHLE1BQU0sa0JBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2pELE1BQU0sUUFBUSxHQUFtQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQU8sSUFBSSxFQUFFLEVBQUU7WUFDMUUsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7WUFDcEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxrQkFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNsQyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQVMsT0FBTyxDQUFDLFdBQVcsQ0FBQztnQkFBRSxPQUFPO1lBRXBELElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQVMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFO2dCQUN4RCxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNwQjtpQkFBTSxJQUFJLE9BQU8sQ0FBQyxTQUFTLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxFQUFFO2dCQUNuRCxPQUFPLGlCQUFpQixpQ0FBTSxPQUFPLEtBQUUsT0FBTyxFQUFFLElBQUksR0FBRyxHQUFHLEtBQUksTUFBTSxDQUFDLENBQUM7YUFDdEU7UUFDRixDQUFDLENBQUEsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRTNDLE1BQU0sQ0FBQyxHQUFzQixNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFFM0MsT0FBWSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQUksU0FBUyxDQUFDLENBQUM7SUFDN0MsQ0FBQztDQUFBO0FBeEJELDhDQXdCQyJ9 \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..792fcefd --- /dev/null +++ b/dist/index.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const Server_1 = require("./Server"); +const server = new Server_1.Server(); +server + .init() + .then(() => { + console.log("[Server] started on :" + server.options.port); +}) + .catch((e) => console.error("[Server] Error starting: ", e)); +//// server +//// .destroy() +//// .then(() => console.log("[Server] closed.")) +//// .catch((e) => console.log("[Server] Error closing: ", e)); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxQ0FBa0M7QUFFbEMsTUFBTSxNQUFNLEdBQUcsSUFBSSxlQUFNLEVBQUUsQ0FBQztBQUM1QixNQUFNO0tBQ0osSUFBSSxFQUFFO0tBQ04sSUFBSSxDQUFDLEdBQUcsRUFBRTtJQUNWLE9BQU8sQ0FBQyxHQUFHLENBQUMsdUJBQXVCLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUM1RCxDQUFDLENBQUM7S0FDRCxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsMkJBQTJCLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUU5RCxXQUFXO0FBQ1gsZ0JBQWdCO0FBQ2hCLGtEQUFrRDtBQUNsRCwrREFBK0QifQ== \ No newline at end of file diff --git a/dist/routes/attachments.d.ts b/dist/routes/attachments.d.ts new file mode 100644 index 00000000..ae2ab413 --- /dev/null +++ b/dist/routes/attachments.d.ts @@ -0,0 +1,2 @@ +declare const router: import("express-serve-static-core").Router; +export default router; diff --git a/dist/routes/attachments.js b/dist/routes/attachments.js new file mode 100644 index 00000000..4b639448 --- /dev/null +++ b/dist/routes/attachments.js @@ -0,0 +1,48 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = require("express"); +const multer_1 = __importDefault(require("multer")); +const Snowflake_1 = __importDefault(require("../Snowflake")); +const multer_ = multer_1.default(); +const router = express_1.Router(); +router.post("/:filename", multer_.single("attachment"), (req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { buffer, mimetype } = req.file; + const { filename } = req.params; + const { db } = req.server; + const File = { + filename, + file: buffer.toString("base64"), + id: Snowflake_1.default.generate(), + type: mimetype, + }; + if (!(yield db.data.attachments.push(File))) + throw new Error("Error uploading file"); + return res.status(201).send({ success: true, message: "attachment uploaded", id: File.id, filename }); +})); +router.get("/:hash/:filename", (req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { db } = req.server; + const { hash, filename } = req.params; + const File = yield db.data.attachments({ id: hash, filename: filename }).get(); + res.set("Content-Type", File.type); + return res.send(Buffer.from(File.file, "base64")); +})); +router.delete("/:hash/:filename", (req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { hash, filename } = req.params; + const { db } = req.server; + yield db.data.attachments({ id: hash, filename: filename }).delete(); + return res.send({ success: true, message: "attachment deleted" }); +})); +exports.default = router; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0YWNobWVudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcm91dGVzL2F0dGFjaG1lbnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7O0FBQUEscUNBQWlDO0FBQ2pDLG9EQUE0QjtBQUM1Qiw2REFBcUM7QUFFckMsTUFBTSxPQUFPLEdBQUcsZ0JBQU0sRUFBRSxDQUFDO0FBQ3pCLE1BQU0sTUFBTSxHQUFHLGdCQUFNLEVBQUUsQ0FBQztBQVN4QixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQU8sR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFO0lBQzFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQztJQUN0QyxNQUFNLEVBQUUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUNoQyxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUUxQixNQUFNLElBQUksR0FBZTtRQUN4QixRQUFRO1FBQ1IsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1FBQy9CLEVBQUUsRUFBRSxtQkFBUyxDQUFDLFFBQVEsRUFBRTtRQUN4QixJQUFJLEVBQUUsUUFBUTtLQUNkLENBQUM7SUFFRixJQUFJLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztJQUVyRixPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsRUFBRSxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztBQUN2RyxDQUFDLENBQUEsQ0FBQyxDQUFDO0FBRUgsTUFBTSxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsRUFBRSxDQUFPLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRTtJQUNqRCxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUMxQixNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFFdEMsTUFBTSxJQUFJLEdBQWUsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUM7SUFFM0YsR0FBRyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25DLE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUNuRCxDQUFDLENBQUEsQ0FBQyxDQUFDO0FBRUgsTUFBTSxDQUFDLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxDQUFPLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRTtJQUNwRCxNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFDdEMsTUFBTSxFQUFFLEVBQUUsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFFMUIsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDckUsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsQ0FBQyxDQUFDO0FBQ25FLENBQUMsQ0FBQSxDQUFDLENBQUM7QUFFSCxrQkFBZSxNQUFNLENBQUMifQ== \ No newline at end of file diff --git a/dist/routes/external.d.ts b/dist/routes/external.d.ts new file mode 100644 index 00000000..ae2ab413 --- /dev/null +++ b/dist/routes/external.d.ts @@ -0,0 +1,2 @@ +declare const router: import("express-serve-static-core").Router; +export default router; diff --git a/dist/routes/external.js b/dist/routes/external.js new file mode 100644 index 00000000..751388f1 --- /dev/null +++ b/dist/routes/external.js @@ -0,0 +1,75 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const body_parser_1 = __importDefault(require("body-parser")); +const express_1 = require("express"); +const node_fetch_1 = __importDefault(require("node-fetch")); +const cheerio_1 = __importDefault(require("cheerio")); +const btoa_1 = __importDefault(require("btoa")); +const url_1 = require("url"); +const router = express_1.Router(); +const DEFAULT_FETCH_OPTIONS = { + redirect: "follow", + follow: 1, + headers: { + "user-agent": "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)", + }, + size: 1024 * 1024 * 8, + compress: true, + method: "GET", +}; +router.post("/", body_parser_1.default.json(), (req, res) => __awaiter(void 0, void 0, void 0, function* () { + if (!req.body) + throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); + const { db } = req.server; + const { url } = req.body; + const ID = btoa_1.default(url); + const cache = yield db.data.crawler({ id: ID }).get(); + if (cache) + return res.send(cache); + try { + const request = yield node_fetch_1.default(url, DEFAULT_FETCH_OPTIONS); + const text = yield request.text(); + const ツ = cheerio_1.default.load(text); + const ogTitle = ツ('meta[property="og:title"]').attr("content"); + const ogDescription = ツ('meta[property="og:description"]').attr("content"); + const ogImage = ツ('meta[property="og:image"]').attr("content"); + const ogUrl = ツ('meta[property="og:url"]').attr("content"); + const ogType = ツ('meta[property="og:type"]').attr("content"); + const filename = new url_1.URL(url).host.split(".")[0]; + const ImageResponse = yield node_fetch_1.default(ogImage, DEFAULT_FETCH_OPTIONS); + const ImageType = ImageResponse.headers.get("content-type"); + const ImageExtension = ImageType === null || ImageType === void 0 ? void 0 : ImageType.split("/")[1]; + const ImageResponseBuffer = (yield ImageResponse.buffer()).toString("base64"); + const cachedImage = `/external/${ID}/${filename}.${ImageExtension}`; + yield db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); + const new_cache_entry = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; + yield db.data.crawler.push(new_cache_entry); + res.send(new_cache_entry); + } + catch (error) { + console.log(error); + throw new Error("Couldn't fetch website"); + } +})); +router.get("/:id/:filename", (req, res) => __awaiter(void 0, void 0, void 0, function* () { + const { db } = req.server; + const { id, filename } = req.params; + const { image, type } = yield db.data.externals({ id: id }).get(); + const imageBuffer = Buffer.from(image, "base64"); + res.set("Content-Type", type); + res.send(imageBuffer); +})); +exports.default = router; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXh0ZXJuYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcm91dGVzL2V4dGVybmFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7O0FBQUEsOERBQXFDO0FBQ3JDLHFDQUFpQztBQUNqQyw0REFBK0I7QUFDL0Isc0RBQThCO0FBQzlCLGdEQUF3QjtBQUN4Qiw2QkFBMEI7QUFFMUIsTUFBTSxNQUFNLEdBQUcsZ0JBQU0sRUFBRSxDQUFDO0FBV3hCLE1BQU0scUJBQXFCLEdBQVE7SUFDbEMsUUFBUSxFQUFFLFFBQVE7SUFDbEIsTUFBTSxFQUFFLENBQUM7SUFDVCxPQUFPLEVBQUU7UUFDUixZQUFZLEVBQUUsbUVBQW1FO0tBQ2pGO0lBQ0QsSUFBSSxFQUFFLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUNyQixRQUFRLEVBQUUsSUFBSTtJQUNkLE1BQU0sRUFBRSxLQUFLO0NBQ2IsQ0FBQztBQUVGLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLHFCQUFVLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBTyxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUU7SUFDdEQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBRWhHLE1BQU0sRUFBRSxFQUFFLEVBQUUsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDO0lBQzFCLE1BQU0sRUFBRSxHQUFHLEVBQUUsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDO0lBRXpCLE1BQU0sRUFBRSxHQUFHLGNBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUVyQixNQUFNLEtBQUssR0FBRyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDdEQsSUFBSSxLQUFLO1FBQUUsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBRWxDLElBQUk7UUFDSCxNQUFNLE9BQU8sR0FBRyxNQUFNLG9CQUFLLENBQUMsR0FBRyxFQUFFLHFCQUFxQixDQUFDLENBQUM7UUFFeEQsTUFBTSxJQUFJLEdBQUcsTUFBTSxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDbEMsTUFBTSxDQUFDLEdBQVEsaUJBQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFbEMsTUFBTSxPQUFPLEdBQUcsQ0FBQyxDQUFDLDJCQUEyQixDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQy9ELE1BQU0sYUFBYSxHQUFHLENBQUMsQ0FBQyxpQ0FBaUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMzRSxNQUFNLE9BQU8sR0FBRyxDQUFDLENBQUMsMkJBQTJCLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDL0QsTUFBTSxLQUFLLEdBQUcsQ0FBQyxDQUFDLHlCQUF5QixDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzNELE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQywwQkFBMEIsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUU3RCxNQUFNLFFBQVEsR0FBRyxJQUFJLFNBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRWpELE1BQU0sYUFBYSxHQUFHLE1BQU0sb0JBQUssQ0FBQyxPQUFPLEVBQUUscUJBQXFCLENBQUMsQ0FBQztRQUNsRSxNQUFNLFNBQVMsR0FBRyxhQUFhLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUM1RCxNQUFNLGNBQWMsR0FBRyxTQUFTLGFBQVQsU0FBUyx1QkFBVCxTQUFTLENBQUUsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNoRCxNQUFNLG1CQUFtQixHQUFHLENBQUMsTUFBTSxhQUFhLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDOUUsTUFBTSxXQUFXLEdBQUcsYUFBYSxFQUFFLElBQUksUUFBUSxJQUFJLGNBQWMsRUFBRSxDQUFDO1FBRXBFLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxFQUFFLG1CQUFtQixFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUM7UUFFdEYsTUFBTSxlQUFlLEdBQVksRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztRQUNoRyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUU1QyxHQUFHLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO0tBQzFCO0lBQUMsT0FBTyxLQUFLLEVBQUU7UUFDZixPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRW5CLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztLQUMxQztBQUNGLENBQUMsQ0FBQSxDQUFDLENBQUM7QUFFSCxNQUFNLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLENBQU8sR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFO0lBQy9DLE1BQU0sRUFBRSxFQUFFLEVBQUUsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDO0lBQzFCLE1BQU0sRUFBRSxFQUFFLEVBQUUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUNwQyxNQUFNLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUNsRSxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsQ0FBQztJQUVqRCxHQUFHLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUM5QixHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3ZCLENBQUMsQ0FBQSxDQUFDLENBQUM7QUFFSCxrQkFBZSxNQUFNLENBQUMifQ== \ No newline at end of file From 35655773d7ea9b3c0319e0376c5e6c2b481732e5 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 4 Jan 2021 16:10:12 +0100 Subject: [PATCH 14/53] :sparkles: update to use new Lambert-server --- package-lock.json | Bin 59731 -> 118860 bytes package.json | 1 + src/Server.ts | 85 ++++++++------------------------------ src/routes/attachments.ts | 6 +-- src/routes/external.ts | 4 +- 5 files changed, 23 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b5a53109bd9137b7bc58ccdb5c55e70f9f32443..c9c7a98d0b6f7c1f670d3baa9cf19d5b6295bc0b 100644 GIT binary patch literal 118860 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 XuTfv4qD!a~eCtgJ;ujeD|NQz7Ls1qd literal 59731 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�lso&`^$KxK! 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%0Dl#VAfYnU0=>G&qzA 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 diff --git a/package.json b/package.json index 03662356..d1f88c56 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "express": "^4.17.1", "express-async-errors": "^3.1.1", "lambert-db": "^1.0.5", + "lambert-server": "^1.0.3", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", "node-fetch": "^2.6.1", diff --git a/src/Server.ts b/src/Server.ts index 7d93c444..5e96c602 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,93 +1,42 @@ -import express, { Application, Router, Request, Response, NextFunction } from "express"; import { MongoDatabase, Database } from "lambert-db"; -import { Server as HTTPServer } from "http"; -import { traverseDirectory } from "./Util"; -import bodyParser from "body-parser"; -import "express-async-errors"; +import { Server, ServerOptions } from "lambert-server"; const log = console.log; console.log = (content) => { log(`[${new Date().toTimeString().split(" ")[0]}]`, content); }; -export type ServerOptions = { - db: string; - port: number; - host: string; -}; - declare global { namespace Express { interface Request { - server: Server; + cdn: CDNServer; } } } -export class Server { - app: Application; - http: HTTPServer; +export interface CDNServerOptions extends ServerOptions { + db: string; +} + +export class CDNServer extends Server { db: Database; - routes: Router[]; - options: ServerOptions; + public options: CDNServerOptions; + + constructor(options: CDNServerOptions) { + super(options); - constructor(options: Partial = { port: 3000, host: "0.0.0.0" }) { - this.app = express(); this.db = new MongoDatabase(options?.db); - this.options = options as ServerOptions; } - async init() { + async start() { + console.log("[Database] connecting ..."); await this.db.init(); - - console.log("[Database] connected..."); - await new Promise((res, rej) => { - this.http = this.app.listen(this.options.port, this.options.host, () => res(null)); - }); - this.routes = await this.registerRoutes(__dirname + "/routes/"); + console.log("[Database] connected"); + return super.start(); } - async registerRoutes(root: string) { - this.app.use((req, res, next) => { - req.server = this; - next(); - }); - const routes = await traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); - this.app.use((err: string | Error, req: Request, res: Response, next: NextFunction) => { - res.status(400).send(err); - next(err); - }); - return routes; - } - - 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); - console.log(`[Routes] ${path} registerd`); - - return router; - } catch (error) { - console.error(new Error(`[Server] ¯\\_(ツ)_/¯ Failed to register route ${path}: ${error}`)); - } - } - - async destroy() { + async stop() { await this.db.destroy(); - await new Promise((res, rej) => { - this.http.close((err) => { - if (err) return rej(err); - return res(""); - }); - }); + return super.stop(); } } diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 7d09e402..9f016174 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -15,7 +15,7 @@ type Attachment = { router.post("/:filename", multer_.single("attachment"), async (req, res) => { const { buffer, mimetype } = req.file; const { filename } = req.params; - const { db } = req.server; + const { db } = req.cdn; const File: Attachment = { filename, @@ -30,7 +30,7 @@ router.post("/:filename", multer_.single("attachment"), async (req, res) => { }); router.get("/:hash/:filename", async (req, res) => { - const { db } = req.server; + const { db } = req.cdn; const { hash, filename } = req.params; const File: Attachment = await db.data.attachments({ id: hash, filename: filename }).get(); @@ -41,7 +41,7 @@ router.get("/:hash/:filename", async (req, res) => { router.delete("/:hash/:filename", async (req, res) => { const { hash, filename } = req.params; - const { db } = req.server; + const { db } = req.cdn; await db.data.attachments({ id: hash, filename: filename }).delete(); return res.send({ success: true, message: "attachment deleted" }); diff --git a/src/routes/external.ts b/src/routes/external.ts index be680003..f75f6a66 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -30,7 +30,7 @@ const DEFAULT_FETCH_OPTIONS: any = { router.post("/", bodyParser.json(), async (req, res) => { if (!req.body) throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); - const { db } = req.server; + const { db } = req.cdn; const { url } = req.body; const ID = btoa(url); @@ -72,7 +72,7 @@ router.post("/", bodyParser.json(), async (req, res) => { }); router.get("/:id/:filename", async (req, res) => { - const { db } = req.server; + const { db } = req.cdn; const { id, filename } = req.params; const { image, type } = await db.data.externals({ id: id }).get(); const imageBuffer = Buffer.from(image, "base64"); From d7a45bb0d625834e3e72f34cf643f700220d3659 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 9 Feb 2021 10:00:35 +0100 Subject: [PATCH 15/53] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d34bd264..6cd6186a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Discord-CDN -cdn for discord clone +# Fosscord-CDN +CDN for Fosscord ## Run localy: ``` From c32f7e2745ab4134251ace75f53a59f9ae423667 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 3 Apr 2021 15:22:10 +0200 Subject: [PATCH 16/53] :see_no_evil: add .DS_Store to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 49260768..234771f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ -node_modules/ \ No newline at end of file +node_modules/ +.DS_Store From dd87cf1dbaaa0ced1d286b3002a803c9815ddd9e Mon Sep 17 00:00:00 2001 From: xnacly Date: Fri, 23 Apr 2021 22:29:14 +0100 Subject: [PATCH 17/53] add dom to libs (compiler flag) --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 26be7b1b..1422446c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ // "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"] /* Specify library files to be included in the compilation. */, + "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'. */ From fdaa277935aa2807218c369cbb323db7f27fd8a2 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 10 May 2021 18:04:02 +0200 Subject: [PATCH 18/53] :bug: fix CDN --- package-lock.json | Bin 118860 -> 81746 bytes package.json | 15 +++++++++------ src/Server.ts | 2 +- src/index.ts | 11 +++-------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9c7a98d0b6f7c1f670d3baa9cf19d5b6295bc0b..a2bdd9283e6c4be1e7b0ad4a6353b27833faa694 100644 GIT binary patch delta 2854 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 diff --git a/package.json b/package.json index d1f88c56..489b67dd 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { - "name": "discord-cdn", + "name": "@fosscord/cdn", "version": "1.0.0", "description": "cdn for discord clone", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc -b .", + "start": "npm run build && node dist/" }, "repository": { "type": "git", @@ -23,18 +25,19 @@ "cheerio": "^1.0.0-rc.5", "express": "^4.17.1", "express-async-errors": "^3.1.1", - "lambert-db": "^1.0.5", - "lambert-server": "^1.0.3", + "lambert-db": "^1.2.3", + "lambert-server": "^1.2.1", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", "node-fetch": "^2.6.1", "uuid": "^8.3.2" }, "devDependencies": { + "@types/body-parser": "^1.19.0", "@types/btoa": "^1.2.3", - "@types/express": "^4.17.9", + "@types/express": "^4.17.11", "@types/multer": "^1.4.5", - "@types/node": "^14.14.16", + "@types/node": "^14.14.43", "@types/node-fetch": "^2.5.7", "@types/uuid": "^8.3.0" } diff --git a/src/Server.ts b/src/Server.ts index 5e96c602..3e8c9321 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -22,7 +22,7 @@ export class CDNServer extends Server { db: Database; public options: CDNServerOptions; - constructor(options: CDNServerOptions) { + constructor(options: Partial) { super(options); this.db = new MongoDatabase(options?.db); diff --git a/src/index.ts b/src/index.ts index d8025968..64fca7e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,9 @@ -import { Server } from "./Server"; +import { CDNServer } from "./Server"; -const server = new Server(); +const server = new CDNServer({ db: "" }); server - .init() + .start() .then(() => { console.log("[Server] started on :" + server.options.port); }) .catch((e) => console.error("[Server] Error starting: ", e)); - -//// server -//// .destroy() -//// .then(() => console.log("[Server] closed.")) -//// .catch((e) => console.log("[Server] Error closing: ", e)); From 8f66556f5acf149fadd2fe0ac9e03f5e6225026e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 27 May 2021 18:54:15 +0200 Subject: [PATCH 19/53] :construction: WIP rewrite --- .env.example | 1 + .gitignore | 1 + package-lock.json | Bin 81746 -> 99841 bytes package.json | 4 +- src/Server.ts | 32 +++------ src/Snowflake.js | 145 -------------------------------------- src/Util.ts | 38 ---------- src/index.ts | 10 ++- src/routes/attachments.ts | 13 ++-- src/routes/external.ts | 22 +++--- src/util/FileStorage.ts | 7 ++ src/util/Storage.ts | 14 ++++ tsconfig.json | 1 + 13 files changed, 57 insertions(+), 231 deletions(-) create mode 100644 .env.example delete mode 100644 src/Snowflake.js delete mode 100644 src/Util.ts create mode 100644 src/util/FileStorage.ts create mode 100644 src/util/Storage.ts diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..2a1176ff --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +STORAGE_LOCATION=files/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 234771f2..6054dfe8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ node_modules/ .DS_Store +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a2bdd9283e6c4be1e7b0ad4a6353b27833faa694..9582bc4059efa90927b387b7d8a6a037902c144a 100644 GIT binary patch delta 12735 zcmeHNX^b1!eaC1et>o2ZE7IzWc2|)cndZ(6heOUdabw>1@SNdM$MIYwhaAof=irq? zF%UFBP&9U?1=BQb;tiX%qJix!k4X zwGzjc><4-=rW8M}jbIn9otcSbw$8u}-H#*B#TZXFiMb1>)qB50)*u*!H zjR)^J2>xw6Vh%OYfIF(vP*p>8vzDo9s%FWoDNa`-n<)5urRB27D(;@7n-(h@TkG+g zmfK(1bG+%n=NquCnL2M`0;t)o>8du}z_+?$SR3*GFY37t6?D~N>f4Ua3~%A@@(t>YLviU;#9o`F3?E{Ty` zF_&twP1dZ{Jn4+plp0o~)DAV$%&4wgeYn=6GE_UI7Ds&>%3W~I+5Url4^Ez0DebXu z$Zv%Zn;lO1ve!DR5HfylAGFS3{(L48AaQRAQ(W<4+bB|Lc~CRDQKFtnT6obb40AzM z@HGm9m}ykxJlUenG!cvtMN#VXNYe6?V!D#RxJE+_DaAZoALO?fxX%{$zVj`+sn~u3 zIbo~s+BaUpAlT8BXSY=%P};8JZs!Kt-~aSFllJ*DfkXr=N7RyO`dL40XoYIq<@d=A z&F7B>a=rvcR7r{tq3ygi>UR~ZU1F0XRj6|WN+A6~yq58HO&?+T20qQS!UcBD|Dipq zVL!d^%8m#Ncl%@ElS|0v_F<#jj4rH^jYSD=ns6i23&2rrS3cd!;Y^M8MvNre@DB1u zD;;2oP^1GFQ?>9=3VGTY4?^@4dd%wh1UBq%dGT!5z!9!Y#p>N!wO~b>O=3_({b4^F znO}f8|C8k-?|~q{ePeZY{+~X!*Z#<#9|9xmKUin_1fWg9b$_wQ^_YUlk;Ss+vXDRZzf)B)&lnT zW2ZlMWO))l)>z)t>N%ES2RU>F7(37j_#arnliWq7>X9*$ESYGaVbNGHSICi3#Vb;- ztnMnBbgUerHBN~RD@B!{YGl!CwOoOiM{5~4tEF7EOtxRmj!+ltVp6?S+#j-pN@IF} z-a2cTZb1|^x7Y=~Xll3^Fx+j=5{uOb3$I=gghILM|uC$uWZyUMVNi`-kG0q{D-qeLo%Bdx3k5R ztEOpc#zQbXo807A)egAEs$!_R0gIZ-iMlvyONxE((z(g!SHFAkzNtWN9xO3g#dMoe z2Uyf;`*9w&U;5fI^73iNlsKzi2SII@$?=*Ox_H)=ANEyIoQ1j-MC zZF~LnsmX7wfA8XfxnH)?55`vKFJSZeqZ{~Y$1-U%;vbpSQ6-)bhdtg;xtaw`mHg2J zSBI%Yi^-AAPQb*5naW7dBYqED*IKdkz!Mu#gV=zUamJ5jBHijpQnMMAX~r^1oCg8y zkYu=3t~EOk`>R)66pGwd^_IS?k{5MsNQ$6~K;2EtaFo0sNCUzQh}tKB+kEP+p8IA4 zGdZ0A1l&*F2mH4jlt_VSX+5%7N856)Mb)DLs~m2n(*05*nS_~vsh0YQg0HF627Dx& z57inTHIc?L`zxpTtdquSl-Ulm+c>9&L8CkgLNBH!(CVP4r|)$ z{nLP-Pk#Z0#apIKQ3uTJGU`U1*lFi~Z;esdhKv=eI!E<%?5BgU zr)Q3QLyD|vsBGhcZYIlBKb zkU{`T&K<2serM0}R+zS-@Tz_K40>WaZaa+5W4N6^bBS>^QMsK?HEWqtwWT&tCZZ*& zn4hX8%e=*V!r?aDHM+%8p%BVeq;yNnqXG(Bf=6i;n=T-7?|^9>a$Atb_@LuOmkJ(iyc64^X0VlJE@%nwxy8YbQy^NrD!!eBK_|aex zNfy*>h0nH3K|_@_m&Be2J?$J*i-^DLZ-OImz3w~giImfr`GHgHo*^MdRREB_a zq=Hpim1d%*pmMs|jFooJ&bOG$T_`xhn)*FFG*m#-Ma*#_B8_?zX(Gs z_MUxfj4vvWEUuKU#jvD@g()T!ki|%|HR|H(pb;OkA(|XkD?L7K^i+n8C%dqL4=M>C zT&d)I=Ez?l2Z5{^SBhEKte8=(Tqw!YG-avPXPpAg{^GaMgW%b@&S2!n-@Zh5rE~=z zG$WB{iVh@wQ7M?=L<8%SfzoK$#>@VAZ=|tsFZ4D%ZM<(9%{C_gqfvlqrN|^ z6~ko)m~B`WeWHN&VNLLocs8uXYt^FAE|d%wi34#~fWD@e4IHTbV*2y~OOti4nr?agMUy(q+NWF|u{E2xo7x!B5Q&_q9Gq~e(hQRt`R zaNpVd-tBGX4#3WTeia-B$a0=V+%KC2?qQMA{39?7gt_)d>u2lHWS2H2uD}=gffn*M z%2`)1Sx4!N;m|v}z2nT4*nw*&$J{$1bVpyaiA5^25x2wCfBf*m8MIihn>1q$l@TaN zD~!j|y^@bfF;V-*qo?oLq5yo+w5TLMfSuhLazfL>8({i{Z(KN!y2C=Y5^3h*gEHoA zBn6U=3==MdXJy}1O%pHsg@Yg{vOGL~W*Imz!hRxufic3ZWG~^z%B?Jg_E?SQhQTlv zP)V4fuu`g8CE$F8jN~}O<4LihqMpa~ii+22g4b)7lUQHIqOzh#Yp&FgHmSJJh1PPj z1nR5p;2BjjB~@W%_g49_Wll{BmBwJ^Wx;-2Itg0;=u9z&e~gM+oBF)hw3=-`RoHb-%iV1fphb+ zB!bEV5Y~rNz899Sy?i;K=6LvpMXNiD429LS;lkV!n6qzOI%~fmUjWVwB)m?~0(5a^ zi7e_XG2Lo2o)>CVfhd$D112P?lpDH~WUh3FlnbW2uq7Q8{%?8v=brtR%H-xnn>?sc=1qlZdiH{R;Lu_- z&bjZP1%ZwOM2f27IV@86Y1LPlev3@XL@G*^4D3{H%sa`07(giLF-b_6wruz>+JQHxk zxu5pho|7l%VmM0lZVuMo_r5iJb1)Zbl2S8EAR9;Quj}_tV?F3W=!IT0ESnXEO8cS% z!86K`VQ()L9JClFPo*iTCAa*RTA_z1IikWwLaO7Vf=erRa-lMn>Icm3Q1vEzV!q>5 z>S5d^&I*Dn*8TP;j8ir@M8?KZ==k{geb5@sw6dvqyg?J*zSQz3Xtg$q7HGtu2n+}f9j;~&8nZy9$E?A9UsN&6C0U_3R=lc{=Gv6{kTt{`003_>pBR6H<>;Q5fu zMRO?La^-;Zt`eCW$jM4Dn@y|5fY9hidquB_u$h9lr-?$Q&Bg0YM472$fBpQ#^}&DK zzpPoxkXMDN4_%AxwSWEN@bRrNhp+jg{nW=VF_nT*XbE*!3-8FTA?%B#;<*~9M`1$q zR2wXk%$Y*Bl&E?tBCa-*xiD$4;bbzM>uIEmDh4&3>4`m|QX6tg%ft7GY(zkI>b1Qe zesTYy#omik7oDpF0d)q$_m*nBj5l>U9HyIn*3}D8LyNPz^;)^q6KNO@SDBa*j`<6zG~A2WPk!cp z`(Hk=Ho5ZT->vL*T!E8s*{)9>n!SKji*YpuLL{3Bc#^I_!c}LCP*`G+9SsxfU*1C?wq0f8DMe^0#QyilW8eGnx7zzY`<*4w zUbCP6A16};sYU`hKI^vu*=7$wF7LF_wDkK7?1tbx6au=`_o^6JUf;1Jp9}g9jAv1qUX;6dF&&fTeW}pi-#wF z`NxOelwi*|a2z@d4*c@uZ@=Wb>qyU>`ttav2SK4O?0vg^{&UMafXM=OI1MdtUd(4( zlI5-IVxylwwjE_E8wR)cLop{KaN1FcN-CL0?F$MiLB1B=-JN#wu8bO0ggD5-HMJJTI6nW z>3`q$I^ZL|+u$P$adEpHzlAcsdX&U_8Lazxc!ES?<>_*H%LS7|e;=c`IvKxsx z1oGN&6Z|%~$!=7CY4iknW%MNSazd{HM7eNV5ar>G$s;d?58N#XWmfh4LKwt8+wRSv$^etz}N^z%WIcm2Q_2!B<*eE!b# z@)r}%F8Br53H!{4pS`}a4n_C9PM!Vo9qH`%AHE)XJM@)%?rKy2`m-2x|MD;2)i2+n nuKxI&4?)-7*f#u_MWJ)2ZtcT^-x`6W?%?~czxN%`KP>$}rm0L^ delta 2383 zcmd7SYiv_h902f~y}E7^L12sxM)wdCyY;^M6cSn2u4{X5d)xK4TUFq;z3b!NwY|6P zZ95W~L}Eli@F>?uqAnzagb={{p(Yv~iHXKQ%tS*-+yv1Nen2JBkYJ*YB_csXNDPbL z&iUu$od5s)&-p#`V$&DDHyr#@`C$v_g6n}%(QF0V)T1B?8jJ=b0k74qhQ0M3c&S;V z2AXy^!?p9jK)P=Q)OBjqj#VU3r`xuJ`o*cFQ!p7-P zegq}s6RBiIE=d7diVoYQA#d7PRVx{)@fpd!bkB)a+Q7Mpbrpt$^pUs``=aX;4p0 zpVUHR0Z}e_K_fi4Zx!6NpkpL|v=l@Gd@d19Q+g&AaGH2W!Bge!VkC_Ykw#}6Ph(lh zB{6Qo>&Gz3JRGYOcyUrBJYL*4Vqqi3L_8k1mzYv!kVc(B!2}Ogbjno^u(wN`SV~LW zj$FQ&&ShDl=P_N+cFcf6`lz&}_^UU9O5)%v!B>o*&oCMhi+Z6Ru=6|9J*qrQKLs)sm&C}a>k z1mEq`G1;8tOlJko#tz3(6BW0!vP_r#Hc3SNp}f_` zyTTz+mQ9k6q^4|Urzss7$%p;HjN6D|GJNUCX84A(8MYj5hjm&+85jj?ls$bw$Hd(e zG1?faQsZO#QJxLpRJQ2Q594Dok+qL7;}M?7=y@^Wm@3kd98VOJ)k47^txzLWaVi$Z z$~Ff(D#%WbMcui0Ny-_qK_i@bO{d;CJkzSq9l6+~etLYqNqeiE@XQ`p@0~qH29P^L z@bYXo{JFJ(5mOXWHCec5?35i;V7mnqWB5{NwE93?rV;G*z^+pSMJ{ir?f4KjQr znkys4s*v-Eo~qBxkBtgMuv(?8q+oI&1rKe#nc|}oSI7x6H!vaPCYN3rzSte~ul^Od zNtu(D;wHHAtgiL80@5T&m&k4dLwD-5an0!966o{Q|8|;at+2bXN2^ zCXsSQSPY&S?^9>hYpuWnSM}q{LK!@;IGj1w%j86dg{D0ElHZ&O+nj+R0VSni8S|G5 zX}XXe4g^huK>|&2LA}u-STc5d%t2;-awN_2I9i;F3ZqVJ#1ga<=;#=NWsvdlkOhi6 zI@Gv!(gCQS&MBiw_Z&uj`-(s6<#Sj?N z!nq??YNvJpcVq2a4y^g#akP|o|LvOG-6I30fHYXGy~WKvg0%5&AjNAx3gCm5e{dAO XFB~OnTm?L$QFcu)estp=aIXGO7Zp6R diff --git a/package.json b/package.json index 489b67dd..69707a51 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ }, "homepage": "https://github.com/discord-open-source/discord-cdn#readme", "dependencies": { + "@fosscord/server-util": "^1.3.3", "body-parser": "^1.19.0", "btoa": "^1.2.1", "cheerio": "^1.0.0-rc.5", + "dotenv": "^10.0.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", "lambert-db": "^1.2.3", @@ -37,7 +39,7 @@ "@types/btoa": "^1.2.3", "@types/express": "^4.17.11", "@types/multer": "^1.4.5", - "@types/node": "^14.14.43", + "@types/node": "^14.17.0", "@types/node-fetch": "^2.5.7", "@types/uuid": "^8.3.0" } diff --git a/src/Server.ts b/src/Server.ts index 3e8c9321..3ad794be 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,42 +1,28 @@ -import { MongoDatabase, Database } from "lambert-db"; import { Server, ServerOptions } from "lambert-server"; +import { Config, db } from "@fosscord/server-util"; +import path from "path"; -const log = console.log; -console.log = (content) => { - log(`[${new Date().toTimeString().split(" ")[0]}]`, content); -}; - -declare global { - namespace Express { - interface Request { - cdn: CDNServer; - } - } -} - -export interface CDNServerOptions extends ServerOptions { - db: string; -} +export interface CDNServerOptions extends ServerOptions {} export class CDNServer extends Server { - db: Database; public options: CDNServerOptions; - constructor(options: Partial) { + constructor(options?: Partial) { super(options); - - this.db = new MongoDatabase(options?.db); } async start() { console.log("[Database] connecting ..."); - await this.db.init(); + // @ts-ignore + await (db as Promise); + await Config.init(); console.log("[Database] connected"); + + await this.registerRoutes(path.join(__dirname, "routes")); return super.start(); } async stop() { - await this.db.destroy(); return super.stop(); } } diff --git a/src/Snowflake.js b/src/Snowflake.js deleted file mode 100644 index feb5eb41..00000000 --- a/src/Snowflake.js +++ /dev/null @@ -1,145 +0,0 @@ -// @ts-nocheck - -// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js -"use strict"; - -// Discord epoch (2015-01-01T00:00:00.000Z) -const EPOCH = 1420070400000; -let INCREMENT = 0; - -/** - * A container for useful snowflake-related methods. - */ -class SnowflakeUtil { - constructor() { - throw new Error(`The ${this.constructor.name} class may not be instantiated.`); - } - - /** - * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z - * ``` - * If we have a snowflake '266241948824764416' we can represent it as binary: - * - * 64 22 17 12 0 - * 000000111011000111100001101001000101000000 00001 00000 000000000000 - * number of ms since Discord epoch worker pid increment - * ``` - * @typedef {string} Snowflake - */ - - /** - * Transforms a snowflake from a decimal string to a bit string. - * @param {Snowflake} num Snowflake to be transformed - * @returns {string} - * @private - */ - static idToBinary(num) { - let bin = ""; - let high = parseInt(num.slice(0, -10)) || 0; - let low = parseInt(num.slice(-10)); - while (low > 0 || high > 0) { - bin = String(low & 1) + bin; - low = Math.floor(low / 2); - if (high > 0) { - low += 5000000000 * (high % 2); - high = Math.floor(high / 2); - } - } - return bin; - } - - /** - * Transforms a snowflake from a bit string to a decimal string. - * @param {string} num Bit string to be transformed - * @returns {Snowflake} - * @private - */ - static binaryToID(num) { - let dec = ""; - - while (num.length > 50) { - const high = parseInt(num.slice(0, -32), 2); - const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); - - dec = (low % 10).toString() + dec; - num = - Math.floor(high / 10).toString(2) + - Math.floor(low / 10) - .toString(2) - .padStart(32, "0"); - } - - num = parseInt(num, 2); - while (num > 0) { - dec = (num % 10).toString() + dec; - num = Math.floor(num / 10); - } - - return dec; - } - - /** - * Generates a Discord snowflake. - * This hardcodes the worker ID as 1 and the process ID as 0. - * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate - * @returns {Snowflake} The generated snowflake - */ - static generate(timestamp = Date.now()) { - if (timestamp instanceof Date) timestamp = timestamp.getTime(); - if (typeof timestamp !== "number" || isNaN(timestamp)) { - throw new TypeError( - `"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})` - ); - } - if (INCREMENT >= 4095) INCREMENT = 0; - const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++) - .toString(2) - .padStart(12, "0")}`; - return SnowflakeUtil.binaryToID(BINARY); - } - - /** - * A deconstructed snowflake. - * @typedef {Object} DeconstructedSnowflake - * @property {number} timestamp Timestamp the snowflake was created - * @property {Date} date Date the snowflake was created - * @property {number} workerID Worker ID in the snowflake - * @property {number} processID Process ID in the snowflake - * @property {number} increment Increment in the snowflake - * @property {string} binary Binary representation of the snowflake - */ - - /** - * Deconstructs a Discord snowflake. - * @param {Snowflake} snowflake Snowflake to deconstruct - * @returns {DeconstructedSnowflake} Deconstructed snowflake - */ - static deconstruct(snowflake) { - const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0"); - const res = { - timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, - workerID: parseInt(BINARY.substring(42, 47), 2), - processID: parseInt(BINARY.substring(47, 52), 2), - increment: parseInt(BINARY.substring(52, 64), 2), - binary: BINARY, - }; - Object.defineProperty(res, "date", { - get: function get() { - return new Date(this.timestamp); - }, - enumerable: true, - }); - return res; - } - - /** - * Discord's epoch value (2015-01-01T00:00:00.000Z). - * @type {number} - * @readonly - */ - static get EPOCH() { - return EPOCH; - } -} - -module.exports = SnowflakeUtil; diff --git a/src/Util.ts b/src/Util.ts deleted file mode 100644 index 291372c1..00000000 --- a/src/Util.ts +++ /dev/null @@ -1,38 +0,0 @@ -import fs from "fs/promises"; -import "missing-native-js-functions"; - -export interface traverseDirectoryOptions { - dirname: string; - filter?: RegExp; - excludeDirs?: RegExp; - recursive?: boolean; -} - -const DEFAULT_EXCLUDE_DIR = /^\./; -const DEFAULT_FILTER = /^([^\.].*)\.js$/; - -export async function traverseDirectory( - options: traverseDirectoryOptions, - action: (path: string) => T -): Promise { - if (!options.filter) options.filter = DEFAULT_FILTER; - if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; - - const routes = await fs.readdir(options.dirname); - const promises = []>routes.map(async (file) => { - const path = options.dirname + file; - const stat = await fs.lstat(path); - if (path.match(options.excludeDirs)) return; - - if (stat.isFile() && path.match(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.filter((x) => x != undefined); -} diff --git a/src/index.ts b/src/index.ts index 64fca7e5..cdf88fd9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,14 @@ import { CDNServer } from "./Server"; +import dotenv from "dotenv"; +dotenv.config(); -const server = new CDNServer({ db: "" }); +if (process.env.STORAGE_LOCATION) { + if (!process.env.STORAGE_LOCATION.startsWith("/")) { + process.env.STORAGE_LOCATION = __dirname + "/../" + process.env.STORAGE_LOCATION; + } +} else process.env.STORAGE_LOCATION = __dirname + "/../files/"; + +const server = new CDNServer(); server .start() .then(() => { diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 9f016174..87368e48 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -1,6 +1,7 @@ import { Router } from "express"; import multer from "multer"; -import Snowflake from "../Snowflake"; +import { Snowflake } from "@fosscord/server-util"; +import { storage } from "../util/Storage"; const multer_ = multer(); const router = Router(); @@ -11,11 +12,11 @@ type Attachment = { id: string; type: string; }; - router.post("/:filename", multer_.single("attachment"), async (req, res) => { const { buffer, mimetype } = req.file; const { filename } = req.params; - const { db } = req.cdn; + + // storage.set(filename, ); const File: Attachment = { filename, @@ -23,14 +24,9 @@ router.post("/:filename", multer_.single("attachment"), async (req, res) => { id: Snowflake.generate(), type: mimetype, }; - - if (!(await db.data.attachments.push(File))) throw new Error("Error uploading file"); - - return res.status(201).send({ success: true, message: "attachment uploaded", id: File.id, filename }); }); router.get("/:hash/:filename", async (req, res) => { - const { db } = req.cdn; const { hash, filename } = req.params; const File: Attachment = await db.data.attachments({ id: hash, filename: filename }).get(); @@ -41,7 +37,6 @@ router.get("/:hash/:filename", async (req, res) => { router.delete("/:hash/:filename", async (req, res) => { const { hash, filename } = req.params; - const { db } = req.cdn; await db.data.attachments({ id: hash, filename: filename }).delete(); return res.send({ success: true, message: "attachment deleted" }); diff --git a/src/routes/external.ts b/src/routes/external.ts index f75f6a66..045eb7da 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -2,8 +2,7 @@ import bodyParser from "body-parser"; import { Router } from "express"; import fetch from "node-fetch"; import cheerio from "cheerio"; -import btoa from "btoa"; -import { URL } from "url"; +import crypto from "crypto"; const router = Router(); @@ -30,25 +29,21 @@ const DEFAULT_FETCH_OPTIONS: any = { router.post("/", bodyParser.json(), async (req, res) => { if (!req.body) throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); - const { db } = req.cdn; const { url } = req.body; - const ID = btoa(url); - - const cache = await db.data.crawler({ id: ID }).get(); - if (cache) return res.send(cache); + const hash = crypto.createHash("md5").update(url).digest("hex"); try { const request = await fetch(url, DEFAULT_FETCH_OPTIONS); const text = await request.text(); - const ツ: any = cheerio.load(text); + const $ = cheerio.load(text); - const ogTitle = ツ('meta[property="og:title"]').attr("content"); - const ogDescription = ツ('meta[property="og:description"]').attr("content"); - const ogImage = ツ('meta[property="og:image"]').attr("content"); - const ogUrl = ツ('meta[property="og:url"]').attr("content"); - const ogType = ツ('meta[property="og:type"]').attr("content"); + const ogTitle = $('meta[property="og:title"]').attr("content"); + const ogDescription = $('meta[property="og:description"]').attr("content"); + const ogImage = $('meta[property="og:image"]').attr("content"); + const ogUrl = $('meta[property="og:url"]').attr("content"); + const ogType = $('meta[property="og:type"]').attr("content"); const filename = new URL(url).host.split(".")[0]; @@ -72,7 +67,6 @@ router.post("/", bodyParser.json(), async (req, res) => { }); router.get("/:id/:filename", async (req, res) => { - const { db } = req.cdn; const { id, filename } = req.params; const { image, type } = await db.data.externals({ id: id }).get(); const imageBuffer = Buffer.from(image, "base64"); diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts new file mode 100644 index 00000000..01be0050 --- /dev/null +++ b/src/util/FileStorage.ts @@ -0,0 +1,7 @@ +import { Storage } from "./Storage"; + +export class FileStorage implements Storage { + async get(path: string, prefix?: string) {} + + async set(path: string, value: any) {} +} diff --git a/src/util/Storage.ts b/src/util/Storage.ts new file mode 100644 index 00000000..ad00fbb7 --- /dev/null +++ b/src/util/Storage.ts @@ -0,0 +1,14 @@ +import { FileStorage } from "./FileStorage"; + +export interface Storage { + set(hash: string, data: any, prefix?: string): Promise; + get(hash: string, prefix?: string): Promise; +} + +var storage: Storage; + +if (process.env.STORAGE_PROVIDER === "file") { + storage = new FileStorage(); +} + +export { storage }; diff --git a/tsconfig.json b/tsconfig.json index 1422446c..08e39435 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "include": ["src/**/*.ts"], "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ From 293894a4f9df62438f092398bd55959e2f23c23e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 27 May 2021 20:01:38 +0200 Subject: [PATCH 20/53] :construction: attachment --- src/routes/attachments.ts | 44 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 87368e48..fb0f0c32 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -1,35 +1,45 @@ import { Router } from "express"; import multer from "multer"; -import { Snowflake } from "@fosscord/server-util"; +import { Config, Snowflake } from "@fosscord/server-util"; import { storage } from "../util/Storage"; -const multer_ = multer(); +const multer_ = multer({ + storage: multer.memoryStorage(), + limits: { + fields: 0, + files: 1, + fileSize: 1024 * 1024 * 100, // 100 mb + }, +}); const router = Router(); -type Attachment = { - filename: string; - file: string; - id: string; - type: string; -}; -router.post("/:filename", multer_.single("attachment"), async (req, res) => { - const { buffer, mimetype } = req.file; - const { filename } = req.params; +router.post("/:channel_id", multer_.single("attachment"), async (req, res) => { + const { buffer, mimetype, stream, size, originalname, fieldname } = req.file; + const { channel_id } = req.params; + const filename = originalname.replaceAll(" ", "_").replace(/\W+/g, ""); + t; + const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; - // storage.set(filename, ); + await storage.set(originalname, buffer); - const File: Attachment = { - filename, - file: buffer.toString("base64"), - id: Snowflake.generate(), + const id = Snowflake.generate(); + + const file = { + id, type: mimetype, + content_type: mimetype, + filename: originalname, + size, + url: `${endpoint}/attachments/${channel_id}/${id}/`, }; + + return res.json(file); }); router.get("/:hash/:filename", async (req, res) => { const { hash, filename } = req.params; - const File: Attachment = await db.data.attachments({ id: hash, filename: filename }).get(); + const File = await db.data.attachments({ id: hash, filename: filename }).get(); res.set("Content-Type", File.type); return res.send(Buffer.from(File.file, "base64")); From 4e69d834578c14215dbeef929813344a70c7ca72 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Thu, 27 May 2021 20:01:50 +0200 Subject: [PATCH 21/53] :construction: file storage --- .env.example | 3 ++- package-lock.json | Bin 99841 -> 99841 bytes package.json | 2 +- src/index.ts | 2 +- src/util/FileStorage.ts | 10 ++++++++-- src/util/Storage.ts | 4 ++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 2a1176ff..0a91585c 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -STORAGE_LOCATION=files/ \ No newline at end of file +STORAGE_LOCATION=files/ +PORT=3003 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9582bc4059efa90927b387b7d8a6a037902c144a..623e59c1a7b3a906401a8f3d6c9fe35fdec09e0f 100644 GIT binary patch delta 248 zcmZqdVQcJR+i;PI(P;A}rY#(kCx+@zF1X4*c@MV)qtWD#%<^FcA(4s2W^P3RhNe!2 z{)q-j*?y)afxdo){>fnxiI!O=ZXs?Z{uVCbo|f4K1>TuS+Gc)UC21l4{yxd&6{V$- zWyujn+9_e~W~n8H!4={9-pPjMfs-31)g7MpyZpM7%!kIfmn*}S>oX5$(FbPrc# delta 242 zcmZqdVQcJR+i;PI(Qxx6rY#(k8$I+V7hGkZyoXz2@~2?M(0uJ6v!ZY_125NPQ%g&8 z^K#FMqSU~!Voz6p$H?+@=fa$nB1iqQ>;mV!Bo}kv!jRN7e@pY6+ { diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 01be0050..b4d00213 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -1,7 +1,13 @@ import { Storage } from "./Storage"; +import fs from "fs/promises"; +import { join } from "path"; export class FileStorage implements Storage { - async get(path: string, prefix?: string) {} + async get(path: string) { + return fs.readFile(join(process.env.STORAGE_LOCATION || "", path), { encoding: "binary" }); + } - async set(path: string, value: any) {} + async set(path: string, value: any) { + return fs.writeFile(join(process.env.STORAGE_LOCATION || "", path), value, { encoding: "binary" }); + } } diff --git a/src/util/Storage.ts b/src/util/Storage.ts index ad00fbb7..391afa83 100644 --- a/src/util/Storage.ts +++ b/src/util/Storage.ts @@ -1,8 +1,8 @@ import { FileStorage } from "./FileStorage"; export interface Storage { - set(hash: string, data: any, prefix?: string): Promise; - get(hash: string, prefix?: string): Promise; + set(path: string, data: any): Promise; + get(path: string): Promise; } var storage: Storage; From 9352b965d8da7bce39d475f248b7e0f9259f01d4 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 28 May 2021 12:22:19 +0200 Subject: [PATCH 22/53] Update attachments.ts --- src/routes/attachments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index fb0f0c32..730d2bb1 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -17,7 +17,7 @@ router.post("/:channel_id", multer_.single("attachment"), async (req, res) => { const { buffer, mimetype, stream, size, originalname, fieldname } = req.file; const { channel_id } = req.params; const filename = originalname.replaceAll(" ", "_").replace(/\W+/g, ""); - t; + const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; await storage.set(originalname, buffer); From 614d8f14fa2bac5c4f4357d51b4df527bea93c19 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 28 May 2021 21:19:19 +0200 Subject: [PATCH 23/53] :sparkles: attachments --- .env.example | 1 + .gitignore | 3 +- dist/Server.d.ts | 29 -------- dist/Server.js | 92 ------------------------ dist/Snowflake.d.ts | 85 ----------------------- dist/Snowflake.js | 131 ----------------------------------- dist/Util.d.ts | 8 --- dist/Util.js | 45 ------------ dist/index.d.ts | 1 - dist/index.js | 15 ---- dist/routes/attachments.d.ts | 2 - dist/routes/attachments.js | 48 ------------- dist/routes/external.d.ts | 2 - dist/routes/external.js | 75 -------------------- package-lock.json | Bin 99841 -> 107248 bytes package.json | 3 +- src/Server.ts | 2 +- src/routes/attachments.ts | 42 ++++++----- src/routes/external.ts | 1 + src/util/FileStorage.ts | 23 +++++- src/util/Storage.ts | 5 +- 21 files changed, 54 insertions(+), 559 deletions(-) delete mode 100644 dist/Server.d.ts delete mode 100644 dist/Server.js delete mode 100644 dist/Snowflake.d.ts delete mode 100644 dist/Snowflake.js delete mode 100644 dist/Util.d.ts delete mode 100644 dist/Util.js delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.js delete mode 100644 dist/routes/attachments.d.ts delete mode 100644 dist/routes/attachments.js delete mode 100644 dist/routes/external.d.ts delete mode 100644 dist/routes/external.js diff --git a/.env.example b/.env.example index 0a91585c..b5e011f1 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ STORAGE_LOCATION=files/ +STORAGE_PROVIDER=file PORT=3003 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6054dfe8..7d04eaee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/ node_modules/ .DS_Store -.env \ No newline at end of file +.env +dist/ \ No newline at end of file diff --git a/dist/Server.d.ts b/dist/Server.d.ts deleted file mode 100644 index f4d12c65..00000000 --- a/dist/Server.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/// -import { Application, Router } from "express"; -import { Database } from "lambert-db"; -import { Server as HTTPServer } from "http"; -import "express-async-errors"; -export declare type ServerOptions = { - db: string; - port: number; - host: string; -}; -declare global { - namespace Express { - interface Request { - server: Server; - } - } -} -export declare class Server { - app: Application; - http: HTTPServer; - db: Database; - routes: Router[]; - options: ServerOptions; - constructor(options?: Partial); - init(): Promise; - registerRoutes(root: string): Promise; - registerRoute(root: string, file: string): any; - destroy(): Promise; -} diff --git a/dist/Server.js b/dist/Server.js deleted file mode 100644 index 72046dfc..00000000 --- a/dist/Server.js +++ /dev/null @@ -1,92 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Server = void 0; -const express_1 = __importDefault(require("express")); -const lambert_db_1 = require("lambert-db"); -const Util_1 = require("./Util"); -require("express-async-errors"); -const log = console.log; -console.log = (content) => { - log(`[${new Date().toTimeString().split(" ")[0]}]`, content); -}; -class Server { - constructor(options = { port: 3000, host: "0.0.0.0" }) { - this.app = express_1.default(); - this.db = new lambert_db_1.MongoDatabase(options === null || options === void 0 ? void 0 : options.db); - this.options = options; - } - init() { - return __awaiter(this, void 0, void 0, function* () { - yield this.db.init(); - console.log("[Database] connected..."); - yield new Promise((res, rej) => { - this.http = this.app.listen(this.options.port, this.options.host, () => res(null)); - }); - this.routes = yield this.registerRoutes(__dirname + "/routes/"); - }); - } - registerRoutes(root) { - return __awaiter(this, void 0, void 0, function* () { - this.app.use((req, res, next) => { - req.server = this; - next(); - }); - const routes = yield Util_1.traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); - this.app.use((err, req, res, next) => { - res.status(400).send(err); - next(err); - }); - return routes; - }); - } - registerRoute(root, file) { - var _a, _b; - 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 || ((_b = (_a = router === null || router === void 0 ? void 0 : router.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) !== "router") - throw `File doesn't export any default router`; - this.app.use(path, router); - console.log(`[Routes] ${path} registerd`); - return router; - } - catch (error) { - console.error(new Error(`[Server] ¯\\_(ツ)_/¯ Failed to register route ${path}: ${error}`)); - } - } - destroy() { - return __awaiter(this, void 0, void 0, function* () { - yield this.db.destroy(); - yield new Promise((res, rej) => { - this.http.close((err) => { - if (err) - return rej(err); - return res(""); - }); - }); - }); - } -} -exports.Server = Server; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1NlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7QUFBQSxzREFBd0Y7QUFDeEYsMkNBQXFEO0FBRXJELGlDQUEyQztBQUUzQyxnQ0FBOEI7QUFFOUIsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQztBQUN4QixPQUFPLENBQUMsR0FBRyxHQUFHLENBQUMsT0FBTyxFQUFFLEVBQUU7SUFDekIsR0FBRyxDQUFDLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUM5RCxDQUFDLENBQUM7QUFnQkYsTUFBYSxNQUFNO0lBT2xCLFlBQVksVUFBa0MsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUU7UUFDNUUsSUFBSSxDQUFDLEdBQUcsR0FBRyxpQkFBTyxFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDLEVBQUUsR0FBRyxJQUFJLDBCQUFhLENBQUMsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBd0IsQ0FBQztJQUN6QyxDQUFDO0lBRUssSUFBSTs7WUFDVCxNQUFNLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFLENBQUM7WUFFckIsT0FBTyxDQUFDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO1lBQ3ZDLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUU7Z0JBQzlCLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8s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 \ No newline at end of file diff --git a/dist/Snowflake.d.ts b/dist/Snowflake.d.ts deleted file mode 100644 index 17effce6..00000000 --- a/dist/Snowflake.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -export = SnowflakeUtil; -/** - * A container for useful snowflake-related methods. - */ -declare class SnowflakeUtil { - /** - * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z - * ``` - * If we have a snowflake '266241948824764416' we can represent it as binary: - * - * 64 22 17 12 0 - * 000000111011000111100001101001000101000000 00001 00000 000000000000 - * number of ms since Discord epoch worker pid increment - * ``` - * @typedef {string} Snowflake - */ - /** - * Transforms a snowflake from a decimal string to a bit string. - * @param {Snowflake} num Snowflake to be transformed - * @returns {string} - * @private - */ - private static idToBinary; - /** - * Transforms a snowflake from a bit string to a decimal string. - * @param {string} num Bit string to be transformed - * @returns {Snowflake} - * @private - */ - private static binaryToID; - /** - * Generates a Discord snowflake. - * This hardcodes the worker ID as 1 and the process ID as 0. - * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate - * @returns {Snowflake} The generated snowflake - */ - static generate(timestamp?: number | Date | undefined): string; - /** - * A deconstructed snowflake. - * @typedef {Object} DeconstructedSnowflake - * @property {number} timestamp Timestamp the snowflake was created - * @property {Date} date Date the snowflake was created - * @property {number} workerID Worker ID in the snowflake - * @property {number} processID Process ID in the snowflake - * @property {number} increment Increment in the snowflake - * @property {string} binary Binary representation of the snowflake - */ - /** - * Deconstructs a Discord snowflake. - * @param {Snowflake} snowflake Snowflake to deconstruct - * @returns {DeconstructedSnowflake} Deconstructed snowflake - */ - static deconstruct(snowflake: string): { - /** - * Timestamp the snowflake was created - */ - timestamp: number; - /** - * Date the snowflake was created - */ - date: Date; - /** - * Worker ID in the snowflake - */ - workerID: number; - /** - * Process ID in the snowflake - */ - processID: number; - /** - * Increment in the snowflake - */ - increment: number; - /** - * Binary representation of the snowflake - */ - binary: string; - }; - /** - * Discord's epoch value (2015-01-01T00:00:00.000Z). - * @type {number} - * @readonly - */ - static readonly get EPOCH(): number; -} diff --git a/dist/Snowflake.js b/dist/Snowflake.js deleted file mode 100644 index 4a50845e..00000000 --- a/dist/Snowflake.js +++ /dev/null @@ -1,131 +0,0 @@ -// @ts-nocheck -// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js -"use strict"; -// Discord epoch (2015-01-01T00:00:00.000Z) -const EPOCH = 1420070400000; -let INCREMENT = 0; -/** - * A container for useful snowflake-related methods. - */ -class SnowflakeUtil { - constructor() { - throw new Error(`The ${this.constructor.name} class may not be instantiated.`); - } - /** - * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z - * ``` - * If we have a snowflake '266241948824764416' we can represent it as binary: - * - * 64 22 17 12 0 - * 000000111011000111100001101001000101000000 00001 00000 000000000000 - * number of ms since Discord epoch worker pid increment - * ``` - * @typedef {string} Snowflake - */ - /** - * Transforms a snowflake from a decimal string to a bit string. - * @param {Snowflake} num Snowflake to be transformed - * @returns {string} - * @private - */ - static idToBinary(num) { - let bin = ""; - let high = parseInt(num.slice(0, -10)) || 0; - let low = parseInt(num.slice(-10)); - while (low > 0 || high > 0) { - bin = String(low & 1) + bin; - low = Math.floor(low / 2); - if (high > 0) { - low += 5000000000 * (high % 2); - high = Math.floor(high / 2); - } - } - return bin; - } - /** - * Transforms a snowflake from a bit string to a decimal string. - * @param {string} num Bit string to be transformed - * @returns {Snowflake} - * @private - */ - static binaryToID(num) { - let dec = ""; - while (num.length > 50) { - const high = parseInt(num.slice(0, -32), 2); - const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); - dec = (low % 10).toString() + dec; - num = - Math.floor(high / 10).toString(2) + - Math.floor(low / 10) - .toString(2) - .padStart(32, "0"); - } - num = parseInt(num, 2); - while (num > 0) { - dec = (num % 10).toString() + dec; - num = Math.floor(num / 10); - } - return dec; - } - /** - * Generates a Discord snowflake. - * This hardcodes the worker ID as 1 and the process ID as 0. - * @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate - * @returns {Snowflake} The generated snowflake - */ - static generate(timestamp = Date.now()) { - if (timestamp instanceof Date) - timestamp = timestamp.getTime(); - if (typeof timestamp !== "number" || isNaN(timestamp)) { - throw new TypeError(`"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})`); - } - if (INCREMENT >= 4095) - INCREMENT = 0; - const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++) - .toString(2) - .padStart(12, "0")}`; - return SnowflakeUtil.binaryToID(BINARY); - } - /** - * A deconstructed snowflake. - * @typedef {Object} DeconstructedSnowflake - * @property {number} timestamp Timestamp the snowflake was created - * @property {Date} date Date the snowflake was created - * @property {number} workerID Worker ID in the snowflake - * @property {number} processID Process ID in the snowflake - * @property {number} increment Increment in the snowflake - * @property {string} binary Binary representation of the snowflake - */ - /** - * Deconstructs a Discord snowflake. - * @param {Snowflake} snowflake Snowflake to deconstruct - * @returns {DeconstructedSnowflake} Deconstructed snowflake - */ - static deconstruct(snowflake) { - const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0"); - const res = { - timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, - workerID: parseInt(BINARY.substring(42, 47), 2), - processID: parseInt(BINARY.substring(47, 52), 2), - increment: parseInt(BINARY.substring(52, 64), 2), - binary: BINARY, - }; - Object.defineProperty(res, "date", { - get: function get() { - return new Date(this.timestamp); - }, - enumerable: true, - }); - return res; - } - /** - * Discord's epoch value (2015-01-01T00:00:00.000Z). - * @type {number} - * @readonly - */ - static get EPOCH() { - return EPOCH; - } -} -module.exports = SnowflakeUtil; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU25vd2ZsYWtlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1Nub3dmbGFrZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjO0FBRWQsb0VBQW9FO0FBQ3BFLFlBQVksQ0FBQztBQUViLDJDQUEyQztBQUMzQyxNQUFNLEtBQUssR0FBRyxhQUFhLENBQUM7QUFDNUIsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFDO0FBRWxCOztHQUVHO0FBQ0gsTUFBTSxhQUFhO0lBQ2xCO1FBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxpQ0FBaUMsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBRUg7Ozs7O09BS0c7SUFDSCxNQUFNLENBQUMsVUFBVSxDQUFDLEdBQUc7UUFDcEIsSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDO1FBQ2IsSUFBSSxJQUFJLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUMsSUFBSSxHQUFHLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ25DLE9BQU8sR0FBRyxHQUFHLENBQUMsSUFBSSxJQUFJLEdBQUcsQ0FBQyxFQUFFO1lBQzNCLEdBQUcsR0FBRyxNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQztZQUM1QixHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDMUIsSUFBSSxJQUFJLEdBQUcsQ0FBQyxFQUFFO2dCQUNiLEdBQUcsSUFBSSxVQUFVLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQy9CLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElB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 \ No newline at end of file diff --git a/dist/Util.d.ts b/dist/Util.d.ts deleted file mode 100644 index c0a33362..00000000 --- a/dist/Util.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import "missing-native-js-functions"; -export interface traverseDirectoryOptions { - dirname: string; - filter?: RegExp; - excludeDirs?: RegExp; - recursive?: boolean; -} -export declare function traverseDirectory(options: traverseDirectoryOptions, action: (path: string) => T): Promise; diff --git a/dist/Util.js b/dist/Util.js deleted file mode 100644 index 45421484..00000000 --- a/dist/Util.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.traverseDirectory = void 0; -const promises_1 = __importDefault(require("fs/promises")); -require("missing-native-js-functions"); -const DEFAULT_EXCLUDE_DIR = /^\./; -const DEFAULT_FILTER = /^([^\.].*)\.js$/; -function traverseDirectory(options, action) { - return __awaiter(this, void 0, void 0, function* () { - if (!options.filter) - options.filter = DEFAULT_FILTER; - if (!options.excludeDirs) - options.excludeDirs = DEFAULT_EXCLUDE_DIR; - const routes = yield promises_1.default.readdir(options.dirname); - const promises = routes.map((file) => __awaiter(this, void 0, void 0, function* () { - const path = options.dirname + file; - const stat = yield promises_1.default.lstat(path); - if (path.match(options.excludeDirs)) - return; - if (stat.isFile() && path.match(options.filter)) { - return action(path); - } - else if (options.recursive && stat.isDirectory()) { - return traverseDirectory(Object.assign(Object.assign({}, options), { dirname: path + "/" }), action); - } - })); - const result = yield Promise.all(promises); - const t = result.flat(); - return t.filter((x) => x != undefined); - }); -} -exports.traverseDirectory = traverseDirectory; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9VdGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDJEQUE2QjtBQUM3Qix1Q0FBcUM7QUFTckMsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLENBQUM7QUFDbEMsTUFBTSxjQUFjLEdBQUcsaUJBQWlCLENBQUM7QUFFekMsU0FBc0IsaUJBQWlCLENBQ3RDLE9BQWlDLEVBQ2pDLE1BQTJCOztRQUUzQixJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU07WUFBRSxPQUFPLENBQUMsTUFBTSxHQUFHLGNBQWMsQ0FBQztRQUNyRCxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVc7WUFBRSxPQUFPLENBQUMsV0FBVyxHQUFHLG1CQUFtQixDQUFDO1FBRXBFLE1BQU0sTUFBTSxHQUFHLE1BQU0sa0JBQUUsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2pELE1BQU0sUUFBUSxHQUFtQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQU8sSUFBSSxFQUFFLEVBQUU7WUFDMUUsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7WUFDcEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxrQkFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNsQyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQVMsT0FBTyxDQUFDLFdBQVcsQ0FBQztnQkFBRSxPQUFPO1lBRXBELElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQVMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFO2dCQUN4RCxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNwQjtpQkFBTSxJQUFJLE9BQU8sQ0FBQyxTQUFTLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxFQUFFO2dCQUNuRCxPQUFPLGlCQUFpQixpQ0FBTSxPQUFPLEtBQUUsT0FBTyxFQUFFLElBQUksR0FBRyxHQUFHLEtBQUksTUFBTSxDQUFDLENBQUM7YUFDdEU7UUFDRixDQUFDLENBQUEsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRTNDLE1BQU0sQ0FBQyxHQUFzQixNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFFM0MsT0FBWSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLElBQUksU0FBUyxDQUFDLENBQUM7SUFDN0MsQ0FBQztDQUFBO0FBeEJELDhDQXdCQyJ9 \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index cb0ff5c3..00000000 --- a/dist/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 792fcefd..00000000 --- a/dist/index.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const Server_1 = require("./Server"); -const server = new Server_1.Server(); -server - .init() - .then(() => { - console.log("[Server] started on :" + server.options.port); -}) - .catch((e) => console.error("[Server] Error starting: ", e)); -//// server -//// .destroy() -//// .then(() => console.log("[Server] closed.")) -//// .catch((e) => console.log("[Server] Error closing: ", e)); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxQ0FBa0M7QUFFbEMsTUFBTSxNQUFNLEdBQUcsSUFBSSxlQUFNLEVBQUUsQ0FBQztBQUM1QixNQUFNO0tBQ0osSUFBSSxFQUFFO0tBQ04sSUFBSSxDQUFDLEdBQUcsRUFBRTtJQUNWLE9BQU8sQ0FBQyxHQUFHLENBQUMsdUJBQXVCLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUM1RCxDQUFDLENBQUM7S0FDRCxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsMkJBQTJCLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUU5RCxXQUFXO0FBQ1gsZ0JBQWdCO0FBQ2hCLGtEQUFrRDtBQUNsRCwrREFBK0QifQ== \ No newline at end of file diff --git a/dist/routes/attachments.d.ts b/dist/routes/attachments.d.ts deleted file mode 100644 index ae2ab413..00000000 --- a/dist/routes/attachments.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const router: import("express-serve-static-core").Router; -export default router; diff --git a/dist/routes/attachments.js b/dist/routes/attachments.js deleted file mode 100644 index 4b639448..00000000 --- a/dist/routes/attachments.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const express_1 = require("express"); -const multer_1 = __importDefault(require("multer")); -const Snowflake_1 = __importDefault(require("../Snowflake")); -const multer_ = multer_1.default(); -const router = express_1.Router(); -router.post("/:filename", multer_.single("attachment"), (req, res) => __awaiter(void 0, void 0, void 0, function* () { - const { buffer, mimetype } = req.file; - const { filename } = req.params; - const { db } = req.server; - const File = { - filename, - file: buffer.toString("base64"), - id: Snowflake_1.default.generate(), - type: mimetype, - }; - if (!(yield db.data.attachments.push(File))) - throw new Error("Error uploading file"); - return res.status(201).send({ success: true, message: "attachment uploaded", id: File.id, filename }); -})); -router.get("/:hash/:filename", (req, res) => __awaiter(void 0, void 0, void 0, function* () { - const { db } = req.server; - const { hash, filename } = req.params; - const File = yield db.data.attachments({ id: hash, filename: filename }).get(); - res.set("Content-Type", File.type); - return res.send(Buffer.from(File.file, "base64")); -})); -router.delete("/:hash/:filename", (req, res) => __awaiter(void 0, void 0, void 0, function* () { - const { hash, filename } = req.params; - const { db } = req.server; - yield db.data.attachments({ id: hash, filename: filename }).delete(); - return res.send({ success: true, message: "attachment deleted" }); -})); -exports.default = router; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0YWNobWVudHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcm91dGVzL2F0dGFjaG1lbnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7O0FBQUEscUNBQWlDO0FBQ2pDLG9EQUE0QjtBQUM1Qiw2REFBcUM7QUFFckMsTUFBTSxPQUFPLEdBQUcsZ0JBQU0sRUFBRSxDQUFDO0FBQ3pCLE1BQU0sTUFBTSxHQUFHLGdCQUFNLEVBQUUsQ0FBQztBQVN4QixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQU8sR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFO0lBQzFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQztJQUN0QyxNQUFNLEVBQUUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUNoQyxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUUxQixNQUFNLElBQUksR0FBZTtRQUN4QixRQUFRO1FBQ1IsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1FBQy9CLEVBQUUsRUFBRSxtQkFBUyxDQUFDLFFBQVEsRUFBRTtRQUN4QixJQUFJLEVBQUUsUUFBUTtLQUNkLENBQUM7SUFFRixJQUFJLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztJQUVyRixPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsRUFBRSxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztBQUN2RyxDQUFDLENBQUEsQ0FBQyxDQUFDO0FBRUgsTUFBTSxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsRUFBRSxDQUFPLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRTtJQUNqRCxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztJQUMxQixNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFFdEMsTUFBTSxJQUFJLEdBQWUsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUM7SUFFM0YsR0FBRyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25DLE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUNuRCxDQUFDLENBQUEsQ0FBQyxDQUFDO0FBRUgsTUFBTSxDQUFDLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxDQUFPLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRTtJQUNwRCxNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFDdEMsTUFBTSxFQUFFLEVBQUUsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFFMUIsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDckUsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsQ0FBQyxDQUFDO0FBQ25FLENBQUMsQ0FBQSxDQUFDLENBQUM7QUFFSCxrQkFBZSxNQUFNLENBQUMifQ== \ No newline at end of file diff --git a/dist/routes/external.d.ts b/dist/routes/external.d.ts deleted file mode 100644 index ae2ab413..00000000 --- a/dist/routes/external.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const router: import("express-serve-static-core").Router; -export default router; diff --git a/dist/routes/external.js b/dist/routes/external.js deleted file mode 100644 index 751388f1..00000000 --- a/dist/routes/external.js +++ /dev/null @@ -1,75 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const body_parser_1 = __importDefault(require("body-parser")); -const express_1 = require("express"); -const node_fetch_1 = __importDefault(require("node-fetch")); -const cheerio_1 = __importDefault(require("cheerio")); -const btoa_1 = __importDefault(require("btoa")); -const url_1 = require("url"); -const router = express_1.Router(); -const DEFAULT_FETCH_OPTIONS = { - redirect: "follow", - follow: 1, - headers: { - "user-agent": "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)", - }, - size: 1024 * 1024 * 8, - compress: true, - method: "GET", -}; -router.post("/", body_parser_1.default.json(), (req, res) => __awaiter(void 0, void 0, void 0, function* () { - if (!req.body) - throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); - const { db } = req.server; - const { url } = req.body; - const ID = btoa_1.default(url); - const cache = yield db.data.crawler({ id: ID }).get(); - if (cache) - return res.send(cache); - try { - const request = yield node_fetch_1.default(url, DEFAULT_FETCH_OPTIONS); - const text = yield request.text(); - const ツ = cheerio_1.default.load(text); - const ogTitle = ツ('meta[property="og:title"]').attr("content"); - const ogDescription = ツ('meta[property="og:description"]').attr("content"); - const ogImage = ツ('meta[property="og:image"]').attr("content"); - const ogUrl = ツ('meta[property="og:url"]').attr("content"); - const ogType = ツ('meta[property="og:type"]').attr("content"); - const filename = new url_1.URL(url).host.split(".")[0]; - const ImageResponse = yield node_fetch_1.default(ogImage, DEFAULT_FETCH_OPTIONS); - const ImageType = ImageResponse.headers.get("content-type"); - const ImageExtension = ImageType === null || ImageType === void 0 ? void 0 : ImageType.split("/")[1]; - const ImageResponseBuffer = (yield ImageResponse.buffer()).toString("base64"); - const cachedImage = `/external/${ID}/${filename}.${ImageExtension}`; - yield db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); - const new_cache_entry = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; - yield db.data.crawler.push(new_cache_entry); - res.send(new_cache_entry); - } - catch (error) { - console.log(error); - throw new Error("Couldn't fetch website"); - } -})); -router.get("/:id/:filename", (req, res) => __awaiter(void 0, void 0, void 0, function* () { - const { db } = req.server; - const { id, filename } = req.params; - const { image, type } = yield db.data.externals({ id: id }).get(); - const imageBuffer = Buffer.from(image, "base64"); - res.set("Content-Type", type); - res.send(imageBuffer); -})); -exports.default = router; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXh0ZXJuYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcm91dGVzL2V4dGVybmFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7O0FBQUEsOERBQXFDO0FBQ3JDLHFDQUFpQztBQUNqQyw0REFBK0I7QUFDL0Isc0RBQThCO0FBQzlCLGdEQUF3QjtBQUN4Qiw2QkFBMEI7QUFFMUIsTUFBTSxNQUFNLEdBQUcsZ0JBQU0sRUFBRSxDQUFDO0FBV3hCLE1BQU0scUJBQXFCLEdBQVE7SUFDbEMsUUFBUSxFQUFFLFFBQVE7SUFDbEIsTUFBTSxFQUFFLENBQUM7SUFDVCxPQUFPLEVBQUU7UUFDUixZQUFZLEVBQUUsbUVBQW1FO0tBQ2pGO0lBQ0QsSUFBSSxFQUFFLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQztJQUNyQixRQUFRLEVBQUUsSUFBSTtJQUNkLE1BQU0sRUFBRSxLQUFLO0NBQ2IsQ0FBQztBQUVGLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLHFCQUFVLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBTyxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUU7SUFDdEQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBRWhHLE1BQU0sRUFBRSxFQUFFLEVBQUUsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDO0lBQzFCLE1BQU0sRUFBRSxHQUFHLEVBQUUsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDO0lBRXpCLE1BQU0sRUFBRSxHQUFHLGNBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUVyQixNQUFNLEtBQUssR0FBRyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDdEQsSUFBSSxLQUFLO1FBQUUsT0FBTyxHQUFHLENBQUMsSUFB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 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 623e59c1a7b3a906401a8f3d6c9fe35fdec09e0f..c51b11e5dce93b095f4f073e1d7c030ed10c59d9 100644 GIT binary patch delta 4115 zcmd5;TZ|iL71p0@cDsq9T(`Mx%Ep@z!o(hr$2;~^jfgMn`~5O&J2b?enYG8B`*?hV zsM-fq2~sKTk1|N0wowsOd1$h-&+`<(Ni^L^jSmC3vBPG0>R@Iv&$A$Vom9(V-2ba0vDS$n%Lv$kjFY!@-Y zvEXnHUj_dJ=04v}6#jhsY53#WxnfNTNC8?ert$e_r_d}4x$J|Ek+9MI8rEQVvQm{XzA_<(iD@m2q|b`c>SAm@DF>YhF2zj z1g51HD{=!?Md5Y%2<$K5N6)wK+pHC8N3EybX{^kHd8(8qqNz-+66i6q$|IywMSZ?n zD3neP23@7xlbe}(A{tPWK_cJ^1-gW?97|_te~XAIN+yx7R?uwE>rq6vDrbaDvRn1V zi7s3*|9)fGbhk4wnOWU=W<1EB3ni zQpeXJdu~5THN9S6vLc51i&TQ+2iT&&7w_bXjj$38xy6{A%lh*L!PCsUaH8ZclVz8Z z(Wp)}+3Tv|7@uk+B8$}qAAR!=<*_4oI_;@LesKU)fJHyg%y?EpBu4I_+I zKX4ts-$Q0q?yv0_aV-Gmi|Jl{$w#(p32zEr$~c|jAU5!(ok^~njfeV6ISo?~Jk)41 z9Xec(E8QN=Hv8cvO~OSt8ci07Ak(D~U#{Zzc?tzF(+bjg4!+!*E30gksn*PBx@^tf zmh7U$uy(C&ngy$wafj34w4L2KZGLQ~-E1A)auB8i-*Buq)Mx@U%jt4B;V)l21K$#- zAUtshZp$5eh7%c;)g-l{YY!6qf+kC%X*>t_efRkA+9ltvNtR_j3vT0H1RRD}KSU33 z>jtwdx;)$2zE;7!P(uoM8lY@j?3yBhy6_u**+`09fY|FaHaAMs|=Yo~|m#yz< z+s2shtaI&#UK{O(|H^*ug=JQiG|gu1y1ZVmM}Hz$&ALkBxM(#XLtUk*ewzsl`ev8SnGaNT&|3zHxHg zJeeuKq_SPE4et@N!y|8gbsGL2o!a@JT_F~o1vuPy)^*@9&HT!(g9jh%-Dqt)4zIsC zUvv+G0V2?87W=xE@=(rfLMu^zj~ojGNu<+OlVq(N%ykIepGETVww?OLU;t%_We06=qu&9-Bb3-`KkyOzglNr0p#9ojPiU`HqS_E;0BX zI=%m+al+du25{rVB5=XK-?W*_ON%BP_$e;Z_ax$MVc?chVvA115T42>h>pyK*b;#< zxRj502Z1F|v6pd4BHd^qokG};x$&MJYDV%n!xrkTxKt|jg_6=2xF|D<+xTD`I0_@H z`@uzcVf8Z;7cp~?FWvY&JjNU!etY$&2d2gl09Upj1O&u9u9>mLY>)BEZbAcm{c9(R z*+jjANL;B~st4+_E5l?mRU{_M?jXqtags%yeyzXEhLS3gO%UBArAU-WBI#0=jCzul znou012wvdYg_x7nyLney2-73fT6&bzEYNW}Fb6gs1KW8EjMn+Z^14m$!iM*uMz-80 z3pj9i=f%^HyPYBZt{qH_XwcIW#jC}xn57k7W%C5pY&ZH5 zld#a197kDUnePStsRo}aW|kYC?jlacMLo!*xwc-CglMfF^^hHZJLF7pqTFhDTmr4< zA`+4d<0C%7{(JUgq_i@_94;suXC_DGA-(XW6V&F3f^Yw9VhDfo^3G2}su=I6U>5%M zy^&VA1$J%9<4M&@d27^)iTcAI{_2ktPb5^zTOw4lqZq#a+f$#gKr!ybtSi}2D2Il( z{!BghX^9|P$dAU>Yk|4E-1ZzJH?+QcbvZTd-tMtRGG#@bZ}n0J3n=3Uwv z<~_Iey&5P!weKUU@Z-~LVJm3XP-ejK5tKZZW{tnN!DrzQ?(R4C>;(r*0yZhha4Z1l zh(*V*SQ?yoeoL5iLqx)Rf0{8q+za*?cejDv#;qop*<~{721^;Yo8Zvoh+-X>As4~y z+CQ4$`}_X?6l+c1R&ngw>t6voJ{@FgVIR4Pg)A1gVC5k#@d%rmB4ebTFRy*;GFX1< E-xr-qivR!s delta 364 zcmexxl&!IcZNeu;6v+^Kv)IGR#TJ@GMMCb14j+ym7bs=2MS0FitMu5!sy2F3P&Op7)W!=40}K zE}Qw%?727l7CYN)mfG0Dxw&oMQQ^%tS9BRSyI-HeMF+=CUns*UGui(OJJ5xj7$-mY z$U1ozAK&C!_Ft0)UP*4A@oWS0 { - const { buffer, mimetype, stream, size, originalname, fieldname } = req.file; +router.post("/:channel_id", multer_.single("file"), async (req, res) => { + const { buffer, mimetype, size, originalname, fieldname } = req.file; const { channel_id } = req.params; - const filename = originalname.replaceAll(" ", "_").replace(/\W+/g, ""); - + const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); + const id = Snowflake.generate(); + const path = `attachments/${channel_id}/${id}/${filename}`; + const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; - await storage.set(originalname, buffer); - - const id = Snowflake.generate(); + await storage.set(path, buffer); const file = { id, - type: mimetype, content_type: mimetype, - filename: originalname, + filename: filename, size, - url: `${endpoint}/attachments/${channel_id}/${id}/`, + url: `${endpoint}/attachments/${channel_id}/${id}/${filename}`, }; return res.json(file); }); -router.get("/:hash/:filename", async (req, res) => { - const { hash, filename } = req.params; +router.get("/:channel_id/:id/:filename", async (req, res) => { + const { channel_id, id, filename } = req.params; - const File = await db.data.attachments({ id: hash, filename: filename }).get(); + const file = await storage.get(`attachments/${channel_id}/${id}/${filename}`); + if (!file) throw new HTTPError("File not found"); + const result = await FileType.fromBuffer(file); - res.set("Content-Type", File.type); - return res.send(Buffer.from(File.file, "base64")); + res.set("Content-Type", result?.mime); + + return res.send(file); }); -router.delete("/:hash/:filename", async (req, res) => { - const { hash, filename } = req.params; +router.delete("/:channel_id/:id/:filename", async (req, res) => { + const { channel_id, id, filename } = req.params; + const path = `attachments/${channel_id}/${id}/${filename}`; + + storage.delete(path); - await db.data.attachments({ id: hash, filename: filename }).delete(); return res.send({ success: true, message: "attachment deleted" }); }); diff --git a/src/routes/external.ts b/src/routes/external.ts index 045eb7da..3bcb39b0 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import bodyParser from "body-parser"; import { Router } from "express"; import fetch from "node-fetch"; diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index b4d00213..9c9911f3 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -1,13 +1,30 @@ import { Storage } from "./Storage"; import fs from "fs/promises"; import { join } from "path"; +import "missing-native-js-functions"; export class FileStorage implements Storage { - async get(path: string) { - return fs.readFile(join(process.env.STORAGE_LOCATION || "", path), { encoding: "binary" }); + async get(path: string): Promise { + path = join(process.env.STORAGE_LOCATION || "", path); + try { + const file = await fs.readFile(path); + // @ts-ignore + return file; + } catch (error) { + return null; + } } async set(path: string, value: any) { - return fs.writeFile(join(process.env.STORAGE_LOCATION || "", path), value, { encoding: "binary" }); + path = join(process.env.STORAGE_LOCATION || "", path); + const dir = path.split("/").slice(0, -1).join("/"); + await fs.mkdir(dir, { recursive: true }).caught(); + + return fs.writeFile(path, value, { encoding: "binary" }); + } + + async delete(path: string) { + path = join(process.env.STORAGE_LOCATION || "", path); + await fs.unlink(path); } } diff --git a/src/util/Storage.ts b/src/util/Storage.ts index 391afa83..f8b09e71 100644 --- a/src/util/Storage.ts +++ b/src/util/Storage.ts @@ -1,8 +1,9 @@ import { FileStorage } from "./FileStorage"; export interface Storage { - set(path: string, data: any): Promise; - get(path: string): Promise; + set(path: string, data: Buffer): Promise; + get(path: string): Promise; + delete(path: string): Promise; } var storage: Storage; From 21e659fce5feeec5a79101bb1ac9bd7e6c6ac847 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 28 May 2021 22:40:48 +0200 Subject: [PATCH 24/53] :sparkles: external assets --- src/routes/external.ts | 56 +++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/src/routes/external.ts b/src/routes/external.ts index 3bcb39b0..2f8de5d9 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -2,8 +2,10 @@ import bodyParser from "body-parser"; import { Router } from "express"; import fetch from "node-fetch"; -import cheerio from "cheerio"; import crypto from "crypto"; +import { HTTPError } from "lambert-server"; +import { Snowflake } from "@fosscord/server-util"; +import { storage } from "../util/Storage"; const router = Router(); @@ -28,52 +30,34 @@ const DEFAULT_FETCH_OPTIONS: any = { }; router.post("/", bodyParser.json(), async (req, res) => { - if (!req.body) throw new Error("Invalid Body (url missing) \nExample: url:https://discord.com"); - + if (!req.body) throw new HTTPError("Invalid Body"); const { url } = req.body; + if (!url || typeof url !== "string") throw new HTTPError("Invalid url"); - const hash = crypto.createHash("md5").update(url).digest("hex"); + const id = Snowflake.generate(); try { - const request = await fetch(url, DEFAULT_FETCH_OPTIONS); + const response = await fetch(ogImage, DEFAULT_FETCH_OPTIONS); + const buffer = await response.buffer(); - const text = await request.text(); - const $ = cheerio.load(text); + await storage.set(`/external/${id}`, buffer); - const ogTitle = $('meta[property="og:title"]').attr("content"); - const ogDescription = $('meta[property="og:description"]').attr("content"); - const ogImage = $('meta[property="og:image"]').attr("content"); - const ogUrl = $('meta[property="og:url"]').attr("content"); - const ogType = $('meta[property="og:type"]').attr("content"); - - const filename = new URL(url).host.split(".")[0]; - - const ImageResponse = await fetch(ogImage, DEFAULT_FETCH_OPTIONS); - const ImageType = ImageResponse.headers.get("content-type"); - const ImageExtension = ImageType?.split("/")[1]; - const ImageResponseBuffer = (await ImageResponse.buffer()).toString("base64"); - const cachedImage = `/external/${ID}/${filename}.${ImageExtension}`; - - await db.data.externals.push({ image: ImageResponseBuffer, id: ID, type: ImageType }); - - const new_cache_entry: crawled = { id: ID, ogTitle, ogDescription, cachedImage, ogUrl, ogType }; - await db.data.crawler.push(new_cache_entry); - - res.send(new_cache_entry); + res.send({ id }); } catch (error) { - console.log(error); - - throw new Error("Couldn't fetch website"); + throw new HTTPError("Couldn't fetch website"); } }); -router.get("/:id/:filename", async (req, res) => { - const { id, filename } = req.params; - const { image, type } = await db.data.externals({ id: id }).get(); - const imageBuffer = Buffer.from(image, "base64"); +router.get("/:id/", async (req, res) => { + const { id } = req.params; - res.set("Content-Type", type); - res.send(imageBuffer); + const file = await storage.get(`/external/${id}`); + if (!file) throw new HTTPError("File not found"); + const result = await FileType.fromBuffer(file); + + res.set("Content-Type", result?.mime); + + return res.send(file); }); export default router; From 3b032667851905eeec5c61d50be1e242e1aa739c Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 29 May 2021 18:23:16 +0200 Subject: [PATCH 25/53] :sparkles: avatars --- src/Server.ts | 10 +++++++ src/routes/attachments.ts | 22 +++++--------- src/routes/avatars.ts | 63 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/routes/avatars.ts diff --git a/src/Server.ts b/src/Server.ts index de02a585..15868129 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,6 +1,7 @@ import { Server, ServerOptions } from "lambert-server"; import { Config, db } from "@fosscord/server-util"; import path from "path"; +import multerConfig from "multer"; export interface CDNServerOptions extends ServerOptions {} @@ -26,3 +27,12 @@ export class CDNServer extends Server { return super.stop(); } } + +export const multer = multerConfig({ + storage: multerConfig.memoryStorage(), + limits: { + fields: 0, + files: 1, + fileSize: 1024 * 1024 * 100, // 100 mb + }, +}); diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index f477d1b2..3bbced31 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -1,21 +1,13 @@ import { Router } from "express"; -import multer from "multer"; import { Config, Snowflake } from "@fosscord/server-util"; import { storage } from "../util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; +import { multer } from "../Server"; -const multer_ = multer({ - storage: multer.memoryStorage(), - limits: { - fields: 0, - files: 1, - fileSize: 1024 * 1024 * 100, // 100 mb - }, -}); const router = Router(); -router.post("/:channel_id", multer_.single("file"), async (req, res) => { +router.post("/:channel_id", multer.single("file"), async (req, res) => { const { buffer, mimetype, size, originalname, fieldname } = req.file; const { channel_id } = req.params; const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); @@ -31,7 +23,7 @@ router.post("/:channel_id", multer_.single("file"), async (req, res) => { content_type: mimetype, filename: filename, size, - url: `${endpoint}/attachments/${channel_id}/${id}/${filename}`, + url: `${endpoint}/${path}`, }; return res.json(file); @@ -42,9 +34,9 @@ router.get("/:channel_id/:id/:filename", async (req, res) => { const file = await storage.get(`attachments/${channel_id}/${id}/${filename}`); if (!file) throw new HTTPError("File not found"); - const result = await FileType.fromBuffer(file); + const type = await FileType.fromBuffer(file); - res.set("Content-Type", result?.mime); + res.set("Content-Type", type?.mime); return res.send(file); }); @@ -53,9 +45,9 @@ router.delete("/:channel_id/:id/:filename", async (req, res) => { const { channel_id, id, filename } = req.params; const path = `attachments/${channel_id}/${id}/${filename}`; - storage.delete(path); + await storage.delete(path); - return res.send({ success: true, message: "attachment deleted" }); + return res.send({ success: true }); }); export default router; diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts new file mode 100644 index 00000000..c447db9f --- /dev/null +++ b/src/routes/avatars.ts @@ -0,0 +1,63 @@ +import { Router } from "express"; +import { Config, Snowflake } from "@fosscord/server-util"; +import { storage } from "../util/Storage"; +import FileType from "file-type"; +import { HTTPError } from "lambert-server"; +import { multer } from "../Server"; + +// TODO: check premium and animated pfp are allowed in the config +// TODO: generate different sizes of avatar +// TODO: generate different image types of avatar +// TODO: delete old avatars +// TODO: check request signature for modify methods + +const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp"]; +const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; + +const router = Router(); + +router.post("/:user_id", multer.single("file"), async (req, res) => { + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { user_id } = req.params; + + const id = Snowflake.generate(); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + const path = `avatars/${user_id}/${id}`; + const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; + + await storage.set(path, buffer); + + return res.json({ + id, + content_type: type.mime, + size, + url: `${endpoint}/path`, + }); +}); + +router.get("/:user_id/:id", async (req, res) => { + const { user_id, id } = req.params; + const path = `avatars/${user_id}/${id}`; + + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await FileType.fromBuffer(file); + + res.set("Content-Type", type?.mime); + + return res.send(file); +}); + +router.delete("/:user_id/:id", async (req, res) => { + const { user_id, id } = req.params; + const path = `avatars/${user_id}/${id}`; + + await storage.delete(path); + + return res.send({ success: true }); +}); + +export default router; From e87bebc3a3bd3b9408aca527b374f703c980070b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 30 May 2021 01:44:46 +0200 Subject: [PATCH 26/53] :sparkles: avatars + attachments --- .gitignore | 3 ++- package-lock.json | Bin 107248 -> 108526 bytes package.json | 3 ++- src/Server.ts | 4 ++-- src/routes/attachments.ts | 18 ++++++++++++++++++ src/routes/avatars.ts | 11 +++++++++-- src/routes/external.ts | 2 ++ 7 files changed, 35 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 7d04eaee..f2e69ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ .DS_Store .env -dist/ \ No newline at end of file +dist/ +files/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c51b11e5dce93b095f4f073e1d7c030ed10c59d9..fe21ab207c26cb4e659c76881e0e92a001a0ebd8 100644 GIT binary patch delta 952 zcmexxlE zPUe23&S*IKVxU53L8*3xV}5E;j()h0TSdNKMTntan7Ln;L12h>U}n0vX?jMHVVHA7 zwx6k=enF_CS7@cPxlx)|KxDSIX=u8EbCOqfK$1a(UvW^FS3zl&MYchZ(dI^$GkI(f z`zPDal%AYWW*`c8j=ruw+-;LT?o$i%^6@hBFLz4wH}p<+)D9{03ehgJNKbM#%M4G- zGBWY0bn$abcXRbK$f-(A&ofJQHcQkmF!u9wNlY#9E%wR^%*qK3FZ6UaFZU|5$Z>M3 z2=L2FE}vYeBJWmMnpz6>znPwao{c<(pPqV&v`Z zT2N{lk(W}I8KG|+n&Fldl$RbF;OAYEURDy7IXSULVe;F(Jd^M3WS^YGBfa_BR#qL< zI1QsooTg82)MAvL?Ei&*^QlKXNdq0bVAqIXi{9;LTo@HC2QZ4JFO*@FfyEIYILQGC WaI#aH>?o@;`9XvJc85B~4JrU`1Vc0c delta 274 zcmaENp6$a?wh5mYEhe9h6Q9h@xMTBu#?OqCFInkNzVk$5deR(5h0vrz=X~en(ug2u zm$IDv@Z{V;mtglWSI1P3ko4SClgt#S!creg<3N4Kim+6J91Aa_j56)QT(cxE%iI!g z?U2X-uZ+-~s_aq|5ACcx-y{#q++z31jb{}$A7ELNxA|{Lr_<);J6Lt-VC!^7X-1i; i{fz9J=RW00+J3}?QQUHJf~?Bq2~GOj?V1@kr~m*^QD(US diff --git a/package.json b/package.json index da374117..a6ac35ce 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/discord-open-source/discord-cdn#readme", "dependencies": { - "@fosscord/server-util": "^1.3.8", + "@fosscord/server-util": "^1.3.10", "body-parser": "^1.19.0", "btoa": "^1.2.1", "cheerio": "^1.0.0-rc.5", @@ -28,6 +28,7 @@ "express": "^4.17.1", "express-async-errors": "^3.1.1", "file-type": "^16.5.0", + "image-size": "^1.0.0", "lambert-db": "^1.2.3", "lambert-server": "^1.2.1", "missing-native-js-functions": "^1.0.8", diff --git a/src/Server.ts b/src/Server.ts index 15868129..57dfa536 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -31,8 +31,8 @@ export class CDNServer extends Server { export const multer = multerConfig({ storage: multerConfig.memoryStorage(), limits: { - fields: 0, - files: 1, + fields: 10, + files: 10, fileSize: 1024 * 1024 * 100, // 100 mb }, }); diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 3bbced31..e99b8d87 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -4,10 +4,14 @@ import { storage } from "../util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; import { multer } from "../Server"; +import imageSize from "image-size"; const router = Router(); router.post("/:channel_id", multer.single("file"), async (req, res) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + const { buffer, mimetype, size, originalname, fieldname } = req.file; const { channel_id } = req.params; const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); @@ -17,6 +21,15 @@ router.post("/:channel_id", multer.single("file"), async (req, res) => { const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; await storage.set(path, buffer); + var width; + var height; + if (mimetype.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + width = dimensions.width; + height = dimensions.height; + } + } const file = { id, @@ -24,6 +37,8 @@ router.post("/:channel_id", multer.single("file"), async (req, res) => { filename: filename, size, url: `${endpoint}/${path}`, + width, + height, }; return res.json(file); @@ -42,6 +57,9 @@ router.get("/:channel_id/:id/:filename", async (req, res) => { }); router.delete("/:channel_id/:id/:filename", async (req, res) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + const { channel_id, id, filename } = req.params; const path = `attachments/${channel_id}/${id}/${filename}`; diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts index c447db9f..973c45fc 100644 --- a/src/routes/avatars.ts +++ b/src/routes/avatars.ts @@ -4,6 +4,7 @@ import { storage } from "../util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; import { multer } from "../Server"; +import crypto from "crypto"; // TODO: check premium and animated pfp are allowed in the config // TODO: generate different sizes of avatar @@ -18,10 +19,13 @@ const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); router.post("/:user_id", multer.single("file"), async (req, res) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); const { buffer, mimetype, size, originalname, fieldname } = req.file; const { user_id } = req.params; - const id = Snowflake.generate(); + const id = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); const type = await FileType.fromBuffer(buffer); if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); @@ -39,7 +43,8 @@ router.post("/:user_id", multer.single("file"), async (req, res) => { }); router.get("/:user_id/:id", async (req, res) => { - const { user_id, id } = req.params; + var { user_id, id } = req.params; + id = id.split(".")[0]; const path = `avatars/${user_id}/${id}`; const file = await storage.get(path); @@ -52,6 +57,8 @@ router.get("/:user_id/:id", async (req, res) => { }); router.delete("/:user_id/:id", async (req, res) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); const { user_id, id } = req.params; const path = `avatars/${user_id}/${id}`; diff --git a/src/routes/external.ts b/src/routes/external.ts index 2f8de5d9..dcf56c8c 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -30,6 +30,8 @@ const DEFAULT_FETCH_OPTIONS: any = { }; router.post("/", bodyParser.json(), async (req, res) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); if (!req.body) throw new HTTPError("Invalid Body"); const { url } = req.body; if (!url || typeof url !== "string") throw new HTTPError("Invalid url"); From 066bd9fa21fade4f0bde7b07111424119855dc12 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 30 May 2021 01:46:28 +0200 Subject: [PATCH 27/53] :arrow_up: npm i @fosscord/server-util@1.3.11 --- package-lock.json | Bin 108526 -> 108526 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index fe21ab207c26cb4e659c76881e0e92a001a0ebd8..4736191da3a375e6097c3ed1bc314d0d232f978a 100644 GIT binary patch delta 231 zcmaENp6%Uvwh5mnpNW&$%*}X$X|hqc{^UDPG$$Y2st}S=;8E%4Vp-;6QeGaOnG#x5 zQdVi^RpsTL=~P(dXX+Oml9E*DuAS*+5MEFo;NuwNZxZ2G5^3O(o0b#iZt113?eCqN u7@1KKmFrk$oL3r=<>ET|gTLbD11xXvBij^83!4h2J9aQCZ=U-!@DKoc^jP@- delta 238 zcmaENp6%Uvwh5mZ4JMzB6W`3uc!FtipqBpRJ5Mwxv&t)k7L;m7IOeAo<>-g|xK-r) zRfHJ&g_-+h83cxC2WF;wo2F+J8HPDWWc!)==@*1LdWBXxn;WHh1w>|Rn}((vI45~! x2P7Fp_!S3*c@>maS!5do8BK0HtFZY1%iH_3w`w}0G^5Paen$4qbDstt0sue|Q|ABx diff --git a/package.json b/package.json index a6ac35ce..b523cbce 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/discord-open-source/discord-cdn#readme", "dependencies": { - "@fosscord/server-util": "^1.3.10", + "@fosscord/server-util": "^1.3.11", "body-parser": "^1.19.0", "btoa": "^1.2.1", "cheerio": "^1.0.0-rc.5", From 70db286481acbec4e2de98d3aaa9a1117c13b73c Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 30 May 2021 16:46:23 +0200 Subject: [PATCH 28/53] :arrow_up: npm i @fosscord/server-util@1.3.12 --- package-lock.json | Bin 108526 -> 108526 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 4736191da3a375e6097c3ed1bc314d0d232f978a..caa611aae3857114f3973586ae2c3d63b097a046 100644 GIT binary patch delta 234 zcmaENp6%Uvwh5mZjV7Os6W`3uc!FuN;AQ>Ecb;fYUTC8bTH&E@Qs|vlWNcDpkX?|U zlu=P-7!qEZ=M$V0RALd}?Br~bTWMxtVN&D~l3N;7kre3}8kpp4Y!YUWnrZCg7v!y9 tX;M{U=ET|gTLbD11xXvBij^83!4h2J9aQCZ=U-!@DKoc^jP@- diff --git a/package.json b/package.json index b523cbce..0ef25947 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/discord-open-source/discord-cdn#readme", "dependencies": { - "@fosscord/server-util": "^1.3.11", + "@fosscord/server-util": "^1.3.12", "body-parser": "^1.19.0", "btoa": "^1.2.1", "cheerio": "^1.0.0-rc.5", From c726aeb81e9996c8b1e5dc9cddfc0a04df924cec Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 30 May 2021 17:06:05 +0200 Subject: [PATCH 29/53] :arrow_up: npm i @fosscord/server-util@1.3.13 --- package-lock.json | Bin 108526 -> 108526 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index caa611aae3857114f3973586ae2c3d63b097a046..8aa6b760b8c2afa88fc83e1c72989f19c5794c25 100644 GIT binary patch delta 252 zcmaENp6%Uvwh5mZjVGUt6W`3uc!Fv2!IuV;?>^C({%{7P%wz*ImQc3{zx2YyWdD*P zi()tTk`!$hgPdaDl<*|W2otALCug&?(1Mf*KeLRgQnR4qsF3nduc++c(iD9kI}^?UTP*zuDx(Bi;D E05slT>i_@% delta 234 zcmaENp6%Uvwh5mZjV7Os6W`3uc!FuN;AQ>Ecb;fYUTC8bTH&E@Qs|vlWNcDpkX?|U zlu=P-7!qEZ=M$V0RALd}?Br~bTWMxtVN&D~l3N;7kre3}8kpp4Y!YUWnrZCg7v!y9 tX;M{U= Date: Sun, 30 May 2021 22:15:01 +0200 Subject: [PATCH 30/53] npm i @fosscord/server-util@1.3.14 --- package-lock.json | Bin 108526 -> 109852 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 8aa6b760b8c2afa88fc83e1c72989f19c5794c25..0c0b45d24984ec907b9f11b3ea4994557ecb1466 100644 GIT binary patch delta 610 zcmaENo^8%8wh5mZO(vg>6W`3uc!Fv2B4ho@cb;fYzG$xy7GPNzXpxj`U>Ka05#;8U zRc;vWW*lfzR8XE|5}ap|=2Bknubq{epW;}R;qK;Q7?73Wl`qK+i7{#V1D>5pC8s-H%d#4AN=2hkCJ4ckK`-i6|1$cyp`}>>v zr@00tm->f$2Im&0m>C*m4zW_pDsCKaX^1Oy~Gnq~N#7rC2Qq!wzY zxx2a;`%Z2=tFSp{iaXQh7Bb%7 delta 283 zcmbPpi|yTcwh5mZjVBum^hU>Ih&=07NkV@nPpU!ngta{g_MVSMP&zYxj gvhATXv3>f-7Dk!L0e{#xy>!HI1n1`JHwzB{0HFhDe*gdg diff --git a/package.json b/package.json index c30f3a64..fe8e0bf1 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/discord-open-source/discord-cdn#readme", "dependencies": { - "@fosscord/server-util": "^1.3.13", + "@fosscord/server-util": "^1.3.14", "body-parser": "^1.19.0", "btoa": "^1.2.1", "cheerio": "^1.0.0-rc.5", From f0d59bb37d131f361adc9beb5347f3ad6d2ed644 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 31 May 2021 20:41:44 +0200 Subject: [PATCH 31/53] :sparkles: start.ts file --- package-lock.json | Bin 109852 -> 109852 bytes package.json | 4 ++-- src/index.ts | 18 +----------------- src/start.ts | 17 +++++++++++++++++ 4 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 src/start.ts diff --git a/package-lock.json b/package-lock.json index 0c0b45d24984ec907b9f11b3ea4994557ecb1466..5c2ff2f217d6514a7e23689dac2e92169ef5316b 100644 GIT binary patch delta 247 zcmbPpi*3#=whcl|j3(39M>A?|wq@GZJbjV^qyF@sC`Og(BFh;SLd{cLB21!6yiKDl zl3c>_y*>TREA`DYv`x~Dlfq3yLqn1Re8V!m-2F3sGhB@=lPr8IO9GrKL!ydZUA2Q! z^@F1l-E&=C%bhY(T`fJ_4Fa=LOe-fh-ci_myRGRmU2L14xP(z=^6j=&+h0dB8U_LY D1=&-* delta 244 zcmbPpi*3#=whcl|lMk_KZ?nS8r#)%MrXjD~>#-!oIu diff --git a/package.json b/package.json index fe8e0bf1..580f837f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc -b .", - "start": "npm run build && node dist/" + "start": "npm run build && node dist/start.js" }, "repository": { "type": "git", @@ -30,7 +30,7 @@ "file-type": "^16.5.0", "image-size": "^1.0.0", "lambert-db": "^1.2.3", - "lambert-server": "^1.2.1", + "lambert-server": "^1.2.4", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", "node-fetch": "^2.6.1", diff --git a/src/index.ts b/src/index.ts index 72175f46..7513bd2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1 @@ -import { CDNServer } from "./Server"; -import dotenv from "dotenv"; -dotenv.config(); - -if (process.env.STORAGE_LOCATION) { - if (!process.env.STORAGE_LOCATION.startsWith("/")) { - process.env.STORAGE_LOCATION = __dirname + "/../" + process.env.STORAGE_LOCATION; - } -} else process.env.STORAGE_LOCATION = __dirname + "/../files/"; - -const server = new CDNServer({ port: Number(process.env.PORT) || 3003 }); -server - .start() - .then(() => { - console.log("[Server] started on :" + server.options.port); - }) - .catch((e) => console.error("[Server] Error starting: ", e)); +export * from "./Server"; diff --git a/src/start.ts b/src/start.ts new file mode 100644 index 00000000..72175f46 --- /dev/null +++ b/src/start.ts @@ -0,0 +1,17 @@ +import { CDNServer } from "./Server"; +import dotenv from "dotenv"; +dotenv.config(); + +if (process.env.STORAGE_LOCATION) { + if (!process.env.STORAGE_LOCATION.startsWith("/")) { + process.env.STORAGE_LOCATION = __dirname + "/../" + process.env.STORAGE_LOCATION; + } +} else process.env.STORAGE_LOCATION = __dirname + "/../files/"; + +const server = new CDNServer({ port: Number(process.env.PORT) || 3003 }); +server + .start() + .then(() => { + console.log("[Server] started on :" + server.options.port); + }) + .catch((e) => console.error("[Server] Error starting: ", e)); From e4fc61dc347cc54d81eded31000377ee9e78e888 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 31 May 2021 21:01:28 +0200 Subject: [PATCH 32/53] :zap: add explicit types to req and res --- src/routes/attachments.ts | 8 ++++---- src/routes/avatars.ts | 8 ++++---- src/routes/external.ts | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index e99b8d87..2e635b43 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import { Router, Response, Request } from "express"; import { Config, Snowflake } from "@fosscord/server-util"; import { storage } from "../util/Storage"; import FileType from "file-type"; @@ -8,7 +8,7 @@ import imageSize from "image-size"; const router = Router(); -router.post("/:channel_id", multer.single("file"), async (req, res) => { +router.post("/:channel_id", multer.single("file"), async (req: Request, res: Response) => { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); @@ -44,7 +44,7 @@ router.post("/:channel_id", multer.single("file"), async (req, res) => { return res.json(file); }); -router.get("/:channel_id/:id/:filename", async (req, res) => { +router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => { const { channel_id, id, filename } = req.params; const file = await storage.get(`attachments/${channel_id}/${id}/${filename}`); @@ -56,7 +56,7 @@ router.get("/:channel_id/:id/:filename", async (req, res) => { return res.send(file); }); -router.delete("/:channel_id/:id/:filename", async (req, res) => { +router.delete("/:channel_id/:id/:filename", async (req: Request, res: Response) => { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts index 973c45fc..321ae02e 100644 --- a/src/routes/avatars.ts +++ b/src/routes/avatars.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import { Router, Response, Request } from "express"; import { Config, Snowflake } from "@fosscord/server-util"; import { storage } from "../util/Storage"; import FileType from "file-type"; @@ -18,7 +18,7 @@ const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); -router.post("/:user_id", multer.single("file"), async (req, res) => { +router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); if (!req.file) throw new HTTPError("Missing file"); @@ -42,7 +42,7 @@ router.post("/:user_id", multer.single("file"), async (req, res) => { }); }); -router.get("/:user_id/:id", async (req, res) => { +router.get("/:user_id/:id", async (req: Request, res: Response) => { var { user_id, id } = req.params; id = id.split(".")[0]; const path = `avatars/${user_id}/${id}`; @@ -56,7 +56,7 @@ router.get("/:user_id/:id", async (req, res) => { return res.send(file); }); -router.delete("/:user_id/:id", async (req, res) => { +router.delete("/:user_id/:id", async (req: Request, res: Response) => { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); const { user_id, id } = req.params; diff --git a/src/routes/external.ts b/src/routes/external.ts index dcf56c8c..3abe9c22 100644 --- a/src/routes/external.ts +++ b/src/routes/external.ts @@ -1,6 +1,6 @@ // @ts-nocheck import bodyParser from "body-parser"; -import { Router } from "express"; +import { Router, Response, Request } from "express"; import fetch from "node-fetch"; import crypto from "crypto"; import { HTTPError } from "lambert-server"; @@ -29,7 +29,7 @@ const DEFAULT_FETCH_OPTIONS: any = { method: "GET", }; -router.post("/", bodyParser.json(), async (req, res) => { +router.post("/", bodyParser.json(), async (req: Request, res: Response) => { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); if (!req.body) throw new HTTPError("Invalid Body"); @@ -50,7 +50,7 @@ router.post("/", bodyParser.json(), async (req, res) => { } }); -router.get("/:id/", async (req, res) => { +router.get("/:id/", async (req: Request, res: Response) => { const { id } = req.params; const file = await storage.get(`/external/${id}`); From c845d19226e1a261b062784132cd5d3d70dd9a96 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Wed, 23 Jun 2021 19:15:37 +0200 Subject: [PATCH 33/53] :bug: fix .env file not loading --- package-lock.json | Bin 109852 -> 111588 bytes package.json | 5 +++-- src/start.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c2ff2f217d6514a7e23689dac2e92169ef5316b..e296e974feacfa4320f90cfc79902b3201c0e698 100644 GIT binary patch delta 7724 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 Iq4PWc0|s~-rvLx| delta 7363 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 diff --git a/package.json b/package.json index 580f837f..a6bc580d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "@fosscord/cdn", "version": "1.0.0", "description": "cdn for discord clone", - "main": "index.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc -b .", @@ -30,7 +31,7 @@ "file-type": "^16.5.0", "image-size": "^1.0.0", "lambert-db": "^1.2.3", - "lambert-server": "^1.2.4", + "lambert-server": "^1.2.5", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", "node-fetch": "^2.6.1", diff --git a/src/start.ts b/src/start.ts index 72175f46..54228a5f 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,7 +1,8 @@ -import { CDNServer } from "./Server"; import dotenv from "dotenv"; dotenv.config(); +import { CDNServer } from "./Server"; + if (process.env.STORAGE_LOCATION) { if (!process.env.STORAGE_LOCATION.startsWith("/")) { process.env.STORAGE_LOCATION = __dirname + "/../" + process.env.STORAGE_LOCATION; From 7b84ef1aa027cf9a0defc90044012e625fe2e427 Mon Sep 17 00:00:00 2001 From: Joaquim Peixoto Date: Fri, 25 Jun 2021 20:20:00 +0100 Subject: [PATCH 34/53] FIX MKDIR --- .env.example | 3 --- package-lock.json | Bin 111588 -> 50726 bytes src/util/FileStorage.ts | 40 ++++++++++++++++++++-------------------- 3 files changed, 20 insertions(+), 23 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index b5e011f1..00000000 --- a/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -STORAGE_LOCATION=files/ -STORAGE_PROVIDER=file -PORT=3003 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e296e974feacfa4320f90cfc79902b3201c0e698..5c56d9e3efe42bab730fe776f638e41d1dc5cb47 100644 GIT binary patch delta 58 zcmaFzoNZYT^8{BhLme(orJ~fr(#)dNVkIkulA_X79j?iW+M=7kzBLZpZtcSO-DtaO OIin2Y_L2(50&4(jD-^f@ delta 13625 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 diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 9c9911f3..c6497306 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -4,27 +4,27 @@ import { join } from "path"; import "missing-native-js-functions"; export class FileStorage implements Storage { - async get(path: string): Promise { - path = join(process.env.STORAGE_LOCATION || "", path); - try { - const file = await fs.readFile(path); - // @ts-ignore - return file; - } catch (error) { - return null; - } - } + async get(path: string): Promise { + path = join(process.env.STORAGE_LOCATION || "", path); + try { + const file = await fs.readFile(path); + // @ts-ignore + return file; + } catch (error) { + return null; + } + } - async set(path: string, value: any) { - path = join(process.env.STORAGE_LOCATION || "", path); - const dir = path.split("/").slice(0, -1).join("/"); - await fs.mkdir(dir, { recursive: true }).caught(); + async set(path: string, value: any) { + path = join(process.env.STORAGE_LOCATION || "", path).replace(/[\\]/g, "/"); + const dir = path.split("/").slice(0, -1).join("/"); + await fs.mkdir(dir, { recursive: true }).caught(); - return fs.writeFile(path, value, { encoding: "binary" }); - } + return fs.writeFile(path, value, { encoding: "binary" }); + } - async delete(path: string) { - path = join(process.env.STORAGE_LOCATION || "", path); - await fs.unlink(path); - } + async delete(path: string) { + path = join(process.env.STORAGE_LOCATION || "", path); + await fs.unlink(path); + } } From 1a62f1dd8e77220bf86526349ae0ac54e0336464 Mon Sep 17 00:00:00 2001 From: Joaquim Peixoto Date: Fri, 25 Jun 2021 20:26:34 +0100 Subject: [PATCH 35/53] FIXS --- .env.example | 3 +++ src/util/FileStorage.ts | 42 ++++++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..b5e011f1 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +STORAGE_LOCATION=files/ +STORAGE_PROVIDER=file +PORT=3003 \ No newline at end of file diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index c6497306..42b32528 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -4,27 +4,27 @@ import { join } from "path"; import "missing-native-js-functions"; export class FileStorage implements Storage { - async get(path: string): Promise { - path = join(process.env.STORAGE_LOCATION || "", path); - try { - const file = await fs.readFile(path); - // @ts-ignore - return file; - } catch (error) { - return null; - } - } + async get(path: string): Promise { + path = join(process.env.STORAGE_LOCATION || "", path); + try { + const file = await fs.readFile(path); + // @ts-ignore + return file; + } catch (error) { + return null; + } + } - async set(path: string, value: any) { - path = join(process.env.STORAGE_LOCATION || "", path).replace(/[\\]/g, "/"); - const dir = path.split("/").slice(0, -1).join("/"); - await fs.mkdir(dir, { recursive: true }).caught(); + async set(path: string, value: any) { + path = join(process.env.STORAGE_LOCATION || "", path).replace(/[\\]/g, "/"); + const dir = path.split("/").slice(0, -1).join("/"); + await fs.mkdir(dir, { recursive: true }).caught(); - return fs.writeFile(path, value, { encoding: "binary" }); - } + return fs.writeFile(path, value, { encoding: "binary" }); + } - async delete(path: string) { - path = join(process.env.STORAGE_LOCATION || "", path); - await fs.unlink(path); - } -} + async delete(path: string) { + path = join(process.env.STORAGE_LOCATION || "", path); + await fs.unlink(path); + } +} \ No newline at end of file From b874e999ab0a7d3d0954d3a32e8593c5dab63895 Mon Sep 17 00:00:00 2001 From: Joaquim Peixoto Date: Fri, 25 Jun 2021 20:28:59 +0100 Subject: [PATCH 36/53] 4 TABS --- src/util/FileStorage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 42b32528..48b4a6a5 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -17,14 +17,14 @@ export class FileStorage implements Storage { async set(path: string, value: any) { path = join(process.env.STORAGE_LOCATION || "", path).replace(/[\\]/g, "/"); - const dir = path.split("/").slice(0, -1).join("/"); - await fs.mkdir(dir, { recursive: true }).caught(); + const dir = path.split("/").slice(0, -1).join("/"); + await fs.mkdir(dir, { recursive: true }).caught(); - return fs.writeFile(path, value, { encoding: "binary" }); + return fs.writeFile(path, value, { encoding: "binary" }); } async delete(path: string) { path = join(process.env.STORAGE_LOCATION || "", path); await fs.unlink(path); } -} \ No newline at end of file +} From 3de6cf003e40690b55f18763275f0920d8e780ce Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 10 Jul 2021 19:02:24 +0200 Subject: [PATCH 37/53] :wheelchair: use fs sync for backwards compatiblity --- src/util/FileStorage.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 48b4a6a5..b87c4651 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -1,5 +1,5 @@ import { Storage } from "./Storage"; -import fs from "fs/promises"; +import fs from "fs"; import { join } from "path"; import "missing-native-js-functions"; @@ -7,7 +7,7 @@ export class FileStorage implements Storage { async get(path: string): Promise { path = join(process.env.STORAGE_LOCATION || "", path); try { - const file = await fs.readFile(path); + const file = fs.readFileSync(path); // @ts-ignore return file; } catch (error) { @@ -18,13 +18,13 @@ export class FileStorage implements Storage { async set(path: string, value: any) { path = join(process.env.STORAGE_LOCATION || "", path).replace(/[\\]/g, "/"); const dir = path.split("/").slice(0, -1).join("/"); - await fs.mkdir(dir, { recursive: true }).caught(); + fs.mkdirSync(dir, { recursive: true }); - return fs.writeFile(path, value, { encoding: "binary" }); + return fs.writeFileSync(path, value, { encoding: "binary" }); } async delete(path: string) { path = join(process.env.STORAGE_LOCATION || "", path); - await fs.unlink(path); + fs.unlinkSync(path); } } From 358bf873296d4d4e84611d3f3d5ae48eabfc8ad6 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 10 Jul 2021 19:02:32 +0200 Subject: [PATCH 38/53] :zap: cache assets --- src/routes/attachments.ts | 1 + src/routes/avatars.ts | 1 + src/start.ts | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 2e635b43..acc7604d 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -52,6 +52,7 @@ router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => const type = await FileType.fromBuffer(file); res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); return res.send(file); }); diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts index 321ae02e..fea7c5f4 100644 --- a/src/routes/avatars.ts +++ b/src/routes/avatars.ts @@ -52,6 +52,7 @@ router.get("/:user_id/:id", async (req: Request, res: Response) => { const type = await FileType.fromBuffer(file); res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); return res.send(file); }); diff --git a/src/start.ts b/src/start.ts index 54228a5f..822a749f 100644 --- a/src/start.ts +++ b/src/start.ts @@ -7,7 +7,10 @@ if (process.env.STORAGE_LOCATION) { if (!process.env.STORAGE_LOCATION.startsWith("/")) { process.env.STORAGE_LOCATION = __dirname + "/../" + process.env.STORAGE_LOCATION; } -} else process.env.STORAGE_LOCATION = __dirname + "/../files/"; +} else { + process.env.STORAGE_LOCATION = __dirname + "/../files/"; + process.env.STORAGE_PROVIDER = "file"; +} const server = new CDNServer({ port: Number(process.env.PORT) || 3003 }); server From 4d7ce61ce5f3f51fcb29848f3bfd5304082043d4 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 16 Jul 2021 16:15:59 +0200 Subject: [PATCH 39/53] :arrow_up: update lambert-server --- package-lock.json | Bin 50726 -> 109248 bytes package.json | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c56d9e3efe42bab730fe776f638e41d1dc5cb47..12355e04b4931ad7985be2dc795654977b3b8935 100644 GIT binary patch delta 11357 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 delta 287 zcmV+)0pR|?)dr@F1CT`)F)RuRB64MMb!l>Cb0Rt*baHiNEDDoBe-*RfKYW2;_M(+Ibts`VK_>8a#ApIGHy|5OgA%1QaCnmVpmKzV{d0> zSTQnTb!T>La&}{IV`)QgFKBCaMRR9$I51i?Q!-~VL`X+gSV(d>a8*fZF;-}CMoKtU zW_Odp9T}5w@fou{`fyv57?CfN;D891kgldvNMR$5|WPcb!0XHaKtacxsm zMtE~eR!nz7Lu5rwb~RQoPeLwt>g;VdOXP5v0 diff --git a/package.json b/package.json index a6bc580d..7e623223 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "file-type": "^16.5.0", "image-size": "^1.0.0", "lambert-db": "^1.2.3", - "lambert-server": "^1.2.5", + "lambert-server": "^1.2.7", "missing-native-js-functions": "^1.0.8", "multer": "^1.4.2", "node-fetch": "^2.6.1", @@ -40,8 +40,9 @@ "devDependencies": { "@types/body-parser": "^1.19.0", "@types/btoa": "^1.2.3", + "@types/dotenv": "^8.2.0", "@types/express": "^4.17.12", - "@types/multer": "^1.4.5", + "@types/multer": "^1.4.7", "@types/node": "^14.17.0", "@types/node-fetch": "^2.5.7", "@types/uuid": "^8.3.0" From 1754557af46f8b24a7277fb1e5fe77a75cb6688b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 18 Jul 2021 19:51:19 +0200 Subject: [PATCH 40/53] fix attachment --- package-lock.json | Bin 109248 -> 52718 bytes package.json | 2 +- src/routes/attachments.ts | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 12355e04b4931ad7985be2dc795654977b3b8935..a12b2477a582446453efe6ced4da4dcf71278ec4 100644 GIT binary patch delta 1195 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 { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) return; const { buffer, mimetype, size, originalname, fieldname } = req.file; const { channel_id } = req.params; From 295202de90c27bd9fafa5549779da38bfd12fa4c Mon Sep 17 00:00:00 2001 From: Diego Magdaleno <38844659+DiegoMagdaleno@users.noreply.github.com> Date: Tue, 20 Jul 2021 16:05:12 -0500 Subject: [PATCH 41/53] [Add] Add working Dockerfile --- Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..d9ad78f4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:lts-alpine +WORKDIR /usr/src/fosscord-cdn +COPY package.json . +RUN npm install +COPY . . +EXPOSE 3003 +CMD ["node", "dist/"] \ No newline at end of file From a6eac742365f23b784214a4d6236133f91a718a5 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 13:15:26 +0200 Subject: [PATCH 42/53] :lock: fix path traversal security issue --- src/util/FileStorage.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index b87c4651..8001c608 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -1,26 +1,24 @@ import { Storage } from "./Storage"; import fs from "fs"; -import { join } from "path"; +import { join, relative } from "path"; import "missing-native-js-functions"; +function getPath(path: string) { + // STORAGE_LOCATION has a default value in start.ts + return join(process.env.STORAGE_LOCATION || "../", relative("/", path)); +} + export class FileStorage implements Storage { async get(path: string): Promise { - path = join(process.env.STORAGE_LOCATION || "", path); try { - const file = fs.readFileSync(path); - // @ts-ignore - return file; + return fs.readFileSync(getPath(path)); } catch (error) { return null; } } async set(path: string, value: any) { - path = join(process.env.STORAGE_LOCATION || "", path).replace(/[\\]/g, "/"); - const dir = path.split("/").slice(0, -1).join("/"); - fs.mkdirSync(dir, { recursive: true }); - - return fs.writeFileSync(path, value, { encoding: "binary" }); + return fs.writeFileSync(getPath(path), value, { encoding: "binary" }); } async delete(path: string) { From ede8ed4b715a0167a63edcc15cdc5d4c644356c5 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 13:15:35 +0200 Subject: [PATCH 43/53] :sparkles: icons + banners route --- package-lock.json | Bin 52718 -> 119374 bytes package.json | 2 ++ src/Server.ts | 15 +++++---------- src/routes/attachments.ts | 2 +- src/routes/avatars.ts | 6 +++--- src/start.ts | 22 ++++++++++++++-------- src/util/multer.ts | 10 ++++++++++ 7 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 src/util/multer.ts diff --git a/package-lock.json b/package-lock.json index a12b2477a582446453efe6ced4da4dcf71278ec4..78f760abc606de8b1d888c98348506e4c328aabe 100644 GIT binary patch delta 16086 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

!ZS78Pt_z^Y=3;yg; z3p~7UI(&+1LR;r)VZj|caDDavAHwB%2!krJ9r8G){1Bq>S5UDW>_$nwwa-|v6Y}VY z0e*4kw)`j#Nc8<2;i60q)MkL~>$LDKI^d8S>I57!8?Oi0Bi;`I0es z5!sT-tb<_9);}yh;=q4D_z!i~D7&8@KO_rJf^Qzo6Wa}v z4#`_{^A4T63|MD8{E|$7-+uVQRP*9tC*hL21uIw|)kpK$iWdFUBeOKp00))B`U57E zg8%mcJ%409Y)JLaucX;bDvB{J? zoSvohM9w|_aZFhlMZA4PpUHu$XyyC0aPbMT(s<@c=Vg#Ov3KoH`1REs{y-{$!Q3PB zMWNsHqXs2!HS``u9#2p@Qx!sy50jN>a*t#sM|g>XS_(Jhz5k;`ppPzvgKG>LJM@qX zV9V2@yZGH}=0deK4ZeIj8NNK64%tT&%hY2im2%9gqq3Iep#7Leb6FJR${&BC8o=7} zl^t-YbJR5Q8)*(hnD&i3Pc_fHBNYb73VwyfWbw1})zzz_I78#J?}vJQ9KgrTGDmWg<(5v*mAgH8NpachF4p<)*yiz?} zdt3`&KEEwh?v=vb*^A)z7v9E1a{tt&&UkfP;Deujl_m*{INHVIa{h(mCuK9}mrU>< zgbu7-)ApaXz;|A3m2t5B#a|=j^sxG+h0@e2`aK5k=Y)aAMT`YdT_o}=7(7=Gc9yqu^*zOH0h_JeZ>y@dZW!3o)mO|g>)JhQ z+uNF}+S>!wHEY_*&7CbJHQj3p8!BDwW?O3m(cjAUdIMVunVv$<+1S%#ZLex9Z4bDu z1*}jTQN52rXgGn&1GCd20~kN!rM+WywC7&i5EI{&pTn$EV%+!5sazTV46n;w>EwAJ6D?!NI z!q8sri1+JRasP6^lgop$j5xS4Wqy!J{&fSq(>q(UD_FMeuQMW0jiupa^A%}0f#Krp z%87-(OKvuSjg4~>#^GFQhsc&I)FBd_?<`Nc3iZmUm0mYrkCb0Rt*baHiNEDDox4;8cN{{9WO9u5K0 oO}7JF0qO>~HDv)d47a6n0oNe6kCOo$0k^)C0T(y7%FF?T1?v1ByZ`_I diff --git a/package.json b/package.json index e63787b9..97033983 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "express": "^4.17.1", "express-async-errors": "^3.1.1", "file-type": "^16.5.0", + "fs-extra": "^10.0.0", "image-size": "^1.0.0", "lambert-db": "^1.2.3", "lambert-server": "^1.2.8", @@ -42,6 +43,7 @@ "@types/btoa": "^1.2.3", "@types/dotenv": "^8.2.0", "@types/express": "^4.17.12", + "@types/fs-extra": "^9.0.12", "@types/multer": "^1.4.7", "@types/node": "^14.17.0", "@types/node-fetch": "^2.5.7", diff --git a/src/Server.ts b/src/Server.ts index 57dfa536..9996f07a 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,7 +1,7 @@ import { Server, ServerOptions } from "lambert-server"; import { Config, db } from "@fosscord/server-util"; import path from "path"; -import multerConfig from "multer"; +import avatarsRoute from "./routes/avatars"; export interface CDNServerOptions extends ServerOptions {} @@ -20,6 +20,10 @@ export class CDNServer extends Server { console.log("[Database] connected"); await this.registerRoutes(path.join(__dirname, "routes/")); + this.app.use("/icons/", avatarsRoute); + this.log("info", "[Server] Route /icons registered"); + this.app.use("/banners/", avatarsRoute); + this.log("info", "[Server] Route /banners registered"); return super.start(); } @@ -27,12 +31,3 @@ export class CDNServer extends Server { return super.stop(); } } - -export const multer = multerConfig({ - storage: multerConfig.memoryStorage(), - limits: { - fields: 10, - files: 10, - fileSize: 1024 * 1024 * 100, // 100 mb - }, -}); diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 9d43a921..9d208485 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -3,7 +3,7 @@ import { Config, Snowflake } from "@fosscord/server-util"; import { storage } from "../util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; -import { multer } from "../Server"; +import { multer } from "../util/multer"; import imageSize from "image-size"; const router = Router(); diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts index fea7c5f4..476daacd 100644 --- a/src/routes/avatars.ts +++ b/src/routes/avatars.ts @@ -3,7 +3,7 @@ import { Config, Snowflake } from "@fosscord/server-util"; import { storage } from "../util/Storage"; import FileType from "file-type"; import { HTTPError } from "lambert-server"; -import { multer } from "../Server"; +import { multer } from "../util/multer"; import crypto from "crypto"; // TODO: check premium and animated pfp are allowed in the config @@ -13,7 +13,7 @@ import crypto from "crypto"; // TODO: check request signature for modify methods const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; -const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp"]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); @@ -38,7 +38,7 @@ router.post("/:user_id", multer.single("file"), async (req: Request, res: Respon id, content_type: type.mime, size, - url: `${endpoint}/path`, + url: `${endpoint}${req.baseUrl}/${user_id}/${id}`, }); }); diff --git a/src/start.ts b/src/start.ts index 822a749f..57c9f704 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,17 +1,23 @@ +import path from "path"; import dotenv from "dotenv"; +import fse from "fs-extra"; dotenv.config(); -import { CDNServer } from "./Server"; - -if (process.env.STORAGE_LOCATION) { - if (!process.env.STORAGE_LOCATION.startsWith("/")) { - process.env.STORAGE_LOCATION = __dirname + "/../" + process.env.STORAGE_LOCATION; +if (!process.env.STORAGE_PROVIDER) process.env.STORAGE_PROVIDER = "file"; +// TODO:nodejs path.join trailing slash windows compatible +if (process.env.STORAGE_PROVIDER === "file") { + if (process.env.STORAGE_LOCATION) { + if (!process.env.STORAGE_LOCATION.startsWith("/")) { + process.env.STORAGE_LOCATION = path.join(__dirname, "..", process.env.STORAGE_LOCATION, "/"); + } + } else { + process.env.STORAGE_LOCATION = path.join(__dirname, "..", "files", "/"); } -} else { - process.env.STORAGE_LOCATION = __dirname + "/../files/"; - process.env.STORAGE_PROVIDER = "file"; + fse.ensureDirSync(process.env.STORAGE_LOCATION); } +import { CDNServer } from "./Server"; + const server = new CDNServer({ port: Number(process.env.PORT) || 3003 }); server .start() diff --git a/src/util/multer.ts b/src/util/multer.ts new file mode 100644 index 00000000..bfdf6aff --- /dev/null +++ b/src/util/multer.ts @@ -0,0 +1,10 @@ +import multerConfig from "multer"; + +export const multer = multerConfig({ + storage: multerConfig.memoryStorage(), + limits: { + fields: 10, + files: 10, + fileSize: 1024 * 1024 * 100, // 100 mb + }, +}); From 3bc8a96afdeb337e6d6972e0c989b11bcdbbe70e Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 15:44:56 +0200 Subject: [PATCH 44/53] :bug: fix path --- src/Server.ts | 15 +++++++++++++++ src/util/FileStorage.ts | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Server.ts b/src/Server.ts index 9996f07a..f79437d5 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -20,10 +20,25 @@ export class CDNServer extends Server { console.log("[Database] connected"); await this.registerRoutes(path.join(__dirname, "routes/")); + this.app.use("/icons/", avatarsRoute); this.log("info", "[Server] Route /icons registered"); + + this.app.use("/emojis/", avatarsRoute); + this.log("info", "[Server] Route /emojis registered"); + this.app.use("/banners/", avatarsRoute); this.log("info", "[Server] Route /banners registered"); + + this.app.use("/banners/", avatarsRoute); + this.log("info", "[Server] Route /banners registered"); + + this.app.use("/discover-splashes/", avatarsRoute); + this.log("info", "[Server] Route /discover-splashes registered"); + + this.app.use("/team-icons/", avatarsRoute); + this.log("info", "[Server] Route /team-icons registered"); + return super.start(); } diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 8001c608..119e990f 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -4,8 +4,9 @@ import { join, relative } from "path"; import "missing-native-js-functions"; function getPath(path: string) { + if (path.indexOf("\0") !== -1 || !/^[a-z0-9]+$/.test(path)) throw new Error("invalid path"); // STORAGE_LOCATION has a default value in start.ts - return join(process.env.STORAGE_LOCATION || "../", relative("/", path)); + return join(process.env.STORAGE_LOCATION || "../", path); } export class FileStorage implements Storage { @@ -22,7 +23,6 @@ export class FileStorage implements Storage { } async delete(path: string) { - path = join(process.env.STORAGE_LOCATION || "", path); - fs.unlinkSync(path); + fs.unlinkSync(getPath(path)); } } From e2d142b237a05cbe7a9c2bd3975ec0fbc9c49ce8 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 15:51:46 +0200 Subject: [PATCH 45/53] :bug: fix cors --- src/Server.ts | 11 +++++++++++ src/util/FileStorage.ts | 13 ++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Server.ts b/src/Server.ts index f79437d5..f876a719 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -18,6 +18,17 @@ export class CDNServer extends Server { await (db as Promise); await Config.init(); console.log("[Database] connected"); + this.app.use((req, res, next) => { + res.set("Access-Control-Allow-Origin", "*"); + // TODO: use better CSP policy + res.set( + "Content-security-policy", + "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';" + ); + res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); + res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); + next(); + }); await this.registerRoutes(path.join(__dirname, "routes/")); diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 119e990f..4a449d5a 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -4,9 +4,12 @@ import { join, relative } from "path"; import "missing-native-js-functions"; function getPath(path: string) { - if (path.indexOf("\0") !== -1 || !/^[a-z0-9]+$/.test(path)) throw new Error("invalid path"); // STORAGE_LOCATION has a default value in start.ts - return join(process.env.STORAGE_LOCATION || "../", path); + const root = process.env.STORAGE_LOCATION || "../"; + var filename = join(root, path); + + if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) throw new Error("invalid path"); + return filename; } export class FileStorage implements Storage { @@ -19,7 +22,11 @@ export class FileStorage implements Storage { } async set(path: string, value: any) { - return fs.writeFileSync(getPath(path), value, { encoding: "binary" }); + path = getPath(path); + const dir = path.split("/").slice(0, -1).join("/"); + fs.mkdirSync(dir, { recursive: true }); + + return fs.writeFileSync(path, value, { encoding: "binary" }); } async delete(path: string) { From 007e6fef78a4e5fd9abdc0c1e7df1b512afaddca Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 18:52:05 +0200 Subject: [PATCH 46/53] :sparkles: add asset routes --- src/Server.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Server.ts b/src/Server.ts index f876a719..23e81506 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -38,12 +38,18 @@ export class CDNServer extends Server { this.app.use("/emojis/", avatarsRoute); this.log("info", "[Server] Route /emojis registered"); - this.app.use("/banners/", avatarsRoute); - this.log("info", "[Server] Route /banners registered"); + this.app.use("/stickers/", avatarsRoute); + this.log("info", "[Server] Route /stickers registered"); this.app.use("/banners/", avatarsRoute); this.log("info", "[Server] Route /banners registered"); + this.app.use("/app-icons/", avatarsRoute); + this.log("info", "[Server] Route /app-icons registered"); + + this.app.use("/app-assets/", avatarsRoute); + this.log("info", "[Server] Route /app-assets registered"); + this.app.use("/discover-splashes/", avatarsRoute); this.log("info", "[Server] Route /discover-splashes registered"); From b4e409f5c6f781a78737ba7b13d57441a874459c Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 18:52:22 +0200 Subject: [PATCH 47/53] :bug: fix file storage dir --- src/routes/avatars.ts | 25 +++++++++++++------------ src/util/FileStorage.ts | 6 +++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts index 476daacd..3d99fb86 100644 --- a/src/routes/avatars.ts +++ b/src/routes/avatars.ts @@ -7,10 +7,9 @@ import { multer } from "../util/multer"; import crypto from "crypto"; // TODO: check premium and animated pfp are allowed in the config -// TODO: generate different sizes of avatar -// TODO: generate different image types of avatar -// TODO: delete old avatars -// TODO: check request signature for modify methods +// TODO: generate different sizes of icon +// TODO: generate different image types of icon +// TODO: delete old icons const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; @@ -25,27 +24,29 @@ router.post("/:user_id", multer.single("file"), async (req: Request, res: Respon const { buffer, mimetype, size, originalname, fieldname } = req.file; const { user_id } = req.params; - const id = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + var hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); const type = await FileType.fromBuffer(buffer); if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); - const path = `avatars/${user_id}/${id}`; + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + + const path = `avatars/${user_id}/${hash}`; const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; await storage.set(path, buffer); return res.json({ - id, + hash, content_type: type.mime, size, - url: `${endpoint}${req.baseUrl}/${user_id}/${id}`, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, }); }); -router.get("/:user_id/:id", async (req: Request, res: Response) => { - var { user_id, id } = req.params; - id = id.split(".")[0]; - const path = `avatars/${user_id}/${id}`; +router.get("/:user_id/:hash", async (req: Request, res: Response) => { + var { user_id, hash } = req.params; + hash = hash.split(".")[0]; // remove .file extension + const path = `avatars/${user_id}/${hash}`; const file = await storage.get(path); if (!file) throw new HTTPError("not found", 404); diff --git a/src/util/FileStorage.ts b/src/util/FileStorage.ts index 4a449d5a..453133f3 100644 --- a/src/util/FileStorage.ts +++ b/src/util/FileStorage.ts @@ -1,6 +1,7 @@ import { Storage } from "./Storage"; import fs from "fs"; -import { join, relative } from "path"; +import fse from "fs-extra"; +import { join, relative, dirname } from "path"; import "missing-native-js-functions"; function getPath(path: string) { @@ -23,8 +24,7 @@ export class FileStorage implements Storage { async set(path: string, value: any) { path = getPath(path); - const dir = path.split("/").slice(0, -1).join("/"); - fs.mkdirSync(dir, { recursive: true }); + fse.ensureDirSync(dirname(path)); return fs.writeFileSync(path, value, { encoding: "binary" }); } From 799596c6f582dc1dac97989ba62270fd66f671f0 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 7 Aug 2021 19:42:05 +0200 Subject: [PATCH 48/53] :bug: fix avatars --- src/routes/avatars.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/avatars.ts b/src/routes/avatars.ts index 3d99fb86..60befe2c 100644 --- a/src/routes/avatars.ts +++ b/src/routes/avatars.ts @@ -36,7 +36,7 @@ router.post("/:user_id", multer.single("file"), async (req: Request, res: Respon await storage.set(path, buffer); return res.json({ - hash, + id: hash, content_type: type.mime, size, url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, From b35e58de83ef77d54ee93424f07bf5bb6bf298fa Mon Sep 17 00:00:00 2001 From: BanTheNons <59115290+BanTheNons@users.noreply.github.com> Date: Sat, 7 Aug 2021 22:13:34 +0300 Subject: [PATCH 49/53] added the /splashes/ route --- src/Server.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Server.ts b/src/Server.ts index 23e81506..1b79b037 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -43,6 +43,9 @@ export class CDNServer extends Server { this.app.use("/banners/", avatarsRoute); this.log("info", "[Server] Route /banners registered"); + + this.app.use("/splashes/", avatarsRoute); + this.log("info", "[Server] Route /splashes registered"); this.app.use("/app-icons/", avatarsRoute); this.log("info", "[Server] Route /app-icons registered"); From 0bcb7164e5e972bb580f54bec8aa2b7a6694c728 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 8 Aug 2021 00:28:48 +0200 Subject: [PATCH 50/53] npm i @fosscord/server-util@1.3.42 --- package-lock.json | Bin 119374 -> 119374 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 78f760abc606de8b1d888c98348506e4c328aabe..37b5d340b3ef8c7f8ba46844ce653c3d803c45cd 100644 GIT binary patch delta 261 zcmX@Ng#FwS_6eU@OpKItrthg{RM{-WD8kHWGWlbf{^U3o4Mvmc8~GU(LbEJ=3jOr6 z%(SaQ^Zm-R^ea-0%YCZ?O!Ey({n9=3oy}Yw9V^W|Leh=>bHanteYE}kDpOsv%<{@C zOEO(b!YrIjeIkkqgTmdC14<0j{6js%&7CGsR8yGTAjQ6wgK-1nWCuz9&5zhjf6&SD O=?m90Dg$kHy959h^Hx3p delta 264 zcmX@Ng#FwS_6eU@3{8}Drthg{RM{-WD8kHWIK9x0QGaqAiw2XS+2o6I@?rYG|$>89qc&f&qPNue%*2L2f##)$oX-+2Txs_fS`k_WiRpI$&Ub(61lM|)XwsJ6TV4UnA$-ns#o9Pcst3zmE U_4EbpjIz@c*csV@*1KH-03Xv=dH?_b diff --git a/package.json b/package.json index 97033983..c2179329 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "homepage": "https://github.com/discord-open-source/discord-cdn#readme", "dependencies": { - "@fosscord/server-util": "^1.3.14", + "@fosscord/server-util": "^1.3.42", "body-parser": "^1.19.0", "btoa": "^1.2.1", "cheerio": "^1.0.0-rc.5", From a349841841225e1147b01727d9a11a09ccc4fe13 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 8 Aug 2021 17:19:14 +0200 Subject: [PATCH 51/53] :sparkles: ping route --- src/routes/ping.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/routes/ping.ts diff --git a/src/routes/ping.ts b/src/routes/ping.ts new file mode 100644 index 00000000..38daf81e --- /dev/null +++ b/src/routes/ping.ts @@ -0,0 +1,9 @@ +import { Router, Response, Request } from "express"; + +const router = Router(); + +router.get("/", (req: Request, res: Response) => { + res.send("pong"); +}); + +export default router; From e8052ce7fc8c37cd8b34cfeb3cd3aeea1f292afe Mon Sep 17 00:00:00 2001 From: xnacly Date: Tue, 10 Aug 2021 16:51:45 +0200 Subject: [PATCH 52/53] added removal of metadata includes all posted assets --- package-lock.json | Bin 119374 -> 121877 bytes package.json | 1 + src/util/FileStorage.ts | 7 ++++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 37b5d340b3ef8c7f8ba46844ce653c3d803c45cd..6a51d710c7d0bc4a5cc574a1616f270772b86db4 100644 GIT binary patch delta 1486 zcmd6l&1=(e7{~c-VC{6csasjyV0N;DUD_q>+C)6qZhcSswkCZ+i%FZb>1*0_P17OF zt9T0%4y z!T5Yk6DcKA8;yX^+eL+n+;F+eQDyp(S=qg07`(smXJV{SR^gjqW*pVk7~NC#rlvtt zGO`eAQ-K036k82+{bhftT|oNTL^IgRfrYok-Em1s8~2=+N98_HHa1lhBT z9)BDO6#W9uan^1znAuw6YVLFf^Q5e)PAgjwWl3p$pHp_t7YFZV|4vSdoiUSN9%F;HCb4X%``B>g#glgK5T>3*8*l0G^WvEnY?6YB?s&1lzIFBNe(T*HgX z*AI)zj@ba|(2Nk%!GRIW%7jrnmOC9;V8#7Kj?$=Y)rP3T0x;$Cuv2&3|J5Fy>gbX~ zt8-ZNjt@qCEezk#65IQ49R90bUa0Z5im1SI^Ml9yr)T5GXPr6~)=MgV9$XnF&w<~@ zrMlo&u>e%(?or02_2J Date: Tue, 10 Aug 2021 20:01:04 +0200 Subject: [PATCH 53/53] catch for missing config error --- src/routes/attachments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/attachments.ts b/src/routes/attachments.ts index 9d208485..c387aa37 100644 --- a/src/routes/attachments.ts +++ b/src/routes/attachments.ts @@ -19,7 +19,7 @@ router.post("/:channel_id", multer.single("file"), async (req: Request, res: Res const id = Snowflake.generate(); const path = `attachments/${channel_id}/${id}/${filename}`; - const endpoint = Config.get().cdn.endpoint || "http://localhost:3003"; + const endpoint = Config.get()?.cdn.endpoint || "http://localhost:3003"; await storage.set(path, buffer); var width;