├── .env ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.cjs ├── LICENSE ├── README.md ├── eslint.config.mjs ├── example.gif ├── package-lock.json ├── package.json ├── public └── icons │ ├── stream-bypass.svg │ ├── stream-bypass@128px.png │ ├── stream-bypass@16px.png │ ├── stream-bypass@32px.png │ ├── stream-bypass@48px.png │ ├── stream-bypass@96px.png │ ├── stream-bypass_disabled.svg │ ├── stream-bypass_disabled@128px.png │ ├── stream-bypass_disabled@16px.png │ ├── stream-bypass_disabled@32px.png │ ├── stream-bypass_disabled@48px.png │ └── stream-bypass_disabled@96px.png ├── src ├── entries │ ├── background │ │ ├── mv2.ts │ │ ├── mv3.ts │ │ └── shared.ts │ ├── contentScript │ │ └── main.ts │ ├── player │ │ ├── Player.svelte │ │ ├── player.html │ │ └── player.ts │ └── popup │ │ ├── Popup.svelte │ │ ├── index.html │ │ └── toggle.svelte ├── lib │ ├── match.ts │ ├── settings.ts │ └── util │ │ ├── extract.ts │ │ └── userspace.ts ├── manifest.ts └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.json └── vite.config.ts /.env: -------------------------------------------------------------------------------- 1 | MANIFEST_VERSION=2 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Install nodejs 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 22 18 | cache: 'npm' 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Lint 24 | run: npm run lint 25 | 26 | build: 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | include: 31 | - manifest_version: 2 32 | - manifest_version: 3 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Install nodejs 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: 22 41 | cache: 'npm' 42 | 43 | - name: Install dependencies 44 | run: npm ci 45 | 46 | - name: Build 47 | env: 48 | MANIFEST_VERSION: ${{ matrix.manifest_version }} 49 | run: npm run build 50 | 51 | - name: Upload 52 | uses: actions/upload-artifact@v4 53 | with: 54 | name: stream-bypass-mv${{ matrix.manifest_version }} 55 | path: ./dist 56 | if-no-files-found: error 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | dist/ 3 | release/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=false 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | .env 5 | .env.* 6 | !.env.example 7 | 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import("prettier").Config} */ 4 | module.exports = { 5 | useTabs: true, 6 | singleQuote: true, 7 | trailingComma: 'none', 8 | printWidth: 100, 9 | plugins: ['prettier-plugin-svelte', '@ianvs/prettier-plugin-sort-imports'], 10 | /* prettier-plugin-svelte */ 11 | overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }], 12 | /* @ianvs/prettier-plugin-sort-imports */ 13 | importOrder: ['^~/(.*)$', '^./(.*)$', ''], 14 | importOrderParserPlugins: ['typescript'], 15 | importOrderTypeScriptVersion: '5.0.0', 16 | importOrderCaseSensitive: false 17 | }; 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-NOW ByteDream 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stream Bypass 2 | 3 | A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video. 4 | 5 |

6 | 7 | Version 8 | 9 | 10 | Firefox Addon Store 11 | 12 | 13 | Chrome Store 14 | 15 | 16 | GitHub Downloads 17 | 18 |

19 | 20 |

21 | Introduction 📝 22 | • 23 | Installation 📥 24 | • 25 | Features ✨ 26 | • 27 | Supported Websites 📜 28 | • 29 | Building 🛠️ 30 | • 31 | Settings ⚙️ 32 | • 33 | License ⚖ 34 |

35 | 36 | ## 📝 Introduction 37 | 38 | This addon replaces the video player from this sides with the native player build-in into the browser or redirects directly to the source video. 39 | This has the advantage, that no advertising or popups are shown when trying to interact with the video (playing, skipping, ...) or some sites are showing them even if you do nothing. 40 | Additionally, this enables you to download the video by right-clicking it and just choose the download option. 41 | 42 |
43 | How it's working: 44 | 45 |
46 | 47 | ## 📥 Installation 48 | 49 | ### Official browser stores 50 | 51 | The best way to install the extension are the official browser extension stores: 52 | 53 | - [Firefox Addon Store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/) (Firefox for Android is supported too!) 54 | - [Chrome Web Store](https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb) 55 | 56 | ### Manual installation 57 | 58 | - Firefox 59 | - Download `stream-bypass--mv2.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that) 60 | - Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar 61 | - Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the unzipped directory 62 | - Chromium / Google Chrome 63 | > As nearly every browser other than Firefox is based on Chromium, this should be the same for most of them 64 | - Download `stream-bypass--mv3.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that) 65 | - Go into your browser and type `chrome://extensions` in the address bar 66 | - Turn on the developer mode by checking the switch in the top right corner 67 | - Click `Load unpacked` and choose the unzipped directory 68 | 69 | ## ✨ Features 70 | 71 | | Feature | Firefox | Chrome | Firefox for Android | 72 | | --------------------------------------------------------------------------------------------------------------------------------- | ------- | ------ | ------------------- | 73 | | Replace site-speicifc video player with browser native video player | ✔ | ✔ | ✔ | 74 | | Support websites that are accessed via a redirect | ✔ | ❌ | ✔ | 75 | | Open video in mpv (with [ff2mpv](https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams)) | ✔ | ✔ | ❌ | 76 | 77 | - ✔️: Supported. 78 | - ❌: Not supported. 79 | 80 | ## 📜 Supported websites 81 | 82 | | Site | Firefox & Firefox for Android | Chrome & Chromium based | 83 | | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | 84 | | [dropload.io](https://dropload.io) | ✔ | ✔ | 85 | | [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔ | ⚠ (redirect probably required) | 86 | | [filemoon.to](https://filemoon.to) | ✔ | ✔ | 87 | | [goodstream.uno](https://goodstream.uno) | ✔ | ✔ | 88 | | [kwik.cx](https://kwik.cx) | ✔ | ✔ | 89 | | [loadx.ws](https://loadx.ws) | ✔ | ❌ (background request always required) | 90 | | [luluvdo.com](https://luluvdo.com) | ✔ | ❌ (background request always required) | 91 | | [mixdrop.co](https://mixdrop.co) | ✔ ️ | ✔ | 92 | | [mp4upload.com](https://mp4upload.com) | ✔ | ✔ | 93 | | [newgrounds.com](https://newgrounds.com) | ✔ | ✔ | 94 | | [streama2z.com](https://streama2z.com) | ✔ | ❌ (redirect always required) | 95 | | [streamtape.com](https://streamtape.com) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) | 96 | | [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | ✔ | 97 | | [supervideo.tv](https://supervideo.tv) | ✔ | ✔ | 98 | | [upstream.to](https://upstream.to) | ✔ | ✔ | 99 | | [vidmoly.to](https://vidmoly.me) | ✔ | ✔ | 100 | | [vidoza.net](https://vidoza.net) | ✔ | ✔ | 101 | | [voe.sx](https://voe.sx) | ✔ | ❌ (redirect always required) | 102 | | [vupload.com](https://vupload.com) | ✔ | ✔ | 103 | 104 | - ✔️: Everything ok. 105 | - ⚠: Works with limitations. 106 | - ❌: Not supported. 107 | 108 | _This table might not be 100% accurate, it isn't actively monitored if the addon works for every website!_ 109 | 110 | Some sites put much effort in obfuscating their code / how they receive the video stream so that it simply cost too much time for me to reverse engineer it and find out how to bypass the native video player of the site. 111 | 112 | ## 🛠️ Building 113 | 114 | If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions. 115 | 116 | Requirements: 117 | 118 | - `npm` installed. 119 | - A copy of this repository and a shell / console open in the copied directory. 120 | 121 | If the requirements are satisfied, you can continue with the following commands: 122 | 123 | ```shell 124 | # install all dependencies 125 | $ npm install 126 | 127 | # build the extension source to the dist/ directory 128 | $ npm run build 129 | 130 | # same as build + more optimizations and browser specific settings at release/ 131 | $ npm run release:firefox # or "release:chrome" to create a release for chromium based browsers 132 | ``` 133 | 134 | ##### Install 135 | 136 | If you want to use the addon in Chromium or any browser which is based on it, follow the steps in [installation](#-installation). 137 | When using firefox, use the following: 138 | 139 | 1. Type `about:debugging` in the browser's address bar. 140 | 2. Select 'This Firefox' tab (maybe named different, depending on your language). 141 | 3. Under `Temporary Extensions`, click `Load Temporary Add-on`. 142 | 4. Choose any file in the directory where the compiled sources are. 143 | 144 | ## ⚙️ Settings 145 | 146 | ### ff2mpv: use mpv to directly play streams 147 | 148 | ff2mpv is located at this repository: https://github.com/woodruffw/ff2mpv 149 | 150 | Steps to get it set up: 151 | 152 | - In the [Usage](https://github.com/woodruffw/ff2mpv#usage) section of the ff2mpv repository pick the installation instruction for your operating system (Linux/Windows/macOS; you do not need the browser addon). 153 | - Scroll down to `Install manually` 154 | - Follow instructions for Firefox/Chrome 155 | - Edit the `ff2mpv.json` you created: 156 | - Firefox: Add `{55dd42e8-3dd9-455a-b4fe-86664881b10c}` to `allowed_extensions` -> 157 | ``` 158 | "allowed_extensions": [ 159 | "ff2mpv@yossarian.net", 160 | "{55dd42e8-3dd9-455a-b4fe-86664881b10c}" 161 | ] 162 | ``` 163 | - Chrome/Chromium: 164 | - Go To: Settings -> Extensions 165 | - Click on `Details` of the Stream Bypass extension and copy the ID 166 | - Add `chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/` to `allowed_origins` -> 167 | ``` 168 | "allowed_origins": [ 169 | "chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/", 170 | "chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/" 171 | ] 172 | ``` 173 | 174 | ## ⚖ License 175 | 176 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details. 177 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import prettier from 'eslint-config-prettier'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import ts from 'typescript-eslint'; 5 | 6 | export default ts.config( 7 | js.configs.recommended, 8 | ...ts.configs.recommended, 9 | ...svelte.configs['flat/recommended'], 10 | prettier, 11 | ...svelte.configs['flat/prettier'], 12 | { 13 | files: ['**/*.svelte'], 14 | 15 | languageOptions: { 16 | parserOptions: { 17 | parser: ts.parser 18 | } 19 | } 20 | }, 21 | { 22 | rules: { 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | 'no-undef': 'off' 25 | } 26 | }, 27 | { 28 | ignores: [ 29 | '.DS_Store', 30 | 'node_modules', 31 | 'dist', 32 | 'release', 33 | '.idea', 34 | '.env', 35 | '.env.*', 36 | '!.env.example', 37 | 'package-lock.json' 38 | ] 39 | } 40 | ); 41 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/example.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stream-bypass", 3 | "version": "3.1.6", 4 | "displayName": "Stream Bypass", 5 | "author": "bytedream", 6 | "description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video", 7 | "scripts": { 8 | "build": "vite build", 9 | "watch": "vite build --watch --mode development --minify false", 10 | "dev": "vite", 11 | "serve:firefox": "web-ext run --start-url \"about:debugging#/runtime/this-firefox\" --source-dir ./dist/", 12 | "serve:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/", 13 | "check": "svelte-check --tsconfig ./tsconfig.json", 14 | "lint": "prettier --check . && eslint .", 15 | "format": "prettier --write .", 16 | "release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox", 17 | "release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome" 18 | }, 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/bytedream/stream-bypass.git" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/bytedream/stream-bypass/issues" 26 | }, 27 | "devDependencies": { 28 | "@ianvs/prettier-plugin-sort-imports": "^4.4.1", 29 | "@samrum/vite-plugin-web-extension": "^5.1.1", 30 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 31 | "@tsconfig/svelte": "^5.0.4", 32 | "@types/chrome": "^0.0.320", 33 | "@types/firefox-webext-browser": "^120.0.4", 34 | "eslint": "^9.26.0", 35 | "eslint-config-prettier": "^10.1.3", 36 | "eslint-plugin-svelte": "^3.5.1", 37 | "hls.js": "^1.6.2", 38 | "prettier": "^3.5.3", 39 | "prettier-plugin-svelte": "^3.3.3", 40 | "sass": "^1.87.0", 41 | "svelte": "^5.28.2", 42 | "svelte-check": "^4.1.7", 43 | "svelte-preprocess": "^6.0.3", 44 | "tslib": "^2.8.1", 45 | "typescript": "^5.8.3", 46 | "typescript-eslint": "^8.32.0", 47 | "vite": "^6.3.5", 48 | "web-ext": "^8.6.0" 49 | }, 50 | "type": "module" 51 | } 52 | -------------------------------------------------------------------------------- /public/icons/stream-bypass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/stream-bypass@128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass@128px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass@16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass@16px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass@32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass@32px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass@48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass@48px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass@96px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass@96px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass_disabled.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/stream-bypass_disabled@128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass_disabled@128px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass_disabled@16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass_disabled@16px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass_disabled@32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass_disabled@32px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass_disabled@48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass_disabled@48px.png -------------------------------------------------------------------------------- /public/icons/stream-bypass_disabled@96px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedream/stream-bypass/607326e6d67ab3b7592fc932bf0324c11a026df1/public/icons/stream-bypass_disabled@96px.png -------------------------------------------------------------------------------- /src/entries/background/mv2.ts: -------------------------------------------------------------------------------- 1 | import './shared'; 2 | 3 | import { getMatch, type Match } from '~/lib/match'; 4 | import { Redirect, UrlReferer } from '~/lib/settings'; 5 | 6 | chrome.webRequest.onBeforeSendHeaders.addListener( 7 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 8 | // @ts-ignore 9 | async (details) => { 10 | const referer = await UrlReferer.get(new URL(details.url).hostname); 11 | if (!referer) return; 12 | 13 | await UrlReferer.delete(new URL(details.url).hostname); 14 | 15 | details.requestHeaders!.push({ 16 | name: 'Referer', 17 | value: `https://${referer}/` 18 | }); 19 | 20 | return { requestHeaders: details.requestHeaders }; 21 | }, 22 | { urls: [''], types: ['xmlhttprequest'] }, 23 | ['blocking', 'requestHeaders'] 24 | ); 25 | 26 | chrome.webRequest.onBeforeRedirect.addListener( 27 | async (details) => { 28 | // check if redirects origins from a previous redirect 29 | if ((await Redirect.get()) == null) { 30 | let match: Match | null; 31 | if ((match = await getMatch(new URL(details.url).hostname)) !== null) { 32 | await Redirect.set(match); 33 | } 34 | } else { 35 | await Redirect.delete(); 36 | } 37 | }, 38 | { urls: [''], types: ['main_frame', 'sub_frame'] } 39 | ); 40 | -------------------------------------------------------------------------------- /src/entries/background/mv3.ts: -------------------------------------------------------------------------------- 1 | import './shared'; 2 | -------------------------------------------------------------------------------- /src/entries/background/shared.ts: -------------------------------------------------------------------------------- 1 | chrome.runtime.onMessage.addListener(async (message) => { 2 | if (message.action == 'ff2mpv') { 3 | await chrome.runtime.sendNativeMessage('ff2mpv', { url: message.url }); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /src/entries/contentScript/main.ts: -------------------------------------------------------------------------------- 1 | import { getMatch, MatchMediaType, type Match } from '~/lib/match'; 2 | import { Other, Redirect } from '~/lib/settings'; 3 | 4 | async function main() { 5 | let match: Match | null; 6 | let redirect = false; 7 | if ((match = await getMatch(window.location.host)) === null) { 8 | if ((match = await Redirect.get()) === null) { 9 | return; 10 | } 11 | redirect = true; 12 | } 13 | 14 | // some sites have a javascript based redirect, e.g. example.com redirects to example.org by changing 15 | // window.location.href instead of a 3XX http redirect. an empty body is a sign that such a javascript redirect 16 | // occurred 17 | if (document.body == null) { 18 | await Redirect.set(match); 19 | return; 20 | } 21 | 22 | let re = null; 23 | for (const regex of match.regex) { 24 | if ((re = document.body.innerHTML.match(regex)) !== null) { 25 | break; 26 | } 27 | } 28 | if (re === null) { 29 | return; 30 | } 31 | 32 | if (redirect) { 33 | await Redirect.delete(); 34 | } 35 | 36 | let url: string | null; 37 | let urlType: MatchMediaType | null; 38 | try { 39 | const matchResult = await match.match(re); 40 | if (matchResult && typeof matchResult === 'string') { 41 | url = matchResult; 42 | urlType = url.includes('.m3u8') ? MatchMediaType.Hls : MatchMediaType.Native; 43 | } else if (matchResult && typeof matchResult === 'object') { 44 | if (MatchMediaType.Hls in matchResult) { 45 | url = matchResult[MatchMediaType.Hls]; 46 | urlType = MatchMediaType.Hls; 47 | } else if (MatchMediaType.Native in matchResult) { 48 | url = matchResult[MatchMediaType.Native]; 49 | urlType = MatchMediaType.Native; 50 | } 51 | } 52 | } catch { 53 | return; 54 | } 55 | 56 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 57 | // @ts-ignore 58 | if (!url || !urlType) { 59 | return; 60 | } 61 | 62 | // send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application 63 | if (await Other.getFf2mpv()) { 64 | await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url }); 65 | } 66 | 67 | if (match.replace && urlType != MatchMediaType.Hls) { 68 | // this destroys all intervals that may spawn popups or events 69 | let intervalId = window.setInterval(() => {}, 0); 70 | while (intervalId--) { 71 | clearInterval(intervalId); 72 | } 73 | let timeoutId = window.setTimeout(() => {}, 0); 74 | while (timeoutId--) { 75 | clearTimeout(timeoutId); 76 | } 77 | 78 | // clear completed document 79 | document.documentElement.innerHTML = ''; 80 | 81 | document.body.style.backgroundColor = '#131313'; 82 | 83 | // video player 84 | const player = document.createElement('video'); 85 | player.style.width = '100%'; 86 | player.style.height = '100%'; 87 | player.controls = true; 88 | player.src = url; 89 | 90 | // add video player to document body 91 | document.body.style.margin = '0'; 92 | document.body.append(player); 93 | } else { 94 | window.location.assign( 95 | chrome.runtime.getURL( 96 | `src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${ 97 | window.location.hostname 98 | }&type=${urlType}` 99 | ) 100 | ); 101 | } 102 | } 103 | 104 | main(); 105 | -------------------------------------------------------------------------------- /src/entries/player/Player.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | {#if errorMessage} 22 |
23 |

24 | 25 | {@html errorMessage} 26 |

27 |
28 | {/if} 29 | 30 | 31 | 66 | -------------------------------------------------------------------------------- /src/entries/player/player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stream Bypass 6 | 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/entries/player/player.ts: -------------------------------------------------------------------------------- 1 | import Hls from 'hls.js'; 2 | import { matches, MatchMediaType } from '~/lib/match'; 3 | import { UrlReferer } from '~/lib/settings'; 4 | 5 | async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) { 6 | await UrlReferer.set(new URL(url).hostname, domain); 7 | 8 | videoElem.src = url; 9 | } 10 | 11 | async function playHls(url: string, domain: string, videoElem: HTMLVideoElement) { 12 | if (videoElem.canPlayType('application/vnd.apple.mpegurl')) { 13 | videoElem.src = url; 14 | } else if (Hls.isSupported()) { 15 | const hls = new Hls({ 16 | enableWorker: false, 17 | xhrSetup: async (xhr: XMLHttpRequest, url: string) => { 18 | await UrlReferer.set(new URL(url).hostname, domain); 19 | xhr.open('GET', url); 20 | } 21 | }); 22 | hls.loadSource(url); 23 | hls.attachMedia(videoElem); 24 | } else { 25 | throw 'Failed to play m3u8 video (hls is not supported). Try again or create a new issue here'; 26 | } 27 | } 28 | 29 | export async function play(videoElem: HTMLVideoElement) { 30 | const urlQuery = new URLSearchParams(window.location.search); 31 | const id = urlQuery.get('id') as string; 32 | const url = decodeURIComponent(urlQuery.get('url') as string); 33 | const domain = urlQuery.get('domain') as string; 34 | const type = urlQuery.get('type') as MatchMediaType; 35 | 36 | const match = matches[id]; 37 | if (match === undefined) { 38 | throw `Invalid id: ${id}. Please report this here`; 39 | } 40 | document.title = `Stream Bypass (${domain})`; 41 | 42 | if (type === MatchMediaType.Hls) { 43 | await playHls(url, domain, videoElem); 44 | } else if (type === MatchMediaType.Native) { 45 | await playNative(url, domain, videoElem); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/entries/popup/Popup.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |
34 |
35 | Hoster 36 |
37 | 38 |
39 | Hosters.setAll(hostersEnabled)} 43 | /> 44 |
45 |
46 | {#each hosters as hoster, i (hoster.id)} 47 | 50 |
51 | { 56 | if (hoster.active) { 57 | await Hosters.enable(hoster); 58 | } else { 59 | await Hosters.disable(hoster); 60 | } 61 | }} 62 | > 63 |
64 | {/each} 65 |
66 |
67 | {#if !isMobile} 68 |
69 | Other 70 |
71 | 72 |
73 | { 77 | ff2mpvEnabled = !ff2mpvEnabled; 78 | if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) { 79 | await Other.setFf2mpv(ff2mpvEnabled); 80 | ff2mpvEnabled = !ff2mpvEnabled; 81 | } 82 | }} 83 | > 84 | ? 89 |
90 |
91 |
92 | {/if} 93 | Report issues or requests 96 |
97 | 98 | 99 | 166 | -------------------------------------------------------------------------------- /src/entries/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stream Bypass 7 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/entries/popup/toggle.svelte: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 20 | 21 | 69 | -------------------------------------------------------------------------------- /src/lib/match.ts: -------------------------------------------------------------------------------- 1 | import { Hosters, Redirect, TmpHost } from './settings'; 2 | import { lastPathSegment } from './util/extract'; 3 | import { unpack } from './util/userspace'; 4 | 5 | export interface Match { 6 | name: string; 7 | id: string; 8 | domains: string[]; 9 | replace?: boolean; 10 | regex: RegExp[]; 11 | notice?: string; 12 | 13 | match( 14 | match: RegExpMatchArray 15 | ): Promise< 16 | string | { [MatchMediaType.Hls]: string } | { [MatchMediaType.Native]: string } | null 17 | >; 18 | 19 | // allow other properties that may be implemented by the objects that use this interface declaration 20 | [other: string]: any; 21 | } 22 | 23 | export enum MatchMediaType { 24 | Hls = 'hls', 25 | Native = 'native' 26 | } 27 | 28 | export const Doodstream: Match = { 29 | name: 'Doodstream', 30 | id: 'doodstream', 31 | domains: [ 32 | 'doodstream.com', 33 | 'dood.pm', 34 | 'dood.ws', 35 | 'dood.wf', 36 | 'dood.cx', 37 | 'dood.sh', 38 | 'dood.watch', 39 | 'dood.work', 40 | 'dood.to', 41 | 'dood.so', 42 | 'dood.la', 43 | 'dood.li', 44 | 'dood.re', 45 | 'dood.yt', 46 | 'doods.pro', 47 | 'ds2play.com', 48 | 'dooood.com', 49 | 'd000d.com' 50 | ], 51 | replace: true, 52 | regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s], 53 | 54 | match: async function (match: RegExpMatchArray) { 55 | const response = await fetch(`https://${window.location.host}${match[1]}`, { 56 | headers: { 57 | Range: 'bytes=0-' 58 | }, 59 | referrer: `https://${window.location.host}/e/${ 60 | window.location.pathname.split('/').slice(-1)[0] 61 | }` 62 | }); 63 | return `${await response.text()}1234567890${match[2]}${Date.now()}`; 64 | } 65 | }; 66 | 67 | export const DropLoad: Match = { 68 | name: 'Dropload', 69 | id: 'dropload', 70 | domains: ['dropload.io'], 71 | regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms], 72 | 73 | match: async function (match: RegExpMatchArray) { 74 | const unpacked = await unpack(match[0]); 75 | return unpacked.match(/(?<=file:").*(?=")/)![0]; 76 | } 77 | }; 78 | 79 | export const Filemoon: Match = { 80 | name: 'Filemoon', 81 | id: 'filemoon', 82 | domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'], 83 | regex: [/(?<=)/gms], 84 | replace: true, 85 | 86 | match: async function (match: RegExpMatchArray) { 87 | if (window.location.host.startsWith('filemoon')) { 88 | await TmpHost.set(new URL(match[0]).host, Filemoon); 89 | return null; 90 | } 91 | 92 | await TmpHost.delete(); 93 | 94 | const unpacked = await unpack(match[0]); 95 | return unpacked.match(/(?<=file:")\S*(?=")/)![0]; 96 | } 97 | }; 98 | 99 | export const GoodStream: Match = { 100 | name: 'Goodstream', 101 | id: 'goodstream', 102 | domains: ['goodstream.uno'], 103 | regex: [/(?<=file:\s+").*(?=")/g], 104 | 105 | match: async function (match: RegExpMatchArray) { 106 | return match[0]; 107 | } 108 | }; 109 | 110 | export const Kwik: Match = { 111 | name: 'Kwik', 112 | id: 'kwik', 113 | domains: ['kwik.cx'], 114 | regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms], 115 | 116 | match: async function (match: RegExpMatchArray) { 117 | const unpacked = await unpack(match[0]); 118 | return unpacked.match(/(?<=source=').*(?=')/)![0]; 119 | } 120 | }; 121 | 122 | export const LoadX: Match = { 123 | name: 'LoadX', 124 | id: 'loadx', 125 | domains: ['loadx.ws'], 126 | regex: [/./gm], 127 | 128 | match: async () => { 129 | const hash = encodeURIComponent(lastPathSegment(window.location.href)); 130 | const response = await fetch( 131 | `https://${window.location.host}/player/index.php?data=${hash}&do=getVideo`, 132 | { 133 | method: 'POST', 134 | headers: { 135 | 'X-Requested-With': 'XMLHttpRequest' 136 | } 137 | } 138 | ); 139 | 140 | const responseJson = await response.json(); 141 | const videoSource: string = responseJson['videoSource']; 142 | 143 | // extension of extracted url is '.txt', so we have to manually specify that it's a hls 144 | return { [MatchMediaType.Hls]: videoSource.replace('\\/', '/') }; 145 | } 146 | }; 147 | 148 | export const Luluvdo: Match = { 149 | name: 'Luluvdo', 150 | id: 'luluvdo', 151 | domains: ['luluvdo.com'], 152 | regex: [/./gm], 153 | 154 | match: async () => { 155 | const requestBody = new FormData(); 156 | requestBody.set('op', 'embed'); 157 | requestBody.set('file_code', lastPathSegment(window.location.href)); 158 | const response = await fetch(`https://${window.location.host}/dl`, { 159 | method: 'POST', 160 | body: requestBody, 161 | referrer: window.location.href 162 | }); 163 | 164 | let unpacked; 165 | 166 | const responseText = await response.text(); 167 | const evalMatch = responseText.match(/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms)!; 168 | // sometimes is packed, sometimes it's not. looks like someone forgets to obfuscate the code when pushing to 169 | // production 170 | if (evalMatch) { 171 | unpacked = await unpack(evalMatch[0]); 172 | return unpacked.match(/(?<=file:").*(?=")/)![0]; 173 | } else { 174 | unpacked = responseText; 175 | } 176 | 177 | return unpacked.match(/(?<=file:").*(?=")/)![0]; 178 | } 179 | }; 180 | 181 | export const Mixdrop: Match = { 182 | name: 'Mixdrop', 183 | id: 'mixdrop', 184 | domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'], 185 | regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms], 186 | 187 | match: async function (match: RegExpMatchArray) { 188 | const unpacked = await unpack(match[0]); 189 | const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0]; 190 | return `https:${url}`; 191 | } 192 | }; 193 | 194 | export const Mp4Upload: Match = { 195 | name: 'Mp4Upload', 196 | id: 'mp4upload', 197 | domains: ['mp4upload.com'], 198 | replace: true, 199 | regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms], 200 | 201 | match: async function (match: RegExpMatchArray) { 202 | const unpacked = await unpack(match[0]); 203 | return unpacked.match(/(?<=player.src\(").*(?=")/)![0]; 204 | } 205 | }; 206 | 207 | export const Newgrounds: Match = { 208 | name: 'Newgrounds', 209 | id: 'newgrounds', 210 | domains: ['newgrounds.com'], 211 | regex: [/.*/gm], 212 | 213 | match: async () => { 214 | const id = window.location.pathname.split('/').slice(-1)[0]; 215 | const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, { 216 | headers: { 217 | 'X-Requested-With': 'XMLHttpRequest' 218 | } 219 | }); 220 | const json = await response.json(); 221 | return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']); 222 | } 223 | }; 224 | 225 | export const StreamA2z: Match = { 226 | name: 'Stream2Az', 227 | id: 'stream2az', 228 | domains: ['streama2z.com', 'streama2z.xyz'], 229 | regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm], 230 | 231 | match: async function (match: RegExpMatchArray) { 232 | if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) { 233 | await Redirect.set(StreamA2z); 234 | return null; 235 | } 236 | return match[0]; 237 | } 238 | }; 239 | 240 | export const Streamtape: Match = { 241 | name: 'Streamtape', 242 | id: 'streamtape', 243 | domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'], 244 | regex: [/id=.*(?=')/gm], 245 | 246 | match: async function (match: RegExpMatchArray) { 247 | let i = 0; 248 | while (i < match.length) { 249 | if (match[++i - 1] == match[i]) { 250 | return `https://streamtape.com/get_video?${match[i]}`; 251 | } 252 | } 253 | 254 | // use the old method as fallback 255 | return `https://streamtape.com/get_video?${match.reverse()[0]}`; 256 | } 257 | }; 258 | 259 | export const Streamzz: Match = { 260 | name: 'Streamzz', 261 | id: 'streamzz', 262 | domains: ['streamzz.to', 'streamz.ws'], 263 | regex: [/(?<=\|)\w{2,}/gm], 264 | 265 | match: async function (match: RegExpMatchArray) { 266 | return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${ 267 | match.sort((a, b) => b.length - a.length)[0] 268 | }.dll`; 269 | } 270 | }; 271 | 272 | export const SuperVideo: Match = { 273 | name: 'Supervideo', 274 | id: 'supervideo', 275 | domains: ['supervideo.cc', 'supervideo.tv'], 276 | regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms], 277 | 278 | match: async function (match: RegExpMatchArray) { 279 | const unpacked = await unpack(match[0]); 280 | return unpacked.match(/(?<=file:").*(?=")/)![0]; 281 | } 282 | }; 283 | 284 | export const Upstream: Match = { 285 | name: 'Upstream', 286 | id: 'upstream', 287 | domains: ['upstream.to'], 288 | regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms], 289 | 290 | match: async function (match: RegExpMatchArray) { 291 | const unpacked = await unpack(match[0]); 292 | return unpacked.match(/(?<=file:").*(?=")/)![0]; 293 | } 294 | }; 295 | 296 | export const Vidmoly: Match = { 297 | name: 'Vidmoly', 298 | id: 'vidmoly', 299 | domains: ['vidmoly.me', 'vidmoly.to'], 300 | regex: [/(?<=file:").+\.m3u8/gm], 301 | replace: true, 302 | 303 | match: async function (match: RegExpMatchArray) { 304 | return match[0]; 305 | } 306 | }; 307 | 308 | export const Vidoza: Match = { 309 | name: 'Vidoza', 310 | id: 'vidoza', 311 | domains: ['vidoza.net', 'videzz.net'], 312 | regex: [/(?<=src:\s?").+?(?=")/gm], 313 | replace: true, 314 | 315 | match: async function (match: RegExpMatchArray) { 316 | return match[0]; 317 | } 318 | }; 319 | 320 | export const Voe: Match = { 321 | name: 'Voe', 322 | id: 'voe', 323 | domains: ['voe.sx'], 324 | regex: [ 325 | // voe.sx 326 | /(?<=window\.location\.href\s=\s')\S*(?=')/gm, 327 | // whatever site voe.sx redirects to 328 | /(?<=