diff --git a/package-lock.json b/package-lock.json index 48a033a..6d4fbfb 100755 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@tycrek/express-nofavicon": "^1.0.2", "@tycrek/isprod": "^2.0.2", "@tycrek/log": ">=0.5.x", + "@types/escape-html": "^1.0.1", "@types/fs-extra": "^9.0.12", "@types/node": "^14.17.15", "@types/ws": "^7.4.7", @@ -32,7 +33,7 @@ "luxon": "^1.26.0", "marked": "^2.0.7", "multer": "2.0.0-rc.2", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.2", "node-vibrant": "*", "prompt": "^1.1.0", "pug": "^3.0.2", @@ -42,6 +43,10 @@ "ts": "^0.2.2", "uuid": "^8.3.2" }, + "devDependencies": { + "@types/express": "^4.17.13", + "@types/node-fetch": "^2.5.12" + }, "engines": { "node": ">=14.7.x <16", "npm": ">=7.x.x" @@ -724,6 +729,53 @@ "url": "https://patreon.com/tycrek" } }, + "node_modules/@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha512-4mI1FuUUZiuT95fSVqvZxp/ssQK9zsa86S43h9x3zPOSU9BBJ+BfDkXwuaU7BfsD+e7U0/cUUfJFk3iW2M4okA==" + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "node_modules/@types/fs-extra": { "version": "9.0.12", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", @@ -732,11 +784,49 @@ "@types/node": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "node_modules/@types/node": { "version": "14.17.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.15.tgz", "integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA==" }, + "node_modules/@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -2061,6 +2151,20 @@ "ms": "2.0.0" } }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2894,9 +2998,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", "engines": { "node": "4.x || >=6.0.0" } @@ -5105,6 +5209,53 @@ "use-climate-change-reminder": "^0.0.7" } }, + "@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/escape-html": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.1.tgz", + "integrity": "sha512-4mI1FuUUZiuT95fSVqvZxp/ssQK9zsa86S43h9x3zPOSU9BBJ+BfDkXwuaU7BfsD+e7U0/cUUfJFk3iW2M4okA==" + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/fs-extra": { "version": "9.0.12", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", @@ -5113,11 +5264,49 @@ "@types/node": "*" } }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/node": { "version": "14.17.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.15.tgz", "integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA==" }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -6174,6 +6363,17 @@ } } }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6822,9 +7022,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==" }, "node-vibrant": { "version": "3.2.1-alpha.1", diff --git a/package.json b/package.json index 0acb411..66fd71f 100755 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@tycrek/express-nofavicon": "^1.0.2", "@tycrek/isprod": "^2.0.2", "@tycrek/log": ">=0.5.x", + "@types/escape-html": "^1.0.1", "@types/fs-extra": "^9.0.12", "@types/node": "^14.17.15", "@types/ws": "^7.4.7", @@ -59,7 +60,7 @@ "luxon": "^1.26.0", "marked": "^2.0.7", "multer": "2.0.0-rc.2", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.2", "node-vibrant": "*", "prompt": "^1.1.0", "pug": "^3.0.2", @@ -68,5 +69,9 @@ "submodule": "^1.2.1", "ts": "^0.2.2", "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/express": "^4.17.13", + "@types/node-fetch": "^2.5.12" } -} \ No newline at end of file +} diff --git a/src/definitions.d.ts b/src/definitions.d.ts new file mode 100644 index 0000000..5eea687 --- /dev/null +++ b/src/definitions.d.ts @@ -0,0 +1,49 @@ +import { Request, Response } from "express"; + +export interface FileData { + // Data from Multer file object + path: string + size: number + mimetype: string + ext: string + originalname: string + + // Data from ass + randomId: string + deleteId: string + is: IsPossible, + thumbnail: string + vibrant: string + sha1: string + domain: string + timestamp: number + token: string + opengraph: OpenGraphData +} + +export interface IsPossible { + image: boolean + video: boolean + audio: boolean + other: boolean +} + +export interface OpenGraphData { + title?: string + description?: string + author?: string + authorUrl?: string + provider?: string + providerUrl?: string + color?: string +} + +export interface AssRequest extends Request { + resourceId?: string + ass?: { resourceId: string } + +} + +export interface AssResponse extends Response { + +} \ No newline at end of file diff --git a/src/routers/resource.js b/src/routers/resource.ts similarity index 66% rename from src/routers/resource.js rename to src/routers/resource.ts index 7ffd3b4..0d8b565 100644 --- a/src/routers/resource.js +++ b/src/routers/resource.ts @@ -1,6 +1,8 @@ -const fs = require('fs-extra'); -const escape = require('escape-html'); -const fetch = require('node-fetch'); +import { FileData, IsPossible, AssRequest, AssResponse } from '../definitions'; + +import fs from 'fs-extra'; +import escape from 'escape-html'; +import fetch, { Response } from 'node-fetch'; const { deleteS3 } = require('../storage'); const { diskFilePath, s3enabled, viewDirect } = require('../../config.json'); const { path, log, getTrueHttp, getTrueDomain, formatBytes, formatTimestamp, getS3url, getDirectUrl, getResourceColor, replaceholder } = require('../utils'); @@ -8,23 +10,23 @@ const { CODE_UNAUTHORIZED, CODE_NOT_FOUND, } = require('../../MagicNumbers.json' const data = require('../data'); const users = require('../auth'); -const express = require('express'); +import express from 'express'; const router = express.Router(); // Middleware for parsing the resource ID and handling 404 -router.use((req, res, next) => { +router.use((req: AssRequest, res: AssResponse, next) => { // Parse the resource ID req.ass = { resourceId: escape(req.resourceId || '').split('.')[0] }; // If the ID is invalid, return 404. Otherwise, continue normally data.has(req.ass.resourceId) - .then((has) => has ? next() : res.sendStatus(CODE_NOT_FOUND)) // skipcq: JS-0229 + .then((has: boolean) => has ? next() : res.sendStatus(CODE_NOT_FOUND)) // skipcq: JS-0229 .catch(next); }); // View file -router.get('/', (req, res, next) => data.get(req.ass.resourceId).then((fileData) => { - const { resourceId } = req.ass; +router.get('/', (req: AssRequest, res: AssResponse, next) => data.get(req.ass?.resourceId).then((fileData: FileData) => { + const resourceId = req.ass?.resourceId; // Build OpenGraph meta tags const og = fileData.opengraph, ogs = ['']; @@ -53,19 +55,19 @@ router.get('/', (req, res, next) => data.get(req.ass.resourceId).then((fileData) }).catch(next)); // Direct resource -router.get('/direct*', (req, res, next) => data.get(req.ass.resourceId).then((fileData) => { +router.get('/direct*', (req: AssRequest, res: AssResponse, next) => data.get(req.ass?.resourceId).then((fileData: FileData) => { // Send file as an attachement for downloads if (req.query.download) res.header('Content-Disposition', `attachment; filename="${fileData.originalname}"`); // Return the file differently depending on what storage option was used const uploaders = { - s3: () => fetch(getS3url(fileData.randomId, fileData.ext)).then((file) => { + s3: () => fetch(getS3url(fileData.randomId, fileData.ext)).then((file: Response) => { file.headers.forEach((value, header) => res.setHeader(header, value)); - file.body.pipe(res); + file.body?.pipe(res); }), local: () => { - res.header('Accept-Ranges', 'bytes').header('Content-Length', fileData.size).type(fileData.mimetype); + res.header('Accept-Ranges', 'bytes').header('Content-Length', `${fileData.size}`).type(fileData.mimetype); fs.createReadStream(fileData.path).pipe(res); } }; @@ -74,33 +76,33 @@ router.get('/direct*', (req, res, next) => data.get(req.ass.resourceId).then((fi }).catch(next)); // Thumbnail response -router.get('/thumbnail', (req, res, next) => - data.get(req.ass.resourceId) - .then(({ is, thumbnail }) => fs.readFile((!is || (is.image || is.video)) ? path(diskFilePath, 'thumbnails/', thumbnail) : is.audio ? 'views/ass-audio-icon.png' : 'views/ass-file-icon.png')) - .then((fileData) => res.type('jpg').send(fileData)) +router.get('/thumbnail', (req: AssRequest, res: AssResponse, next) => + data.get(req.ass?.resourceId) + .then(({ is, thumbnail }: { is: IsPossible, thumbnail: string }) => fs.readFile((!is || (is.image || is.video)) ? path(diskFilePath, 'thumbnails/', thumbnail) : is.audio ? 'views/ass-audio-icon.png' : 'views/ass-file-icon.png')) + .then((fileData: Buffer) => res.type('jpg').send(fileData)) .catch(next)); // oEmbed response for clickable authors/providers // https://oembed.com/ // https://old.reddit.com/r/discordapp/comments/82p8i6/a_basic_tutorial_on_how_to_get_the_most_out_of/ -router.get('/oembed', (req, res, next) => - data.get(req.ass.resourceId) - .then(({ opengraph, is, size, timestamp, originalname }) => +router.get('/oembed', (req: AssRequest, res: AssResponse, next) => + data.get(req.ass?.resourceId) + .then((fileData: FileData) => res.type('json').send({ version: '1.0', - type: is.video ? 'video' : is.image ? 'photo' : 'link', - author_url: opengraph.authorUrl, - provider_url: opengraph.providerUrl, - author_name: replaceholder(opengraph.author || '', size, timestamp, originalname), - provider_name: replaceholder(opengraph.provider || '', size, timestamp, originalname) + type: fileData.is.video ? 'video' : fileData.is.image ? 'photo' : 'link', + author_url: fileData.opengraph.authorUrl, + provider_url: fileData.opengraph.providerUrl, + author_name: replaceholder(fileData.opengraph.author || '', fileData.size, fileData.timestamp, fileData.originalname), + provider_name: replaceholder(fileData.opengraph.provider || '', fileData.size, fileData.timestamp, fileData.originalname) })) .catch(next)); // Delete file -router.get('/delete/:deleteId', (req, res, next) => { - let oldName, oldType; // skipcq: JS-0119 - data.get(req.ass.resourceId) - .then((fileData) => { +router.get('/delete/:deleteId', (req: AssRequest, res: AssResponse, next) => { + let oldName: string, oldType: string; // skipcq: JS-0119 + data.get(req.ass?.resourceId) + .then((fileData: FileData) => { // Extract info for logs oldName = fileData.originalname; oldType = fileData.mimetype; @@ -117,7 +119,7 @@ router.get('/delete/:deleteId', (req, res, next) => { (!fileData.is || (fileData.is.image || fileData.is.video)) && fs.existsSync(path(diskFilePath, 'thumbnails/', fileData.thumbnail)) ? fs.rmSync(path(diskFilePath, 'thumbnails/', fileData.thumbnail)) : () => Promise.resolve()]); }) - .then(() => data.del(req.ass.resourceId)) + .then(() => data.del(req.ass?.resourceId)) .then(() => (log.success('Deleted', oldName, oldType), res.type('text').send('File has been deleted!'))) // skipcq: JS-0090 .catch(next); });