30 Commits

Author SHA1 Message Date
696ec22471 Update dependencies and version 2024-07-28 23:43:56 +02:00
e379113aa4 Add firefox android support 2024-07-28 23:33:54 +02:00
84605ceb30 Make nativeMessaging an optional permission 2024-07-28 23:31:25 +02:00
67c492db47 Update popup UI 2024-07-28 23:04:04 +02:00
ae2f196b06 Lint 2024-07-28 18:08:05 +02:00
298e9308ce Add streama2z.com (#19) 2024-07-28 17:54:41 +02:00
3cd4c6b6b7 Fix doodstream.com 2024-07-28 17:54:02 +02:00
6286acf85c Make video replace strategy more aggressive 2024-07-28 17:33:54 +02:00
94eee79218 Fix voe.sx 2024-07-28 16:35:58 +02:00
2f186bda07 Add d000d.com (#18) 2024-07-28 14:46:36 +02:00
1c9f95cebc Fix linting errors 2024-07-14 21:08:47 +02:00
c57cd03407 Fix vidmoly & add it to README 2024-05-20 15:40:38 +02:00
64cf565da3 Update dependencies and version 2024-05-20 15:37:46 +02:00
bf8a7eb602 Remove reliability 2024-05-20 14:11:09 +02:00
c643a39e2d Add new doodstream domain 2024-04-24 01:18:01 +02:00
fcfbc41fdb Fix voe.sx 2024-04-24 00:46:32 +02:00
1251f079f5 Remove discord link 2023-12-10 19:51:35 +01:00
ccfc77167b Add vidmoly.me 2023-12-08 15:50:19 +01:00
a10066458f Add chrome web store link 2023-11-20 12:20:32 +01:00
6a8c705b06 Fix mv2 chrome namespace apis 2023-11-17 16:25:40 +01:00
76a5bac7fd Fix chromium popup height 2023-11-17 16:25:29 +01:00
3aca863a4b Fix invalid release name 2023-11-17 15:06:17 +01:00
fe14edc0ef Make mv3 compatible 2023-11-17 15:06:09 +01:00
fffe23638c Update build instructions 2023-11-11 22:05:39 +01:00
5214228a72 Add linting and build pipeline 2023-11-11 21:48:53 +01:00
7000d5a08b Lint 2023-11-11 21:48:44 +01:00
a21f799e7d Update install instructions 2023-11-11 21:35:24 +01:00
1fb85313dd Use more reliable streamtape match function 2023-11-11 21:31:23 +01:00
6e7410b088 Rewrite 2023-11-11 21:31:20 +01:00
0054f5da0e Update few domains (#15)
* Update matches.ts

* Update matches.ts

* Update matches.ts
2023-08-08 18:58:50 +02:00
64 changed files with 9854 additions and 1334 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
MANIFEST_VERSION=2

9
.eslintignore Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
node_modules
/dist
/release
.env
.env.*
!.env.example
package-lock.json

34
.eslintrc.cjs Normal file
View File

@ -0,0 +1,34 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'no-undef': 'off'
}
};

56
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: ci
on:
push:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install nodejs
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- manifest_version: 2
- manifest_version: 3
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install nodejs
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
env:
MANIFEST_VERSION: ${{ matrix.manifest_version }}
run: npm run build
- name: Upload
uses: actions/upload-artifact@v3
with:
name: stream-bypass-mv${{ matrix.manifest_version }}
path: ./dist
if-no-files-found: error

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
.idea/
build/
dist/
release/
node_modules/

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
auto-install-peers=false
strict-peer-dependencies=false

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
node_modules
/dist
.env
.env.*
!.env.example
package-lock.json

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@ -3,21 +3,21 @@
A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.
<p align="center">
<a href="https://github.com/ByteDream/stream-bypass/releases/latest">
<a href="https://github.com/bytedream/stream-bypass/releases/latest">
<img src="https://img.shields.io/github/v/release/ByteDream/stream-bypass?label=Version&style=flat-square" alt="Version">
</a>
<a href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
<img src="https://img.shields.io/amo/users/stream-bypass?label=Firefox%20Store%20Downloads&style=flat-square" alt="Firefox Addon Store">
<img src="https://img.shields.io/amo/users/stream-bypass?label=Firefox%20Users&style=flat-square" alt="Firefox Addon Store">
</a>
<a href="https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb">
<img src="https://img.shields.io/chrome-web-store/users/ddfpfjomnakfckhmilacnbokdaknamdb?style=flat-square&label=Chrome%20Users" alt="Chrome Store">
</a>
<a href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
<img src="https://img.shields.io/amo/stars/stream-bypass?label=Firefox%20Store%20Stars&style=flat-square" alt="Firefox Addon Stars">
</a>
<a href="https://github.com/ByteDream/stream-bypass/releases/latest">
<a href="https://github.com/bytedream/stream-bypass/releases/latest">
<img src="https://img.shields.io/github/downloads/ByteDream/stream-bypass/total?label=GitHub%20Downloads&style=flat-square" alt="GitHub Downloads">
</a>
<a href="https://discord.gg/gUWwekeNNg">
<img src="https://img.shields.io/discord/915659846836162561?label=Discord&style=flat-square" alt="Discord">
</a>
</p>
<p align="center">
@ -47,48 +47,50 @@ Additionally, this enables you to download the video by right-clicking it and ju
## 📥 Installation
### Firefox
### Official browser stores
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/).
The best way to install the extension are the official browser extension stores:
### Chromium / Google Chrome
- [Firefox Addon Store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/)
- [Chrome Web Store](https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb)
1. Download the zipfile from the [latest release](https://smartrelease.bytedream.org/github/ByteDream/stream-bypass/stream_bypass-{tag}.zip) and unzip it (with [7zip](https://www.7-zip.org/) or something like that).
2. Go into your browser and type `chrome://extensions` in the address bar.
3. Turn the developer mode in the top right corner on.
4. Click Load unpacked.
5. Choose the cloned / unzipped directory.
### Manual installation
### Opera
1. Download the zipfile from the [latest release](https://smartrelease.bytedream.org/github/ByteDream/stream-bypass/stream_bypass-{tag}.zip) and unzip it (with [7zip](https://www.7-zip.org/) or something like that).
2. Go into your browser and type `opera://extensions` in the address bar.
3. Turn the developer mode in the top right corner on.
4. Click Load unpacked.
5. Choose the cloned / unzipped directory.
- Firefox
- Download `stream-bypass-<version>-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)
- Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar
- Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the unzipped directory
- Chromium / Google Chrome
> As nearly every browser other than Firefox is based on Chromium, this should be the same for most of them
- Download `stream-bypass-<version>-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)
- Go into your browser and type `chrome://extensions` in the address bar
- Turn on the developer mode by checking the switch in the top right corner
- Click `Load unpacked` and choose the unzipped directory
## 📜 Supported sites
| Site | Supported | Note |
|-----------------------------------------------------------------------|-----------|--------------------------------------------------------------------------------------------------------------|
| --------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
| [dropload.io](https://dropload.io) | ✔ | |
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | |
| [filemoon.sx](https://filemoon.sx) | ✔ | |
| [goodstream.uno](https://goodstream.uno) | ✔ | |
| [mcloud.to](https://mcloud.to/) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) |
| [mixdrop.co](https://mixdrop.co) | ✔ | |
| [mp4upload.com](https://mp4upload.com) | ✔ | |
| [newgrounds.com](https://newgrounds.com) | ✔ | |
| [streama2s.com](https://streama2z.com) | ✔ |
| [streamtape.com](https://streamtape.com) | ✔ | |
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
| [supervideo.tv](https://supervideo.tv) | ✔ | |
| [upstream.to](https://upstream.to) | ✔ | |
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
| [vidmoly.me](https://vidmoly.me) | ✔ | |
| [vidoza.net](https://vidoza.net) | ✔ | |
| [vidstream.pro](https://vidstream.pro) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) |
| [voe.sx](https://voe.sx) | ✔ | |
| [vupload.com](https://vupload.com) | ✔ | |
| [kwik.cx](https://kwik.cx) | ✔ | |
| [dropload.io](https://dropload.io) | ✔ | |
| [supervideo.tv](https://supervideo.tv) | ✔ | |
| [goodstream.uno](https://goodstream.uno) | ✔ | |
- ✔️: Everything ok.
- ⚠: Included in the addon but will probably not work. See `Note` in this case, an explanation why will stand there in the most cases.
@ -110,37 +112,41 @@ Some sites put much effort in obfuscating their code / how they receive the vide
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
Requirements:
- `npm` installed.
- A copy of this repository and a shell / console open in the copied directory.
If the requirements are satisfied, you can continue with the following commands:
```shell
# install all dependencies
$ npm install
# build the extension source to a build/ directory
# build the extension source to the dist/ directory
$ npm run build
# same as build + create a bundle zipfile at dist/
$ npm run bundle
# same as build + more optimizations and browser specific settings at release/
$ npm run release:firefox # or "release:chrome" to create a release for chromium based browsers
```
##### Install
If you want to use the addon in Chromium or any browser which is based on it (almost every other, Google Chrome, Opera, ...), follow the steps in [installation](#-installation).
If you want to use the addon in Chromium or any browser which is based on it, follow the steps in [installation](#-installation).
When using firefox, use the following:
1. Type `about:debugging` in the browser's address bar.
2. Select 'This Firefox' tab (maybe named different, depending on your language).
3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
4. Choose any file in the directory where the compiled sources are.
## ⚙️ Settings
### <ins>ff2mpv: use mpv to directly play streams</ins>
ff2mpv is located at this repository: https://github.com/woodruffw/ff2mpv
Steps to get it set up:
- 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).
- Scroll down to `Install manually`
- Follow instructions for Firefox/Chrome
@ -163,7 +169,6 @@ Steps to get it set up:
]
```
## ⚖ License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.

8488
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,51 @@
{
"name": "stream-bypass",
"version": "2.2.0",
"version": "3.1.0",
"displayName": "Stream Bypass",
"author": "bytedream",
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
"main": "src/index.ts",
"scripts": {
"build": "node tasks/build.ts",
"bundle": "node tasks/build.ts && web-ext build -s build -a dist -o",
"bundle:all": "node tasks/build.ts && web-ext build -s build -a dist -o && web-ext sign -s build -a dist",
"check": "web-ext lint -s build"
"build": "vite build",
"watch": "vite build --watch --mode development --minify false",
"dev": "vite",
"serve:firefox": "web-ext run --start-url \"about:debugging#/runtime/this-firefox\" --source-dir ./dist/",
"serve:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/",
"check": "svelte-check --tsconfig ./tsconfig.json",
"lint": "prettier --check --plugin prettier-plugin-svelte . && eslint .",
"format": "prettier --write --plugin prettier-plugin-svelte .",
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/ByteDream/stream-bypass.git"
"url": "git+https://github.com/bytedream/stream-bypass.git"
},
"author": "ByteDream",
"license": "MIT",
"bugs": {
"url": "https://github.com/ByteDream/stream-bypass/issues"
"url": "https://github.com/bytedream/stream-bypass/issues"
},
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.1.0",
"@types/chrome": "^0.0.234",
"@types/node-sass": "^4.11.2",
"@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"eslint": "^8.17.0",
"hls.js": "^1.1.5",
"rollup": "^3.21.0",
"sass": "^1.62.1",
"tippy.js": "^6.3.7",
"typescript": "^5.0.4",
"web-ext": "^7.0.0"
}
"@samrum/vite-plugin-web-extension": "^5.1.0",
"@sveltejs/vite-plugin-svelte": "^2.5.3",
"@tsconfig/svelte": "^5.0.4",
"@types/chrome": "^0.0.269",
"@types/firefox-webext-browser": "^120.0.4",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.43.0",
"hls.js": "^1.5.13",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"sass": "^1.77.8",
"svelte": "^4.2.18",
"svelte-check": "^3.8.4",
"svelte-preprocess": "^6.0.2",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"vite": "^4.5.3",
"web-ext": "^8.2.0"
},
"type": "module"
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:#fff;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:gray;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,25 +0,0 @@
import {getMatch} from "./match/match";
import {storageDelete, storageGet, storageSet} from "./store/store";
import {Match} from "./match/matches";
chrome.runtime.onMessage.addListener((message, sender) => {
if (message.action == "ff2mpv") {
chrome.runtime.sendNativeMessage("ff2mpv", {url: message.url})
.catch((error) => {console.error(error)})
}
})
chrome.webRequest.onBeforeRedirect.addListener(async details => {
// check if redirects origins from a previous redirect
if (await storageGet('redirect') === undefined) {
let match: Match
if ((match = await getMatch(new URL(details.url).host)) !== undefined) {
await storageSet('redirect', match.id)
}
} else {
await storageDelete('redirect')
}
}, {
urls: ['<all_urls>'],
types: ['main_frame', 'sub_frame']
})

View File

@ -0,0 +1,40 @@
import './shared';
import type { Match } from '~/lib/match';
import { Redirect, storageDelete, storageGet } from '~/lib/settings';
import { getMatch } from '~/lib/match';
chrome.webRequest.onBeforeSendHeaders.addListener(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
async (details) => {
const referer: { domain: string } | undefined = await storageGet('referer');
if (referer === undefined) return;
details.requestHeaders!.push({
name: 'Referer',
value: `https://${referer.domain}/`
});
await storageDelete('referer');
return { requestHeaders: details.requestHeaders };
},
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] },
['blocking', 'requestHeaders']
);
chrome.webRequest.onBeforeRedirect.addListener(
async (details) => {
// check if redirects origins from a previous redirect
if ((await Redirect.get()) == null) {
let match: Match | null;
if ((match = await getMatch(new URL(details.url).hostname)) !== null) {
await Redirect.set(match);
}
} else {
await Redirect.delete();
}
},
{ urls: ['<all_urls>'], types: ['main_frame', 'sub_frame'] }
);

View File

@ -0,0 +1 @@
import './shared';

View File

@ -0,0 +1,5 @@
chrome.runtime.onMessage.addListener(async (message) => {
if (message.action == 'ff2mpv') {
await chrome.runtime.sendNativeMessage('ff2mpv', { url: message.url });
}
});

View File

@ -0,0 +1,82 @@
import type { Match } from '~/lib/match';
import { getMatch } from '~/lib/match';
import { Other, Redirect } from '~/lib/settings';
async function main() {
let match: Match | null;
let redirect = false;
if ((match = await getMatch(window.location.host)) === null) {
if ((match = await Redirect.get()) === null) {
return;
}
redirect = true;
}
// some sites have a javascript based redirect, e.g. example.com redirects to example.org by changing
// window.location.href instead of a 3XX http redirect. an empty body is a sign that such a javascript redirect
// occurred
if (document.body == null) {
await Redirect.set(match);
return;
}
const re = document.body.innerHTML.match(match.regex);
if (re === null) {
return;
}
if (redirect) {
await Redirect.delete();
}
let url: string | null;
try {
url = await match.match(re);
} catch (e) {
return;
}
if (!url) {
return;
}
// send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application
if (await Other.getFf2mpv()) {
await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url });
}
if (match.replace && !url.includes('.m3u8')) {
// this destroys all intervals that may spawn popups or events
let intervalId = window.setInterval(() => {}, 0);
while (intervalId--) {
clearInterval(intervalId);
}
let timeoutId = window.setTimeout(() => {}, 0);
while (timeoutId--) {
clearTimeout(timeoutId);
}
// clear completed document
document.documentElement.innerHTML = '';
// video player
const player = document.createElement('video');
player.style.width = '100%';
player.style.height = '100%';
player.controls = true;
player.src = url;
// add video player to document body
document.body.style.margin = '0';
document.body.append(player);
} else {
window.location.assign(
chrome.runtime.getURL(
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
window.location.hostname
}`
)
);
}
}
main();

View File

@ -0,0 +1,64 @@
<script lang="ts">
import { play } from '~/entries/player/player';
import { onMount } from 'svelte';
let errorMessage: string | null = null;
let videoElem: HTMLVideoElement;
onMount(async () => {
try {
await play(videoElem);
videoElem.controls = true;
} catch (e) {
errorMessage = e;
}
});
</script>
<!-- svelte-ignore a11y-media-has-caption -->
<video id="video" bind:this={videoElem} />
{#if errorMessage}
<div id="message-container">
<p>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html errorMessage}
</p>
</div>
{/if}
<!-- eslint-disable -->
<style lang="scss" global>
body {
background-color: #131313;
}
html,
body,
video {
width: 100%;
height: 100%;
margin: 0;
}
video {
position: absolute;
top: 0;
left: 0;
}
#message-container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: white;
text-align: center;
height: 100%;
& a,
& a:visited {
color: inherit;
text-decoration: underline;
}
}
</style>

View File

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Stream Bypass</title>
</head>
<body>
<script type="module">
import Player from '~/entries/player/Player.svelte';
new Player({
target: document.body
});
</script>
</body>
</html>

View File

@ -0,0 +1,42 @@
import { matches } from '~/lib/match';
import Hls from 'hls.js';
import { storageSet } from '~/lib/settings';
async function playNative(url: string, videoElem: HTMLVideoElement) {
videoElem.src = url;
}
async function playHls(url: string, videoElem: HTMLVideoElement) {
if (videoElem.canPlayType('application/vnd.apple.mpegurl')) {
videoElem.src = url;
} else if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: false
});
hls.loadSource(url);
hls.attachMedia(videoElem);
} else {
throw 'Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>';
}
}
export async function play(videoElem: HTMLVideoElement) {
const urlQuery = new URLSearchParams(window.location.search);
const id = urlQuery.get('id') as string;
const url = decodeURIComponent(urlQuery.get('url') as string);
const domain = urlQuery.get('domain') as string;
const match = matches[id];
if (match === undefined) {
throw `Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`;
}
document.title = `Stream Bypass (${domain})`;
await storageSet('referer', { domain: domain });
if (new URL(url).pathname.endsWith('.m3u8')) {
await playHls(url, videoElem);
} else {
await playNative(url, videoElem);
}
}

View File

@ -0,0 +1,165 @@
<script lang="ts">
import { type Match, matches } from '~/lib/match';
import { Hosters, Other } from '~/lib/settings';
import Toggle from './toggle.svelte';
let hostersEnabled: boolean;
let hosters: (Match & { active: boolean; disabled: boolean })[] = [];
(async () => {
hostersEnabled = !(await Hosters.getAllDisabled());
const disabled = await Hosters.getDisabled();
hosters = Object.values(matches).map((m: any) => {
m['active'] = disabled.findIndex((p) => p.id == m.id) == -1;
return m;
}) as typeof hosters;
})();
let isMobile: boolean;
(async () => {
isMobile = (await browser.runtime.getPlatformInfo()).os === 'android';
})();
let ff2mpvEnabled: boolean;
(async () => {
ff2mpvEnabled = (await Other.getFf2mpv()) as boolean;
})();
</script>
<main
style={isMobile
? 'height: 100vh; display: flex; flex-direction: column; align-items: center'
: 'height: 500px'}
>
<fieldset>
<legend>Hoster</legend>
<div class="setting-container" style={isMobile ? 'grid-column-gap: 5rem' : ''}>
<label for="hosters-enabled">Enabled</label>
<div>
<Toggle
bind:checked={hostersEnabled}
id="hosters-enabled"
on:change={() => Hosters.setAll(hostersEnabled)}
/>
</div>
<hr />
{#each hosters as hoster, i}
<label for="hoster-{i}" style="cursor: {hostersEnabled ? 'pointer' : 'default'}"
>{hoster.name}</label
>
<div>
<Toggle
bind:checked={hoster.active}
disabled={!hostersEnabled}
id="hoster-{i}"
on:change={async () => {
if (hoster.active) {
await Hosters.enable(hoster);
} else {
await Hosters.disable(hoster);
}
}}
></Toggle>
</div>
{/each}
</div>
</fieldset>
{#if !isMobile}
<fieldset>
<legend>Other</legend>
<div class="setting-container">
<label for="ff2mpv">ff2mpv</label>
<div>
<Toggle
bind:checked={ff2mpvEnabled}
id="ff2mpv"
on:change={async () => {
ff2mpvEnabled = !ff2mpvEnabled;
if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) {
await Other.setFf2mpv(ff2mpvEnabled);
ff2mpvEnabled = !ff2mpvEnabled;
}
}}
></Toggle>
<a
class="info-questionmark"
href="https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams"
>?</a
>
</div>
</div>
</fieldset>
{/if}
<a id="report-notice" href="https://github.com/ByteDream/stream-bypass/issues"
>Report issues or requests</a
>
</main>
<!-- eslint-disable -->
<style lang="scss" global>
body {
background-color: #2b2a33;
color: white;
font-weight: bold;
margin: 0;
padding: 0 8px;
}
fieldset {
border-radius: 5px;
border-color: gray;
}
#report-notice {
border: none;
color: white;
display: block;
font-weight: lighter;
font-size: 0.8rem;
text-align: center;
padding: 5px 0;
}
.settings {
display: flex;
flex-direction: column;
align-items: center;
margin: 10px 0;
}
.header {
font-size: 16px;
margin: 5px 0;
text-align: center;
}
.setting-container {
display: grid;
grid-template-columns: auto auto;
grid-column-gap: 5px;
grid-row-gap: 4px;
align-items: end;
width: 100%;
& > label {
height: 34px;
margin: 0;
user-select: none;
cursor: pointer;
display: flex;
align-items: center;
}
& > hr {
grid-column: 1 / span 2;
width: 100%;
}
}
.info-questionmark {
display: inline-block;
transform: translateX(-40%) translateY(-100%);
color: black;
text-decoration: none;
}
</style>

View File

@ -0,0 +1,17 @@
<!doctype html>
<html style="overflow-y: hidden" lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Stream Bypass</title>
</head>
<body style="overflow-y: scroll">
<script type="module">
import Popup from '~/entries/popup/Popup.svelte';
new Popup({
target: document.body
});
</script>
</body>
</html>

View File

@ -0,0 +1,5 @@
import App from './Popup.svelte';
new App({
target: document.getElementById('app') as Element
});

View File

@ -0,0 +1,65 @@
<!-- https://flowbite.com/docs/forms/toggle/ -->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let checked = false;
export let disabled = false;
export let id: string | null = null;
const dispatch = createEventDispatcher();
</script>
<label class="toggle">
<slot />
<input type="checkbox" {id} bind:checked {disabled} on:change={(e) => dispatch('change', e)} />
<span />
</label>
<style lang="scss" global>
.toggle {
display: inline-flex;
align-items: center;
cursor: pointer;
input {
clip: rect(0, 0, 0, 0);
position: absolute;
&:checked + span {
background: limegreen;
&:after {
transform: translateX(100%);
}
}
&:disabled + span {
background: gray;
}
}
span {
position: relative;
width: 2.75rem;
height: 1.5rem;
background: #cf0000;
border-radius: 9999px;
&:after {
content: '';
position: absolute;
top: 2px;
inset-inline-start: 2px;
background: white;
border-radius: 9999px;
height: 1.25rem;
width: 1.25rem;
transition: all 0.15s;
}
}
&:has(input:disabled) {
cursor: default;
}
}
</style>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="195.32812mm"
height="195.32812mm"
viewBox="0 0 195.32812 195.32812"
version="1.1"
id="svg5"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
sodipodi:docname="logo-disabled.svg"
inkscape:export-filename="disabled_128.png"
inkscape:export-xdpi="16.644812"
inkscape:export-ydpi="16.644812"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.90509668"
inkscape:cx="428.68349"
inkscape:cy="362.94465"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="powerstroke"
id="path-effect3140"
is_visible="true"
lpeversion="1"
offset_points="3,0"
not_jump="false"
sort_points="true"
interpolator_type="CubicBezierJohan"
interpolator_beta="0.2"
start_linecap_type="zerowidth"
linejoin_type="extrp_arc"
miter_limit="4"
scale_width="1"
end_linecap_type="zerowidth" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-11.238281,-30.525391)">
<path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 108.90234,30.525391 c -53.858772,0 -97.664059,43.805286 -97.664059,97.664059 0,53.85878 43.805287,97.66407 97.664059,97.66407 53.85878,0 97.66407,-43.80529 97.66407,-97.66407 0,-53.858773 -43.80529,-97.664059 -97.66407,-97.664059 z m 0,13.433593 c 46.59895,0 84.23047,37.631526 84.23047,84.230466 0,46.59895 -37.63152,84.23047 -84.23047,84.23047 -46.59894,0 -84.232418,-37.63152 -84.232418,-84.23047 0,-46.59894 37.633478,-84.230466 84.232418,-84.230466 z"
id="path2592" />
<path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 8.9863281,105.29688 v 96.92578 l 83.9394529,-48.46289 -7.699219,-4.44532 z m 10.2656249,17.78125 53.142578,30.68164 -53.142578,30.68164 z"
id="path2646"
transform="matrix(1.3085265,0,0,1.3085265,56.033246,-73.00885)" />
<path
style="fill:#808080;stroke:#000000;stroke-width:5.46616"
d="M 345.13114,686.08528 C 222.64086,675.79533 119.51392,599.94853 74.016249,486.68834 64.419992,462.79978 58.296197,439.49115 53.966274,410.37335 51.203358,391.79333 51.205528,346.25661 53.970219,327.58931 61.4703,276.94849 77.618278,233.84988 103.92601,194.25835 143.90493,134.09253 200.40964,90.698198 268.39759,67.947934 322.37862,49.884696 378.75198,46.533168 433.87378,58.109989 c 82.82951,17.396066 153.43381,65.362801 200.69115,136.344381 10.701,16.07313 25.83502,46.0053 32.67962,64.63397 6.70243,18.2417 13.10531,43.27443 16.2797,63.64715 2.0974,13.46078 2.48648,20.4825 2.53407,45.73343 0.0589,31.24975 -0.58565,38.59247 -5.57665,63.52913 -26.77909,133.797 -136.64661,235.61462 -272.39375,252.4357 -17.24628,2.13706 -47.67501,2.9353 -62.95678,1.65153 z M 422.60679,489.0315 C 535.93722,423.59631 628.78214,369.70078 628.92885,369.26367 629.20612,368.43746 217.06471,130.09256 214.27041,129.46316 c -1.37426,-0.30954 -1.58595,31.67289 -1.58595,239.60604 0,227.99566 0.0965,239.93763 1.9335,239.44933 1.06342,-0.28267 94.65839,-54.05183 207.98883,-119.48703 z"
id="path2606"
transform="matrix(0.26458333,0,0,0.26458333,11.238281,30.525391)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="195.32812mm"
height="195.32812mm"
viewBox="0 0 195.32812 195.32812"
version="1.1"
id="svg5"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
sodipodi:docname="logo.svg"
inkscape:export-filename="logo_128.png"
inkscape:export-xdpi="16.644812"
inkscape:export-ydpi="16.644812"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.90509668"
inkscape:cx="198.32136"
inkscape:cy="397.19514"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="1920"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="powerstroke"
id="path-effect3140"
is_visible="true"
lpeversion="1"
offset_points="3,0"
not_jump="false"
sort_points="true"
interpolator_type="CubicBezierJohan"
interpolator_beta="0.2"
start_linecap_type="zerowidth"
linejoin_type="extrp_arc"
miter_limit="4"
scale_width="1"
end_linecap_type="zerowidth" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-11.238281,-30.525391)">
<path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 108.90234,30.525391 c -53.858772,0 -97.664059,43.805286 -97.664059,97.664059 0,53.85878 43.805287,97.66407 97.664059,97.66407 53.85878,0 97.66407,-43.80529 97.66407,-97.66407 0,-53.858773 -43.80529,-97.664059 -97.66407,-97.664059 z m 0,13.433593 c 46.59895,0 84.23047,37.631526 84.23047,84.230466 0,46.59895 -37.63152,84.23047 -84.23047,84.23047 -46.59894,0 -84.232418,-37.63152 -84.232418,-84.23047 0,-46.59894 37.633478,-84.230466 84.232418,-84.230466 z"
id="path2592" />
<path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 8.9863281,105.29688 v 96.92578 l 83.9394529,-48.46289 -7.699219,-4.44532 z m 10.2656249,17.78125 53.142578,30.68164 -53.142578,30.68164 z"
id="path2646"
transform="matrix(1.3085265,0,0,1.3085265,56.033246,-73.00885)" />
<path
style="fill:#ffffff;stroke:#000000;stroke-width:5.46616"
d="M 345.13114,686.08528 C 222.64086,675.79533 119.51392,599.94853 74.016249,486.68834 64.419992,462.79978 58.296197,439.49115 53.966274,410.37335 51.203358,391.79333 51.205528,346.25661 53.970219,327.58931 61.4703,276.94849 77.618278,233.84988 103.92601,194.25835 143.90493,134.09253 200.40964,90.698198 268.39759,67.947933 322.37862,49.884696 378.75198,46.533168 433.87378,58.109989 c 82.82951,17.396066 153.43381,65.362801 200.69115,136.344371 10.701,16.07314 25.83502,46.00531 32.67962,64.63398 6.70243,18.2417 13.10531,43.27443 16.2797,63.64715 2.0974,13.46078 2.48648,20.4825 2.53407,45.73343 0.0589,31.24975 -0.58565,38.59247 -5.57665,63.52913 -26.7791,133.797 -136.64661,235.61462 -272.39375,252.43569 -17.24628,2.13707 -47.67501,2.9353 -62.95678,1.65154 z M 422.60679,489.0315 C 535.93722,423.59631 628.78214,369.70078 628.92885,369.26367 629.20612,368.43746 217.06471,130.09256 214.27041,129.46316 c -1.37426,-0.30954 -1.58595,31.67289 -1.58595,239.60604 0,227.99566 0.0965,239.93763 1.9335,239.44933 1.06342,-0.28267 94.65839,-54.05183 207.98883,-119.48703 z"
id="path374"
transform="matrix(0.26458333,0,0,0.26458333,11.238281,30.525391)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,42 +0,0 @@
import {getMatch} from "./match/match";
import {storageDelete, storageGet, getSetting} from "./store/store";
import {Match, matches} from "./match/matches";
async function main() {
let match: Match;
let redirect = false;
if ((match = await getMatch(window.location.host)) === undefined) {
let id: string
if ((id = await storageGet('redirect')) !== undefined) {
redirect = true
match = matches.find(m => m.id === id)
} else {
return
}
}
const re = document.body.innerHTML.match(match.regex)
if (re === null) return
if (redirect) await storageDelete('redirect')
const url = await match.match(re)
if (await getSetting("ff2mpv")) {
chrome.runtime.sendMessage({action: "ff2mpv", url: url})
}
if (match.replace && !url.includes('.m3u8')) {
const player = document.createElement('video')
player.style.width = '100%'
player.style.height = '100%'
player.controls = true
player.src = url
document.body.innerHTML = ''
document.body.append(player)
} else {
window.location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${window.location.host}`))
}
}
main()

296
src/lib/match.ts Normal file
View File

@ -0,0 +1,296 @@
import { unpack } from './utils';
import { Hosters, Redirect } from './settings';
export interface Match {
name: string;
id: string;
domains: string[];
replace?: boolean;
regex: RegExp;
notice?: string;
match(match: RegExpMatchArray): Promise<string | null>;
}
export const Doodstream: Match = {
name: 'Doodstream',
id: 'doodstream',
domains: [
'doodstream.com',
'dood.pm',
'dood.ws',
'dood.wf',
'dood.cx',
'dood.sh',
'dood.watch',
'dood.to',
'dood.so',
'dood.la',
'dood.li',
'dood.re',
'dood.yt',
'doods.pro',
'ds2play.com',
'dooood.com',
'd000d.com'
],
replace: true,
regex: /(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s,
match: async (match: RegExpMatchArray) => {
const response = await fetch(`https://${window.location.host}${match[1]}`, {
headers: {
Range: 'bytes=0-'
},
referrer: `https://${window.location.host}/e/${
window.location.pathname.split('/').slice(-1)[0]
}`
});
return `${await response.text()}1234567890${match[2]}${Date.now()}`;
}
};
export const DropLoad: Match = {
name: 'Dropload',
id: 'dropload',
domains: ['dropload.ui'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Filemoon: Match = {
name: 'Filemoon',
id: 'filemoon',
domains: ['filemoon.sx', 'filemoon.in'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const GoodStream: Match = {
name: 'Goodstream',
id: 'goodstream',
domains: ['goodstream.uno'],
regex: /(?<=file:\s+").*(?=")/g,
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
export const Kwik: Match = {
name: 'Kwik',
id: 'kwik',
domains: ['kwik.cx'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=source=').*(?=')/)![0];
}
};
export const Mixdrop: Match = {
name: 'Mixdrop',
id: 'mixdrop',
domains: ['mixdrop.co', 'mixdrop.to', 'mixdrop.ch', 'mixdrop.bz', 'mixdrop.gl'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
return `https:${url}`;
}
};
export const Mp4Upload: Match = {
name: 'Mp4Upload',
id: 'mp4upload',
domains: ['mp4upload.com'],
replace: true,
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
}
};
export const Newgrounds: Match = {
name: 'Newgrounds',
id: 'newgrounds',
domains: ['newgrounds.com'],
regex: /.*/gm,
match: async () => {
const id = window.location.pathname.split('/').slice(-1)[0];
const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const json = await response.json();
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']);
}
};
export const StreamA2z: Match = {
name: 'Stream2Az',
id: 'stream2az',
domains: ['streama2z.com', 'streama2z.xyz'],
regex: /https?:\/\/\S*m3u8.+(?=['"])/gm,
match: async (match: RegExpMatchArray) => {
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
await Redirect.set(StreamA2z);
return null;
}
return match[0];
}
};
export const Streamtape: Match = {
name: 'Streamtape',
id: 'streamtape',
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
regex: /id=.*(?=')/gm,
match: async (match: RegExpMatchArray) => {
let i = 0;
while (i < match.length) {
if (match[++i - 1] == match[i]) {
return `https://streamtape.com/get_video?${match[i]}`;
}
}
// use the old method as fallback
return `https://streamtape.com/get_video?${match.reverse()[0]}`;
}
};
export const Streamzz: Match = {
name: 'Streamzz',
id: 'streamzz',
domains: ['streamzz.to', 'streamz.ws'],
regex: /(?<=\|)\w{2,}/gm,
match: async (match: RegExpMatchArray) => {
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
match.sort((a, b) => b.length - a.length)[0]
}.dll`;
}
};
export const SuperVideo: Match = {
name: 'Supervideo',
id: 'supervideo',
domains: ['supervideo.tv'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Upstream: Match = {
name: 'Upstream',
id: 'upstream',
domains: ['upstream.to'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms,
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Vidmoly: Match = {
name: 'Vidmoly',
id: 'vidmoly',
domains: ['vidmoly.me', 'vidmoly.to'],
regex: /(?<=file:").+\.m3u8/gm,
match: async (match: RegExpMatchArray) => {
alert('a');
return match[0];
}
};
export const Vidoza: Match = {
name: 'Vidoza',
id: 'vidoza',
domains: ['vidoza.net'],
regex: /(?<=src:\s?").+?(?=")/gm,
replace: true,
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
export const Voe: Match = {
name: 'Voe',
id: 'voe',
domains: ['voe.sx'],
regex: /https?:\/\/\S*m3u8.+(?=['"])/gm,
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
export const Vupload: Match = {
name: 'Vupload',
id: 'vupload',
domains: ['vupload.com'],
regex: /(?<=src:\s?").+?(?=")/gm,
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
export const matches = {
[Doodstream.id]: Doodstream,
[DropLoad.id]: DropLoad,
[Filemoon.id]: Filemoon,
[GoodStream.id]: GoodStream,
[Kwik.id]: Kwik,
[Mixdrop.id]: Mixdrop,
[Mp4Upload.id]: Mp4Upload,
[Newgrounds.id]: Newgrounds,
[StreamA2z.id]: StreamA2z,
[Streamtape.id]: Streamtape,
[Streamzz.id]: Streamzz,
[SuperVideo.id]: SuperVideo,
[Upstream.id]: Upstream,
[Vidmoly.id]: Vidmoly,
[Vidoza.id]: Vidoza,
[Voe.id]: Voe,
[Vupload.id]: Vupload
};
export async function getMatch(domain: string): Promise<Match | null> {
if (await Hosters.getAllDisabled()) {
return null;
}
for (const match of Object.values(matches)) {
if (
match.domains.indexOf(domain) !== -1 &&
!(await Hosters.getDisabled().then((d) => d.find((p) => p.id == match.id)))
) {
return match;
}
}
return null;
}

85
src/lib/settings.ts Normal file
View File

@ -0,0 +1,85 @@
import type { Match } from './match';
import { matches } from './match';
export const Hosters = {
getDisabled: async () => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
return disabled.map((id) => matches[id]).filter((m) => m !== undefined);
},
disable: async (match: Match) => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
const index = disabled.indexOf(match.id);
if (index === -1) {
disabled.push(match.id);
await storageSet('hosters.disabled', disabled);
}
},
enable: async (match: Match) => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
const index = disabled.indexOf(match.id);
if (index !== -1) {
disabled.splice(index, 1);
await storageSet('hosters.disabled', disabled);
}
},
getAllDisabled: async () => {
return await storageGet<boolean>('hosters.allDisabled', false);
},
setAll: async (enable: boolean) => {
await storageSet('hosters.allDisabled', !enable);
}
};
export const Redirect = {
get: async (): Promise<Match | null> => {
return matches[(await storageGet('redirect')) as string] || null;
},
set: async (match: Match) => {
await storageSet('redirect', match.id);
},
delete: async () => {
await storageDelete('redirect');
}
};
export const Other = {
getFf2mpv: async () => {
return await storageGet('other.ff2mpv', false);
},
setFf2mpv: async (enable: boolean) => {
await storageSet('other.ff2mpv', enable);
}
};
export async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
let resolve: (value: T | undefined) => void;
const promise = new Promise<T | undefined>((r) => (resolve = r));
chrome.storage.local.get(key, (entry) => {
const value = entry[key];
resolve(value === undefined ? defaultValue : value);
});
return promise;
}
export async function storageSet<T>(key: string, value: T) {
let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r));
const obj = {
[key]: value
};
chrome.storage.local.set(obj, () => resolve());
return promise;
}
export async function storageDelete(key: string) {
let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r));
chrome.storage.local.remove(key, () => resolve());
return promise;
}

92
src/lib/utils.ts Normal file
View File

@ -0,0 +1,92 @@
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
export async function unpack(packed: string): Promise<string> {
const context = `
{
eval: function (c) {
packed = c;
},
window: {},
document: {}
}
`;
const toExecute = `
function() {
let packed = "";
with(${context}) {
${packed}
};
return packed;
}'
`;
const res = (await runInPageContext(toExecute)) as string;
return res
.replace(/;/g, ';\n')
.replace(/{/g, '\n{\n')
.replace(/}/g, '\n}\n')
.replace(/\n;\n/g, ';\n')
.replace(/\n\\n/g, '\n');
}
// Adapted from: https://github.com/arikw/extension-page-context
async function runInPageContext<T>(toExecute: string): Promise<T | null> {
// test that we are running with the allow-scripts permission
try {
window.sessionStorage;
} catch (ignore) {
return null;
}
// returned value container
const resultMessageId = crypto.randomUUID();
// prepare script container
const scriptElm = document.createElement('script');
scriptElm.setAttribute('type', 'application/javascript');
// inject the script
scriptElm.textContent = `
(
async function () {
const response = {
id: ${resultMessageId}
};
try {
response.result = JSON.stringify(await (${toExecute})() ); // run script
} catch(err) {
response.error = JSON.stringify(err);
}
window.postMessage(response, '*');
}
)();
`;
// run the script
document.documentElement.appendChild(scriptElm);
// clean up script element
scriptElm.remove();
let resolve: (value: T) => void;
let reject: (value: any) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
function onResult(event: MessageEvent) {
if (event.data.id === resultMessageId) {
window.removeEventListener('message', onResult);
if (event.data.error !== undefined) {
return reject(JSON.parse(event.data.error));
}
return resolve(event.data.result !== undefined ? JSON.parse(event.data.result) : undefined);
}
}
window.addEventListener('message', onResult);
return await promise;
}

View File

@ -1,51 +0,0 @@
{
"manifest_version": 2,
"name": "Stream Bypass",
"author": "ByteDream",
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
"version": "2.2.0",
"homepage_url": "https://github.com/ByteDream/stream-bypass",
"browser_specific_settings": {
"gecko": {
"id": "{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
}
},
"content_scripts": [
{
"all_frames": true,
"matches": [
"<all_urls>"
],
"js": [
"index.js"
],
"run_at": "document_end"
}
],
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"storage",
"webRequest",
"nativeMessaging",
"<all_urls>"
],
"web_accessible_resources": [
"ui/player/*"
],
"browser_action": {
"default_icon": {
"48": "icons/logo_48.png",
"128": "icons/logo_128.png"
},
"default_title": "Stream Bypass",
"default_popup": "ui/popup/popup.html"
},
"icons": {
"48": "icons/logo_48.png",
"128": "icons/logo_128.png"
}
}

87
src/manifest.ts Normal file
View File

@ -0,0 +1,87 @@
import pkg from '../package.json';
import { matches } from './lib/match';
const sharedManifest: Partial<chrome.runtime.ManifestBase> = {
browser_specific_settings: {
gecko: {
id: '{55dd42e8-3dd9-455a-b4fe-86664881b10c}'
},
gecko_android: {}
},
content_scripts: [
{
all_frames: true,
matches: Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`)),
js: ['src/entries/contentScript/main.ts'],
run_at: 'document_end'
}
],
icons: {
16: 'icons/stream-bypass@16px.png',
32: 'icons/stream-bypass@32px.png',
48: 'icons/stream-bypass@48px.png',
96: 'icons/stream-bypass@96px.png',
128: 'icons/stream-bypass@128px.png'
},
permissions: ['storage'],
optional_permissions: ['nativeMessaging']
};
const browserAction = {
default_icon: {
16: 'icons/stream-bypass@16px.png',
32: 'icons/stream-bypass@32px.png'
},
default_popup: 'src/entries/popup/index.html'
};
const ManifestV2 = {
...sharedManifest,
background: {
scripts: ['src/entries/background/mv2.ts'],
persistent: true
},
content_scripts: [{ ...sharedManifest.content_scripts![0], matches: ['<all_urls>'] }],
browser_action: browserAction,
permissions: [...sharedManifest.permissions, 'webRequest', 'webRequestBlocking', '<all_urls>']
};
const ManifestV3 = {
...sharedManifest,
action: browserAction,
background: {
service_worker: 'src/entries/background/mv3.ts'
}
};
export function getManifest(
manifestVersion: number
): chrome.runtime.ManifestV2 | chrome.runtime.ManifestV3 {
const manifest = {
author: pkg.author,
description: pkg.description,
name: pkg.displayName ?? pkg.name,
version: pkg.version
};
if (manifestVersion === 2) {
return {
...manifest,
...ManifestV2,
manifest_version: manifestVersion
} as chrome.runtime.ManifestV2;
}
if (manifestVersion === 3) {
return {
...manifest,
// just like all the adblockers which are unable to fully work under MV3, we need access to every website
// the user enters in order to work correctly, which is forbidden when using MV3
name: `${manifest.name} Lite`,
...ManifestV3,
manifest_version: manifestVersion
} as chrome.runtime.ManifestV3;
}
throw new Error(`Missing manifest definition for manifestVersion ${manifestVersion}`);
}

View File

@ -1,14 +0,0 @@
import {Match, matches} from "./matches";
import {getAllDisabled, getDisabled} from "../store/store";
export async function getMatch(host: string): Promise<Match | undefined> {
if (await getAllDisabled()) {
return undefined
}
for (const match of matches) {
if (match.domains.some(v => host.indexOf(v) !== -1) && !((await getDisabled()).some(v => v === match))) {
return match
}
}
}

View File

@ -1,294 +0,0 @@
import {unPack} from "./unpack";
export enum Reliability {
HIGH,
NORMAL,
LOW,
}
export abstract class Match {
name: string
id: string
reliability: Reliability
domains: string[]
replace?: boolean
regex: RegExp
abstract match(match: RegExpMatchArray): Promise<string>
notice?: string
}
class Doodstream implements Match {
name = 'Doodstream'
id = 'doodstream'
reliability = Reliability.NORMAL
domains = [
'doodstream.com',
'dood.pm',
'dood.ws',
'dood.wf',
'dood.cx',
'dood.sh',
'dood.watch',
'dood.to',
'dood.so',
'dood.la',
'dood.re'
]
replace = true
regex = new RegExp(/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s)
async match(match: RegExpMatchArray): Promise<string> {
const response = await fetch(`https://${window.location.host}${match[1]}`, {
headers: {
'Range': 'bytes=0-'
},
referrer: `https://${window.location.host}/e/${window.location.pathname.split('/').slice(-1)[0]}`,
});
return `${await response.text()}1234567890${match[2]}${Date.now()}`
}
}
class Filemoon implements Match {
name = 'Filemoon'
id = 'filemoon'
reliability = Reliability.HIGH
domains = [
'filemoon.sx',
'filemoon.in'
]
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
return url
}
}
class Mixdrop implements Match {
name = 'Mixdrop'
id = 'mixdrop'
reliability = Reliability.HIGH
domains = [
'mixdrop.co',
'mixdrop.to',
'mixdrop.ch',
'mixdrop.bz',
'mixdrop.gl'
]
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)[0]
return `https:${url}`
}
}
class Mp4Upload implements Match {
name = 'Mp4Upload'
id = 'mp4upload'
reliability = Reliability.HIGH
domains = [
'mp4upload.com'
]
replace = true
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=player.src\(").*(?=")/)[0]
return url
}
}
class Newgrounds implements Match {
name = 'Newgrounds'
id = 'newgrounds'
reliability = Reliability.HIGH
domains = [
'newgrounds.com'
]
regex = new RegExp(/.*/gm)
async match(match: RegExpMatchArray): Promise<string> {
let id = window.location.pathname.split('/').slice(-1)[0]
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
let json = await response.json()
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
}
}
class Streamtape implements Match {
name = 'Streamtape'
id = 'streamtape'
reliability = Reliability.NORMAL
domains = [
'streamtape.com'
]
regex = new RegExp(/id=.*(?=')/gm)
async match(match: RegExpMatchArray): Promise<string> {
return `https://streamtape.com/get_video?${match.reverse()[0]}`
}
}
class Streamzz implements Match {
name = 'Streamzz'
id = 'streamzz'
reliability = Reliability.LOW
domains = [
'streamzz.to',
'streamz.ws'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
async match(match: RegExpMatchArray): Promise<string> {
return `https://get.${document.domain.split('.')[0]}.tw/getlink-${match.sort((a, b) => b.length - a.length)[0]}.dll`
}
}
class Upstream implements Match {
name = 'Upstream'
id = 'upstream'
reliability = Reliability.HIGH
domains = [
'upstream.to'
]
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
return url
}
}
class Vidoza implements Match {
name = 'Vidoza'
id = 'vidoza'
reliability = Reliability.HIGH
domains = [
'vidoza.net'
]
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
async match(match: RegExpMatchArray): Promise<string> {
return match[0]
}
}
class Voe implements Match {
name = 'Voe'
id = 'voe'
reliability = Reliability.HIGH
domains = [
'voe.sx'
]
regex = new RegExp(/https?:\/\/\S*m3u8.+(?=')/gm)
async match(match: RegExpMatchArray): Promise<string> {
return match[0]
}
}
class Vupload implements Match {
name = 'Vupload'
id = 'vupload'
reliability = Reliability.HIGH
domains = [
'vupload.com'
]
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
async match(match: RegExpMatchArray): Promise<string> {
return match[0]
}
}
class Kwik implements Match {
name = 'Kwik'
id = 'kwik'
reliability = Reliability.HIGH
domains = [
'kwik.cx'
]
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
console.log(match[0]);
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=source=').*(?=')/)[0]
return url
}
}
class DropLoad implements Match {
name = 'Dropload'
id = 'dropload'
reliability = Reliability.HIGH
domains = [
'dropload.io'
]
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
return url
}
}
class SuperVideo implements Match {
name = 'Supervideo'
id = 'supervideo'
reliability = Reliability.HIGH
domains = [
'supervideo.tv'
]
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
return url
}
}
class GoodStream implements Match {
name = 'Goodstream'
id = 'goodstream'
reliability = Reliability.NORMAL
domains = [
'goodstream.uno'
]
regex = new RegExp(/(?<=file:\s+").*(?=")/g)
async match(match: RegExpMatchArray): Promise<string> {
return match[0]
}
}
export const matches = [
new Doodstream(),
new Filemoon(),
new Mixdrop(),
new Mp4Upload(),
new Newgrounds(),
new Streamtape(),
new Streamzz(),
new Upstream(),
new Vidoza(),
new Voe(),
new Vupload(),
new Kwik(),
new DropLoad(),
new SuperVideo(),
new GoodStream()
]

View File

@ -1,84 +0,0 @@
function runInPageContext(to_execute: string) {
// Adapted from: https://github.com/arikw/extension-page-context
const doc = document;
// test that we are running with the allow-scripts permission
try { window.sessionStorage; } catch (ignore) { return null; }
// returned value container
const resultMessageId = parseInt('' + Math.floor((Math.random() * 100) + 1) + ((new Date()).getTime()));
// prepare script container
let scriptElm = doc.createElement('script');
scriptElm.setAttribute("type", "application/javascript");
const code = `
(
async function () {
const response = {
id: ${resultMessageId}
};
try {
response.result = JSON.stringify(await (${to_execute})() ); // run script
} catch(err) {
response.error = JSON.stringify(err);
}
window.postMessage(response, '*');
}
)();
`;
// inject the script
scriptElm.textContent = code;
// run the script
doc.documentElement.appendChild(scriptElm);
// clean up script element
scriptElm.remove();
// create a "flat" promise
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
// resolve on result
function onResult(event) {
const data = Object(event.data);
if (data.id === resultMessageId) {
window.removeEventListener('message', onResult);
if (data.error !== undefined) {
return reject(JSON.parse(data.error));
}
return resolve((data.result !== undefined) ? JSON.parse(data.result) : undefined);
}
}
window.addEventListener('message', onResult);
return promise;
}
export const unPack = async (packed: String): Promise<string> => {
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
let context = `
{
eval: function (c) {
packed = c;
},
window: {},
document: {}
}
`
const to_execute = `function() { let packed = ""; with(${context}) { ` + packed + '}; return packed; }'
const res = await runInPageContext(to_execute);
return (res+"").replace(/;/g, ";\n").replace(/{/g, "\n{\n").replace(/}/g, "\n}\n").replace(/\n;\n/g, ";\n").replace(/\n\\n/g, "\n");
}

View File

@ -1,27 +0,0 @@
import { storageSet, storageGet } from "./store"
export class Setting {
name: string
info_url?: string
constructor(name: string, info_url?: string) {
this.name = name
this.info_url = info_url
}
async enable() {
await storageSet(this.name, true)
}
async disable() {
await storageSet(this.name, false)
}
async get_status() {
return await storageGet(this.name)
}
}
export const Settings = [
new Setting("ff2mpv", "https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams")
]

View File

@ -1,70 +0,0 @@
import {Match, matches} from "../match/matches";
export async function storageGet(key: string): Promise<any> {
return new Promise((resolve) => {
chrome.storage.local.get(key, (value) => {
resolve(value[key])
})
})
}
export async function storageSet(key: string, value: any) {
const obj = {}
obj[key] = value
await chrome.storage.local.set(obj)
}
export async function storageDelete(key: string) {
await chrome.storage.local.remove(key)
}
export async function getDisabled(): Promise<Match[]> {
const localMatches = []
for (const disabled of (await storageGet('disabled') as string[]) || []) {
let m: Match
if ((m = matches.find((v) => v.id === disabled)) !== undefined) {
localMatches.push(m)
}
}
return localMatches
}
export async function getAllDisabled(): Promise<boolean> {
const value = await storageGet('all')
return value !== undefined ? String(value).toLowerCase() === 'true' : false
}
export async function enableAll() {
await storageSet('all', false)
await chrome.browserAction.setIcon({
path: null
})
}
export async function disableAll() {
await storageSet('all', true)
await chrome.browserAction.setIcon({
path: {
48: chrome.runtime.getURL('icons/disabled_48.png'),
128: chrome.runtime.getURL('icons/disabled_128.png')
}
})
}
export async function enable(match: Match) {
const disabled = await storageGet('disabled') || []
disabled.splice(disabled.indexOf(match.id))
await storageSet('disabled', disabled)
}
export async function disable(match: Match) {
const disabled = await storageGet('disabled') as string[] || []
if (disabled.indexOf(match.id) !== undefined) {
disabled.push(match.id)
await storageSet('disabled', disabled)
}
}
export const getSetting = storageGet

View File

@ -1,18 +0,0 @@
{
"compilerOptions": {
"module": "esnext",
"target": "es2019",
"removeComments": true,
"esModuleInterop": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015.collection",
"es2015.promise"
],
},
"exclude": [
"../node_modules"
]
}

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stream Bypass</title>
<link rel="stylesheet" href="player.css">
<script src="player.js" defer></script>
</head>
<body>
<video id="video"></video>
<div id="message-container">
<p id="message"></p>
<br>
<p>Open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a></p>
</div>
</body>
</html>

View File

@ -1,34 +0,0 @@
body {
background-color: #131313;
}
html, body, video {
width: 100%;
height: 100%;
margin: 0;
}
video {
position: absolute;
top: 0;
left: 0;
}
#message-container {
visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: white;
text-align: center;
height: 100%;
& * {
visibility: inherit;
}
& a, & a:visited {
color: red
}
}

View File

@ -1,50 +0,0 @@
import {matches} from "../../match/matches";
// @ts-ignore
import Hls from "hls.js";
function show_message(message: string) {
document.getElementById('message').innerText = message
document.getElementById('message-container').style.visibility = 'visible'
document.getElementById('video').hidden = true
}
async function play_native(url: string) {
const video = document.getElementById('video') as HTMLVideoElement
video.controls = true
video.src = url
}
async function play_hls(url: string) {
const video = document.getElementById('video') as HTMLVideoElement
video.controls = true
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url
} else if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: false
})
hls.loadSource(url)
hls.attachMedia(video)
} else {
show_message('Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>')
}
}
async function main() {
const urlQuery = new URLSearchParams(window.location.search)
const id = urlQuery.get('id')
const url = decodeURIComponent(urlQuery.get('url'))
const domain = urlQuery.get('domain')
const match = matches.find((m) => m.id === id)
if (match === undefined) {
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`)
return
}
document.title = `Stream Bypass (${domain})`
new URL(url).pathname.endsWith('.m3u8') ? await play_hls(url) : await play_native(url)
}
main()

View File

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stream-Bypass</title>
<link rel="stylesheet" href="popup.css">
<script src="popup.js" defer></script>
</head>
<body>
<div id="container">
<div id="all">
<div class="buttons">
<a>On</a>
<a>Off</a>
</div>
<p>Hosters</p>
<table id="sub-container">
</table>
<hr class="upper-hr">
<p>Settings</p>
<table id="settings-container">
</table>
</div>
<hr>
<table id="sub-container">
</table>
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues">Something does not work</a>
</div>
</body>
</html>

View File

@ -1,76 +0,0 @@
body {
background-color: #2b2a33;
font-weight: bold;
max-height: 500px;
overflow-x: hidden;
overflow-y: auto;
}
a, p {
color: white;
font-size: 16px;
margin: 5px 0;
cursor: default;
}
a {
border: 1px solid #281515;
cursor: pointer;
font-weight: normal;
padding: 5px 8px;
&.active {
background-color: rgba(255, 65, 65, 0.74);
cursor: default;
}
&.disabled {
background-color: grey;
cursor: not-allowed;
}
&#error {
border: none;
display: block;
font-weight: lighter;
font-size: 0.8rem;
text-align: center;
padding: 10px 0 5px 0;
}
}
hr {
margin: 3px 0;
&.upper-hr {
width: 100%;
}
}
#all {
display: flex;
align-items: center;
flex-direction: column;
margin: 10px 0
}
.buttons {
margin-bottom: 10px;
}
.low-reliability {
text-decoration: underline;
text-decoration-color: rgb(255, 0, 0);
}
.normal-reliability {
text-decoration: underline;
text-decoration-color: yellow;
}
.high-reliability {
text-decoration: underline;
text-decoration-color: #00ff00;
}

View File

@ -1,142 +0,0 @@
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
import {matches, Reliability} from "../../match/matches";
import { Settings, Setting } from "../../store/settings";
async function main() {
const disabled = await getDisabled()
const subContainer = document.getElementById('sub-container')
for (const m of matches) {
const row = document.createElement('tr')
const name = document.createElement('td')
const nameValue = document.createElement('p')
nameValue.innerText = m.name
switch (m.reliability) {
case Reliability.LOW:
nameValue.classList.add('low-reliability')
break
case Reliability.NORMAL:
nameValue.classList.add('normal-reliability')
break
case Reliability.HIGH:
nameValue.classList.add('high-reliability')
break
}
const buttons = document.createElement('td')
buttons.classList.add('buttons')
const on = document.createElement('a')
on.innerText = 'On'
const off = document.createElement('a')
off.innerText = 'Off'
disabled.find((v) => v.id === m.id) === undefined ? on.classList.add('active') : off.classList.add('active')
on.onclick = async function () {
if (!on.classList.contains('disabled')) {
await enable(m)
on.classList.add('active')
off.classList.remove('active')
}
}
off.onclick = async function () {
if (!off.classList.contains('disabled')) {
await disable(m)
on.classList.remove('active')
off.classList.add('active')
}
}
name.append(nameValue)
buttons.append(on, off)
row.append(name, buttons)
subContainer.append(row)
}
const settingsContainer = document.getElementById("settings-container")
for (const s of Settings) {
const row = document.createElement('tr')
const name = document.createElement('td')
const nameValue = document.createElement('p')
nameValue.innerText = s.name
const buttons = document.createElement('td')
buttons.classList.add('buttons')
const on = document.createElement('a')
on.innerText = 'On'
const off = document.createElement('a')
off.innerText = 'Off'
const info = document.createElement('a')
info.target = "_blank"
info.href = s.info_url
info.innerText = "🛈"
await s.get_status() ? on.classList.add('active') : off.classList.add('active')
on.onclick = async function () {
if (!on.classList.contains('disabled')) {
await s.enable()
on.classList.add('active')
off.classList.remove('active')
}
}
off.onclick = async function () {
if (!off.classList.contains('disabled')) {
await s.disable()
on.classList.remove('active')
off.classList.add('active')
}
}
name.append(nameValue)
buttons.append(on, off)
if (s.info_url) {
buttons.append(info)
}
row.append(name, buttons)
settingsContainer.append(row)
}
const allOnButton = document.getElementById('all').getElementsByTagName('a')[0]
const allOffButton = document.getElementById('all').getElementsByTagName('a')[1]
if (await getAllDisabled()) {
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
for (let i = 0; i < allBtns.length; i++) {
allBtns[i].classList.add('disabled')
}
allOffButton.classList.add('active')
} else {
allOnButton.classList.add('active')
}
allOnButton.onclick = async () => {
if (!allOnButton.classList.contains('active')) {
allOnButton.classList.add('active')
allOffButton.classList.remove('active')
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
for (let i = 0; i < allBtns.length; i++) {
allBtns[i].classList.remove('disabled')
}
await enableAll()
}
}
allOffButton.onclick = async () => {
if (!allOffButton.classList.contains('active')) {
allOffButton.classList.add('active')
allOnButton.classList.remove('active')
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
for (let i = 0; i < allBtns.length; i++) {
allBtns[i].classList.add('disabled')
}
await disableAll()
}
}
}
main()

3
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
/// <reference types="@samrum/vite-plugin-web-extension/client" />

7
svelte.config.js Normal file
View File

@ -0,0 +1,7 @@
import sveltePreprocess from 'svelte-preprocess';
export default {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: sveltePreprocess()
};

View File

@ -1,103 +0,0 @@
const fs = require('fs')
const path = require('path')
const rollup = require('rollup')
const rollupPluginCommonJs = require('@rollup/plugin-commonjs')
const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default
const rollupPluginReplace = require('@rollup/plugin-replace')
const rollupPluginTypescript = require('@rollup/plugin-typescript')
const sass = require('sass')
const typescript = require('typescript')
async function buildManifest() {
const manifest = JSON.parse(fs.readFileSync('src/manifest.json'))
manifest['version'] = process.env.npm_package_version
fs.writeFileSync('src/manifest.json', JSON.stringify(manifest, null, 2))
}
async function buildMisc() {
const files = {
'src/manifest.json': 'build/manifest.json',
'src/icons/logo_48.png': 'build/icons/logo_48.png',
'src/icons/logo_128.png': 'build/icons/logo_128.png',
'src/icons/disabled_48.png': 'build/icons/disabled_48.png',
'src/icons/disabled_128.png': 'build/icons/disabled_128.png',
}
for (const [src, dst] of Object.entries(files)) {
fs.mkdirSync(path.dirname(dst), {recursive: true})
fs.copyFileSync(src, dst)
}
}
async function buildHtml() {
const files = {
'src/ui/popup/popup.html': 'build/ui/popup/popup.html',
'src/ui/player/player.html': 'build/ui/player/player.html'
}
for (const [src, dst] of Object.entries(files)) {
fs.mkdirSync(path.dirname(dst), {recursive: true})
fs.copyFileSync(src, dst)
}
}
async function buildCss() {
const files = {
'src/ui/popup/popup.scss': 'build/ui/popup/popup.css',
'src/ui/player/player.scss': 'build/ui/player/player.css'
}
for (const [src, dst] of Object.entries(files)) {
const compiled = await sass.compileAsync(src)
fs.mkdirSync(path.dirname(dst), {recursive: true})
fs.writeFileSync(dst, compiled.css)
}
}
async function buildJs() {
const files = {
'src/ui/popup/popup.ts': 'build/ui/popup/popup.js',
'src/ui/player/player.ts': 'build/ui/player/player.js',
'src/index.ts': 'build/index.js',
'src/background.ts': 'build/background.js'
}
for (const [src, dst] of Object.entries(files)) {
const bundle = await rollup.rollup({
input: src,
plugins: [
rollupPluginNodeResolve({
browser: true
}),
rollupPluginReplace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
rollupPluginTypescript({
typescript,
tsconfig: 'src/tsconfig.json'
}),
rollupPluginCommonJs({
extensions: ['.js', '.ts']
})
]
})
await bundle.write({
file: dst,
strict: true
})
}
}
async function build() {
await buildManifest()
await buildMisc()
await buildHtml()
await buildCss()
await buildJs()
}
build()

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"target": "es2019",
"module": "esnext",
"useDefineForClassFields": true,
"resolveJsonModule": true,
"removeComments": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
},
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}

38
vite.config.ts Normal file
View File

@ -0,0 +1,38 @@
import { defineConfig, loadEnv } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import webExtension from '@samrum/vite-plugin-web-extension';
import path from 'path';
import { getManifest } from './src/manifest';
import { matches } from './src/lib/match';
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
svelte(),
webExtension({
manifest: getManifest(Number(env.MANIFEST_VERSION)),
additionalInputs: {
html: [
{
fileName: 'src/entries/player/player.html',
webAccessible: {
matches:
Number(env.MANIFEST_VERSION) === 3
? Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`))
: ['<all_urls>']
}
}
]
}
})
],
resolve: {
alias: {
'~': path.resolve(__dirname, './src')
}
}
};
});