diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f100e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/node_modules +package-lock.json +.env +.old diff --git a/index.js b/index.js deleted file mode 100644 index b5019ab..0000000 --- a/index.js +++ /dev/null @@ -1,18 +0,0 @@ -function fastForwardVideoAds(root) { - const collection = root.getElementsByTagName('video') || []; - for (const video of collection) { - if (!video) { continue; } - const isVideoShort = video.duration < 60; - const isVideoPlaying = !!(video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2); - if (isVideoShort && isVideoPlaying) { - video.currentTime = video.currentTime + video.duration; - } - } -} - -document.addEventListener('DOMContentLoaded', () => { - setInterval(() => { - fastForwardVideoAds(document); - }, 200); - document.body.style.borderTop = '10px solid red'; -}); diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..cc60992 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,3 @@ +{ + "watch": ["*.js", "*.json", ".env"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ab4b11b --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "multipurpose-proxy-server", + "version": "1.0.0", + "description": "", + "main": "./src/index.js", + "scripts": { + "dev": "npx nodemon ./src/index.js", + "start": "npm run dev" + }, + "license": "ISC", + "type": "module", + "dependencies": { + "dotenv": "16.3.1", + "express": "4.18.2", + "http-proxy-middleware": "2.0.6", + "nodemon": "3.0.1" + } +} diff --git a/src/apps/video-ads-blocking-proxy.js b/src/apps/video-ads-blocking-proxy.js new file mode 100644 index 0000000..4735479 --- /dev/null +++ b/src/apps/video-ads-blocking-proxy.js @@ -0,0 +1,72 @@ +import express from 'express'; +import { config } from '../utils/config.js'; +import { logger } from '../middlewares/logger.js'; +import { proxy } from '../middlewares/proxy.js'; + +export function runVideoAdsBlockingProxy() { + const { IP, PORT1, HOST1, PATH1, PORT2, HOST2 } = config(); + + const app1 = express(); + app1.use(express.json()); + + app1.use('/*', logger(''), proxy({ + target: HOST1, + destination: IP, + pathRewrite: { '^/site': PATH1 }, + amendIncomingHtml: ` + + `, + })); + + app1.listen(PORT1, () => { + console.log(`APP1 RUNNING: http://${IP}:${PORT1}`); + }); + + const app2 = express(); + app2.use(express.json()); + + app2.use('/*', logger(''), proxy({ + target: HOST2, + destination: IP, + followRedirects: false, + amendIncomingHtml: ` + + `, + })); + + app2.listen(PORT2, () => { + console.log(`APP2 RUNNING: http://${IP}:${PORT2}`); + }); +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..9236811 --- /dev/null +++ b/src/index.js @@ -0,0 +1,3 @@ +import { runVideoAdsBlockingProxy } from './apps/video-ads-blocking-proxy.js'; + +runVideoAdsBlockingProxy(); diff --git a/src/middlewares/delay.js b/src/middlewares/delay.js new file mode 100644 index 0000000..59ef906 --- /dev/null +++ b/src/middlewares/delay.js @@ -0,0 +1,3 @@ +export function delay(time) { + return (_req, _res, next) => setTimeout(next, time); +} diff --git a/src/middlewares/logger.js b/src/middlewares/logger.js new file mode 100644 index 0000000..8c07632 --- /dev/null +++ b/src/middlewares/logger.js @@ -0,0 +1,7 @@ +export function logger(preMessage = '', postMessage = '') { + return (req, res, next) => { + const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl; + console.log(res.statusCode, preMessage, req.method, fullUrl, postMessage); + next(); + }; +} diff --git a/src/middlewares/proxy.js b/src/middlewares/proxy.js new file mode 100644 index 0000000..3870010 --- /dev/null +++ b/src/middlewares/proxy.js @@ -0,0 +1,86 @@ +import { createProxyMiddleware, responseInterceptor } from 'http-proxy-middleware'; +import { parseJson, stringifyJson } from '../utils/json.js'; +import { mergeDeep } from '../utils/objects.js'; +import { parseCookies } from '../utils/cookies.js'; + +export function proxy(config = {}) { + const { + target, + destination, + followRedirects, + amendOutgoingCookies, + amendOutgoingJson, + amendIncomingJson, + amendIncomingHtml, + ...restConfig + } = config; + + return createProxyMiddleware({ + target: target, + secure: true, + changeOrigin: true, + cookieDomainRewrite: destination, + followRedirects, + onProxyReq: (proxyReq, req) => { + const contentType = proxyReq.getHeader('Content-Type') || ''; + + if (amendOutgoingCookies) { + let newCookiesString = ''; + if (typeof amendOutgoingCookies === 'function') { + newCookiesString = amendOutgoingCookies(req.headers.cookie); + } else if (amendOutgoingCookies) { + newCookiesString = stringifyJson( + (parseCookies(req.headers.cookie) || {}), + (parseCookies(amendOutgoingCookies) || {}), + ); + } + proxyReq.setHeader('cookie', newCookiesString); + } + + if (contentType.includes('application/json')) { + let dataObject = req.body || {}; + if (amendOutgoingJson) { + if (typeof amendOutgoingJson === 'function') { + dataObject = amendOutgoingJson(dataObject); + } else if (amendOutgoingJson) { + dataObject = mergeDeep(dataObject, amendOutgoingJson); + } + } + const dataString = stringifyJson(dataObject); + proxyReq.setHeader('Content-Length', Buffer.byteLength(dataString)); + proxyReq.write(dataString); + } + }, + selfHandleResponse: true, + onProxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => { + const contentType = res.getHeader('Content-Type') || ''; + + if (followRedirects === false) { + res.removeHeader('location'); + } + + if (contentType.includes('application/json') && amendIncomingJson) { + let jsonObject = parseJson(responseBuffer.toString('utf8')); + if (typeof amendIncomingJson === 'function') { + jsonObject = amendIncomingJson(jsonObject); + } else if (typeof amendIncomingJson === 'object') { + jsonObject = mergeDeep(jsonObject, amendIncomingJson); + } + return stringifyJson(jsonObject); + } + + if (contentType.includes('text/html') && amendIncomingHtml) { + let htmlString = responseBuffer.toString('utf8'); + if (typeof amendIncomingHtml === 'function') { + htmlString = amendIncomingHtml(htmlString); + } else if (amendIncomingHtml) { + htmlString += amendIncomingHtml; + } + return htmlString; + } + + return responseBuffer; + }), + ...restConfig, + }); +}; diff --git a/src/utils/config.js b/src/utils/config.js new file mode 100644 index 0000000..44d61f0 --- /dev/null +++ b/src/utils/config.js @@ -0,0 +1,9 @@ +import dotenv from 'dotenv'; +import { getIp } from './ip.js'; + +dotenv.config(); + +export function config() { + process.env.IP = getIp(); + return process.env; +} diff --git a/src/utils/cookies.js b/src/utils/cookies.js new file mode 100644 index 0000000..5f3aed7 --- /dev/null +++ b/src/utils/cookies.js @@ -0,0 +1,25 @@ +export function parseCookies(cookiesInput) { + if (typeof cookiesInput === 'string') { + const cookiesObject = {}; + const cookiesList = cookiesInput.split(';'); + for (const cookiesPair of cookiesList) { + const [cookieKey, cookieValue] = cookiesPair.split('='); + if (cookieKey.trim()) { + cookiesObject[cookieKey.trim()] = (cookieValue || '').trim(); + } + } + return cookiesObject; + } if (typeof cookiesInput === 'object') { + return cookiesInput; + } + return null; +} + +export function stringifyCookies(cookiesInput) { + if (typeof cookiesInput === 'object') { + return Object.entries(cookiesInput).map(([key, value]) => `${key}=${value}`).join('; '); + } if (typeof cookiesInput === 'string') { + return cookiesInput; + } + return ''; +} diff --git a/src/utils/ip.js b/src/utils/ip.js new file mode 100644 index 0000000..ce17967 --- /dev/null +++ b/src/utils/ip.js @@ -0,0 +1,6 @@ +import { networkInterfaces } from 'os'; + +export function getIp() { + return [].concat(...Object.values(networkInterfaces())) + .find((item) => !item.internal && item.family === 'IPv4')?.address; +} diff --git a/src/utils/json.js b/src/utils/json.js new file mode 100644 index 0000000..366ca51 --- /dev/null +++ b/src/utils/json.js @@ -0,0 +1,20 @@ +export function parseJson(jsonString) { + if (typeof jsonString !== 'string') { + return null; + } + try { + return JSON.parse(jsonString.replace(/([{,][ ]*)(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '$1"$3": ')); + } catch(error) { + console.error(error, jsonString); + return null; + } +} + +export function stringifyJson(jsonObject) { + try { + return JSON.stringify(jsonObject); + } catch(error) { + console.error(error, value); + return ''; + } +} diff --git a/src/utils/objects.js b/src/utils/objects.js new file mode 100644 index 0000000..108e53a --- /dev/null +++ b/src/utils/objects.js @@ -0,0 +1,52 @@ +export function isPlainObject(item) { + if (typeof item !== 'object' || item === null) { + return false; + } + const prototype = Object.getPrototypeOf(item); + return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null); +} + +export function isArray(item) { + return Array.isArray(item); +} + +export function mergeDeep(target, ...sources) { + if (!sources.length) { + return target; + } + const source = sources.shift(); + if (isPlainObject(target) && isPlainObject(source)) { + for (const key in source) { + if (isPlainObject(source[key])) { + if (!isPlainObject(target[key])) { + target[key] = {}; + } + mergeDeep(target[key], source[key]); + } else if (isArray(source[key])) { + if (!isArray(target[key])) { + target[key] = []; + } + mergeDeep(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + } else if (isArray(target) && isArray(source)) { + for (let key = 0; key < source.length; ++key) { + if (isPlainObject(source[key])) { + if (!isPlainObject(target[key])) { + target[key] = {}; + } + mergeDeep(target[key], source[key]); + } else if (isArray(source[key])) { + if (!isArray(target[key])) { + target[key] = []; + } + mergeDeep(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + } + return mergeDeep(target, ...sources); +}