Merge pull request #2 from ByteDream/v2

Update to v2
This commit is contained in:
ByteDream 2022-06-20 10:45:23 +02:00 committed by GitHub
commit 60a2247b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 987 additions and 731 deletions

3
.gitignore vendored
View File

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

View File

@ -1,9 +0,0 @@
FROM alpine:latest
RUN apk --no-cache add python3 nodejs npm
RUN npm install -g typescript sass
COPY [".", "."]
CMD ["python3", "build.py", "-b", "-c"]

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 ByteDream
Copyright (c) 2022 ByteDream
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

141
README.md
View File

@ -2,65 +2,52 @@
A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.
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.
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.
Additionally this enables you to download the video by right-clicking it and just choose the download option.
<p align="center">
<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%20Addon%20Store&style=flat-square" alt="Firefox Addon Store">
<img src="https://img.shields.io/amo/users/stream-bypass?label=Firefox%20Store%20Downloads&style=flat-square" alt="Firefox Addon 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">
<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">
<img src="https://img.shields.io/discord/915659846836162561?label=Discord&style=flat-square" alt="Discord">
</a>
</p>
Supported streaming providers (for a complete list of all supported websites, see [here](SUPPORTED) or in [show all](#all-supported-websites) below):
- [streamtape.com](https://streamtape.com)
- [vivo.sx](https://vivo.sx)
- [voe.sx](https://voe.sx)
<p align="center">
<a href="#-introduction">Introduction 📝</a>
<a href="#-installation">Installation 📥</a>
<a href="#-supported-sites">Supported Sites 📜</a>
<a href="#%EF%B8%8F-compiling">Compiling ⚙️</a>
<a href="#-license">License ⚖</a>
</p>
<details id="all-supported-websites">
<summary><b>Show all</b></summary>
<ul>
<li><a href="https://evoload.io">evoload.io</a></li>
<li><a href="https://mcloud.to">mcloud.to</a></li>
<li><a href="https://mixdrop.co">mixdrop.co</a></li>
<li><a href="https://newgrounds.com">newgrounds.com</a></li>
<li><a href="https://streamtape.com">streamtape.com</a></li>
<li><a href="https://streamzz.to">streamzz.to</a></li>
<li><a href="https://thevideome.com">thevideome.com</a></li>
<li><a href="https://vidlox.me">vidlox.me</a></li>
<li><a href="https://vidstream.pro">vidstream.pro</a></li>
<li><a href="https://vidoza.net">vidoza.net</a></li>
<li><a href="https://vivo.st">vivo.st</a></li>
<li><a href="https://vivo.sx">vivo.sx</a></li>
<li><a href="https://voe.sx">voe.sx</a></li>
<li><a href="https://vupload.com">vupload.com</a></li>
</ul>
</details>
## 📝 Introduction
---
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.
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.
Additionally, this enables you to download the video by right-clicking it and just choose the download option.
<details id="example">
<summary><b>How it's working</b></summary>
<summary><b>How it's working:</b></summary>
<img src="example.gif" alt="">
</details>
The addon was tested on
- Firefox (96.0.3)
- Ungoogled Chromium (97.0)
- Vivaldi (5.0)
- Opera (83.0)
## Installing
## 📥 Installation
### Firefox
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/) or from the [latest release](https://smartrelease.bytedream.org/github/ByteDream/stream-bypass/stream_bypass-{tag}_firefox.xpi) (if anything newer is available since firefox disables the plugin sometimes because of a setting which is necessary to run the addon properly).
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/).
### Chromium / Google Chrome
@ -78,54 +65,60 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
4. Click Load unpacked.
5. Choose the cloned / unzipped directory.
## Compiling
## 📜 Supported sites
If you want to use / install the addon from source, you have to compile the `typescript` and `sass` files yourself.
- Compile it [manual](#manual).
- Compile it using [docker](#docker).
| Site | Supported | Note |
|-----------------------------------------------------------------------|-----------|--------------------------------------------------|
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ❌ | Reverse engineering the site costs too much time |
| [evoload.io](https://evoload.io) | ✔️ | |
| [mixdrop.co](https://mixdrop.co) | ✔ | |
| [mp4upload.com](https://mp4upload.com) | ❌ | URL can be extracted but not played |
| [newgrounds.com](https://newgrounds.com) | ✔ | |
| [streamtape.com](https://streamtape.com) | ✔ | |
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
| [upstream.to](https://upstream.to) | ✔ | |
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
| [vidlox.me](https://vidlox.me) | ⚠ | Website down / Timeout |
| [vidoza.net](https://vidoza.net) | ✔ | |
| [vivo.sx](https://vivo.sx) | ⚠️ | Website down / Timeout |
| [voe.sx](https://voe.sx) / [voeunblk.com](https://voeunblk.com) | ✔ | |
| [vupload.com](https://vupload.com) | ✔ | |
### Manual
- ✔️: 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.
- ❌: Not included / supported by the addon. This can have various reasons. See `Note` for an explanation.
For compiling everything bare bones, you need [typescript](https://www.typescriptlang.org/) and [sass](https://sass-lang.com/) installed.
- Compile typescript
```
$ tsc -p src
```
- Compile sass (replace `<path to sass file>` with every `.sass` file in the `src` directory)
```
$ sass --no-source-map <path to sass file>
```
The compiled output will be in the `src` directory.
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.
If you want to keep it a little cleaner, you additionally need [python3](https://www.python.org).
- Compile everything with one line
```
$ python3 build.py -b -c
```
The compiled output will remain in a (new created if not existing) `build` directory.
## ⚙️ Compiling
### Docker
If you want to compile the addon from source and not using the [installation](#installation) way, follow the instructions.
For this, you need [docker](https://www.docker.com/) to be installed.
- Build the docker image
```
$ docker build -t stream-bypass .
```
- Compile
```
$ docker run --rm -v build:/build stream-bypass
```
The compiled output will remain in a (new created if not existing) `build` directory.
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
$ npm run build
# same as build + create a bundle zipfile at dist/
$ npm run bundle
```
##### 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 [installing](#installing).
When using firefox, use the following
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).
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.
## License
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.

View File

@ -1,14 +0,0 @@
evoload.io
mcloud.to
mixdrop.co
newgrounds.com
streamtape.com
streamzz.to
thevideome.com
vidlox.me
vidstream.pro
vidoza.net
vivo.st
vivo.sx
voe.sx
vupload.com

158
build.py
View File

@ -1,158 +0,0 @@
#!/usr/bin/python3
import argparse
import io
import json
import sys
import urllib.request
import zipfile
from pathlib import Path
import re
import shutil
import subprocess
def load_matches():
matched = []
indexed = False
pattern = re.compile(r"(?<=\[')\S*(?=',)")
for line in open('src/match.ts', 'r'):
if not indexed:
if 'constmatches=[' in line.replace(' ', ''):
indexed = True
else:
match = pattern.findall(line)
if match:
if not line.strip().startswith('//'):
matched.append(match[0])
else:
break
return matched
def write_manifest():
matches = load_matches()
manifest = json.load(open('src/manifest.json', 'r'))
for content_script in manifest['content_scripts']:
content_script['matches'] = [f'*://*.{match}/*' for match in matches]
json.dump(manifest, open('src/manifest.json', 'w'), indent=2)
def write_supported():
open('SUPPORTED', 'w').writelines([f'{match}\n' for match in load_matches()])
def write_readme():
firefox_pattern = re.compile(r'Mozilla Firefox (?P<version>.+)')
chromium_pattern = re.compile(r'(?P<version>\d+\.\d+)')
tested = {}
stdout, stderr = subprocess.Popen(['firefox', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if stderr == b'':
tested['Firefox'] = re.search(firefox_pattern, stdout.decode('utf-8').replace('\n', '')).group('version')
for command, name in {'chromium': 'Ungoogled Chromium', 'vivaldi-stable': 'Vivaldi', 'opera': 'Opera'}.items():
stdout, stderr = subprocess.Popen([command, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if stderr == b'':
tested[name] = re.search(chromium_pattern, stdout.decode('utf-8').replace('\n', '')).group('version')
# it this the right syntax if i want to read and write to a file? * dreams in python3.10 *
with open('README.md', 'r') as read_file:
readme = read_file.read()
# adds all available websites
all_providers_regex = r'(?<=<ul>\n)(.+?)(?=</ul>)'
domains = filter(lambda domain: domain != '', open('SUPPORTED', 'r').read().split('\n'))
all_providers = '\n'.join(f'\t\t<li><a href="https://{supported}">{supported}</a></li>' for supported in domains) + '\n'
readme = re.sub(all_providers_regex, all_providers, readme, flags=re.DOTALL)
# adds all installed browsers to the tested browser section. i'm just to lazy to seek out all browser versions manually
tested_browsers_regex = r'(?<=The addon was tested on\n)(.+?)(?=\n*## Installing)'
tested_browsers = '\n'.join(f'- {name} ({version})' for name, version in tested.items())
readme = re.sub(tested_browsers_regex, tested_browsers, readme, flags=re.DOTALL)
# rewrite the readme
with open('README.md', 'w') as write_file:
write_file.write(readme)
write_file.close()
read_file.close()
def copy_built():
if not shutil.which('tsc'):
sys.stderr.write('The typescript compiler `tsc` could not be found')
sys.exit(1)
elif not shutil.which('sass'):
sys.stderr.write('The sass compiler `sass` could not be found')
sys.exit(1)
write_manifest()
subprocess.call(['tsc', '-p', 'src'])
build_path = Path('build')
if not build_path.is_dir():
build_path.mkdir()
for file in Path('src').rglob('*'):
build_file = build_path.joinpath(str(file)[4:])
if file.is_dir():
if not build_file.exists():
build_file.mkdir(parents=True)
elif file.suffix == '.sass':
css_file = str(file)[:-4] + 'css'
subprocess.call(['sass', '--no-source-map', file, css_file])
shutil.copy(css_file, str(build_path.joinpath(css_file[4:])))
elif file.name == 'tsconfig.json':
continue
elif file.suffix != '.ts':
shutil.copy(str(file), str(build_file))
ext_path = Path('build', 'ext')
if not ext_path.is_dir():
ext_path.mkdir()
# download hls.js (version 1.1.1)
with zipfile.ZipFile(io.BytesIO(urllib.request.urlopen('https://github.com/video-dev/hls.js/releases/download/v1.1.1/release.zip').read())) as z:
open(ext_path.joinpath('hls.light.min.js'), 'wb').write(z.read('dist/hls.light.min.js'))
z.close()
# download popperjs core (version 2.10.2)
open(ext_path.joinpath('popper.min.js'), 'wb').write(urllib.request.urlopen('https://unpkg.com/@popperjs/core@2.10.2/dist/umd/popper.min.js').read())
# download tippy.js (version 6.3.7)
open(ext_path.joinpath('tippy-bundle.umd.min.js'), 'wb').write(urllib.request.urlopen('https://unpkg.com/tippy.js@6.3.7/dist/tippy-bundle.umd.min.js').read())
def clean_build():
for file in Path('src').rglob('*'):
if file.suffix in ['.js', '.css', '.map']:
file.unlink()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--manifest', action='store_true', help='Builds the manifest.json file for addon information in ./src')
parser.add_argument('-r', '--readme', action='store_true', help='Updates the README.md with the currently installed ')
parser.add_argument('-s', '--supported', action='store_true', help='Builds the SUPPORTED file with all supported domains in the current directory')
parser.add_argument('-b', '--build', action='store_true', help='Creates a ./build folder and builds all typescript / sass files')
parser.add_argument('-c', '--clean', action='store_true', help='Cleans the ./src folder from .js, .css and .map files')
parsed = parser.parse_args()
if parsed.manifest:
write_manifest()
if parsed.readme:
write_readme()
if parsed.supported:
write_supported()
if parsed.build:
copy_built()
if parsed.clean:
clean_build()
if not parsed.manifest and not parsed.supported and not parsed.build and not parsed.clean:
print('\n'.join(load_matches()))

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "stream-bypass",
"version": "2.0.0",
"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"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ByteDream/stream-bypass.git"
},
"author": "ByteDream",
"license": "MIT",
"bugs": {
"url": "https://github.com/ByteDream/stream-bypass/issues"
},
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^22.0.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-typescript": "^8.3.3",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"@types/chrome": "^0.0.190",
"@types/node-sass": "^4.11.2",
"@types/yazl": "^2.4.2",
"eslint": "^8.17.0",
"hls.js": "^1.1.5",
"node-sass": "^7.0.1",
"node-sass-package-importer": "^5.3.2",
"rollup": "^2.75.6",
"tippy.js": "^6.3.7",
"typescript": "^4.7.3",
"web-ext": "^7.0.0"
}
}

77
src/icons/disabled.svg Normal file
View File

@ -0,0 +1,77 @@
<?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>

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src/icons/disabled_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
src/icons/disabled_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

77
src/icons/logo.svg Normal file
View File

@ -0,0 +1,77 @@
<?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>

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src/icons/logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
src/icons/logo_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,46 +1,24 @@
function hasSuffix(content: string, suffix: string) {
return content.indexOf(suffix, content.length - suffix.length) !== -1
}
import {matches} from "./match/match";
import {getAllDisabled, getDisabled} from "./store/store";
// @ts-ignore
chrome.storage.local.get(['all', 'disabled'], function (result) {
let keys = Object.keys(result)
if (keys.indexOf('all') !== -1 && !result['all']) {
async function main() {
if (await getAllDisabled()) {
return
}
// @ts-ignore
for (let match of matches) {
let domain = match[0] as string
if (window.location.href.indexOf(domain) !== -1) {
if (keys.indexOf('disabled') === -1 || result['disabled'].indexOf(domain) === -1) {
let regex = match[1] as RegExp
let matchClass = match[2] as Match
let reliability = match[3] as Reliability
let re
if (regex !== null) {
if ((re = document.body.innerHTML.match(regex)) === null) {
continue
}
} else {
re = document.body.innerHTML.match(regex)
}
if (matchClass === null) {
if (regex === null) {
location.assign(document.body.innerHTML)
} else {
// @ts-ignore
location.assign(hasSuffix(re[0], 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${re[0]}`) : re[0])
}
} else {
matchClass.match(re).then(function (path) {
// @ts-ignore
location.assign(hasSuffix(path, 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${path}`) : path)
})
}
}
return
for (const match of matches) {
if (!match.domains.some((v) => window.location.host.indexOf(v) !== -1) || ((await getDisabled()).some((v) => v === match))) {
continue
}
const re = document.body.innerHTML.match(match.regex)
if (re === null) {
continue
}
const url = await match.match(re)
location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}`))
}
})
}
main()

View File

@ -3,7 +3,7 @@
"name": "Stream Bypass",
"author": "ByteDream",
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
"version": "1.6.0",
"version": "2.0.0",
"homepage_url": "https://github.com/ByteDream/stream-bypass",
"browser_specific_settings": {
"gecko": {
@ -15,22 +15,20 @@
"all_frames": true,
"matches": [
"*://*.evoload.io/*",
"*://*.mcloud.to/*",
"*://*.mixdrop.co/*",
"*://*.newgrounds.com/*",
"*://*.streamtape.com/*",
"*://*.streamzz.to/*",
"*://*.thevideome.com/*",
"*://*.streamz.ws/*",
"*://*.upstream.to/*",
"*://*.vidlox.me/*",
"*://*.vidstream.pro/*",
"*://*.vidoza.net/*",
"*://*.vivo.st/*",
"*://*.vivo.sx/*",
"*://*.voe.sx/*",
"*://*.voeunblk.com/*",
"*://*.vupload.com/*"
],
"js": [
"match.js",
"index.js"
],
"run_at": "document_end"
@ -39,13 +37,16 @@
"permissions": [
"storage"
],
"content_security_policy": "script-src 'self' blob:; object-src 'self'",
"browser_action": {
"default_icon": "icons/stream-bypass.png",
"default_icon": {
"48": "icons/logo_48.png",
"128": "icons/logo_128.png"
},
"default_title": "Stream Bypass",
"default_popup": "popup/popup.html"
"default_popup": "ui/popup/popup.html"
},
"icons": {
"128": "icons/stream-bypass.png"
"48": "icons/logo_48.png",
"128": "icons/logo_128.png"
}
}

View File

@ -1,139 +0,0 @@
enum Reliability {
LOW = 1,
NORMAL,
HIGH
}
interface Match {
match(match: RegExpMatchArray): Promise<string>
}
class Evoload implements Match {
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch('https://evoload.io/SecurePlayer', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({code: code})
})
const json = await response.json()
return json['stream']['src']
}
}
class MCloud implements Match {
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch(`https://mcloud.to/info/${code}?skey=${match[0]}`, {
headers: {
'Content-Type': 'application/json'
},
referrer: `https://mcloud.to/embed/${code}`
})
const json = await response.json()
return json['media']['sources'][0]['file']
}
}
class Mixdrop implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
}
}
class Newgrounds implements Match {
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 {
async match(match: RegExpMatchArray): Promise<string> {
return `https://streamtape.com/get_video?${match[0]}`
}
}
class TheVideoMe implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://thevideome.com/${match[5]}.mp4`
}
}
class Upstream implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://${match[47]}.upstreamcdn.co/hls/${match.sort((a, b) => {return b.length - a.length})[0]}/master.m3u8`
}
}
class Vidstream implements Match {
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch(`https://vidstream.pro/info/${code}?skey=${match[0]}`, {
headers: {
'Content-Type': 'application/json'
},
referrer: `https://vidstream.pro/embed/${code}`
})
const json = await response.json()
return json['media']['sources'][0]['file']
}
}
class Vivo implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return this.rot47(decodeURIComponent(match[0]))
}
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
rot47(encoded: string): string {
const s = []
for(let i = 0; i < encoded.length; i++) {
const j = encoded.charCodeAt(i)
if((j >= 33) && (j <= 126)) {
s[i] = String.fromCharCode(33+((j+ 14)%94))
} else {
s[i] = String.fromCharCode(j)
}
}
return s.join('')
}
}
// all domains to match. the matches must be structured like this:
// [domain, regex match (can be null), class to call after match (can be null), reliability]
// => the domain which should be redirected
// => the regex gets called if the user visits a site with the given domain and matches the websites document body.
// if the regex is null, the complete document body gets handled as one big regex match
// => the class to call when the regex was parsed successfully. the class has to implement the `Match` interface.
// if the class is null, the user gets redirected to the first regex match element
// => the reliability shows how reliable a stream redirect is. for example, vivo.sx works nearly every time whereas
// upstream.to works only sometimes because of a security mechanism they're using (CORS) which currently can't be bypassed
//
// every match HAS to be on an separate line (for automatically manifest generation)
const matches = [
['evoload.io', null, new Evoload(), Reliability.NORMAL],
['mcloud.to', new RegExp(/(?<=')\w+(?=';)/gm), new MCloud(), Reliability.NORMAL],
['mixdrop.co', new RegExp(/(?<=\|)\w{2,}/gm), new Mixdrop(), Reliability.HIGH],
['newgrounds.com', null, new Newgrounds(), Reliability.HIGH],
['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape(), Reliability.NORMAL],
['streamzz.to', new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm), null, Reliability.NORMAL],
['thevideome.com', new RegExp(/(?<=\|)\w{2,}/gm), new TheVideoMe(), Reliability.NORMAL],
//['upstream.to', new RegExp(/(?<=\|)\w{2,}/gm), new Upstream(), Reliability.LOW],
['vidlox.me', new RegExp(/(?<=\[")\S+?(?=")/gm), null, Reliability.NORMAL],
['vidstream.pro', new RegExp(/(?<=')\w+(?=';)/gm), new Vidstream(), Reliability.LOW],
['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null, Reliability.NORMAL],
['vivo.st', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
['vivo.sx', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
['voe.sx', new RegExp(/https?:\/\/\S*m3u8(?=")/gm), null, Reliability.HIGH],
['vupload.com', new RegExp(/(?<=src:\s?").+?(?=")/gm), null, Reliability.NORMAL]
]

231
src/match/match.ts Normal file
View File

@ -0,0 +1,231 @@
export enum Reliability {
HIGH = 1,
NORMAL,
LOW,
}
export abstract class Match {
name: string
id: string
reliability: Reliability
domains: string[]
regex: RegExp
abstract match(match: RegExpMatchArray): Promise<string>
notice?: string
}
class Evoload implements Match {
name = 'Evoload'
id = 'evoload'
reliability = Reliability.NORMAL
domains = [
'evoload.io'
]
regex = new RegExp(/.*/gm)
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch('https://evoload.io/SecurePlayer', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({code: code})
})
const json = await response.json()
return json['stream']['src']
}
}
class Mixdrop implements Match {
name = 'Mixdrop'
id = 'mixdrop'
reliability = Reliability.HIGH
domains = [
'mixdrop.co'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
async match(match: RegExpMatchArray): Promise<string> {
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
}
}
/*class Mp4Upload implements Match {
name = 'Mp4Upload'
id = 'mp4upload'
reliability = Reliability.LOW
domains = [
'mp4upload.com'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
async match(match: RegExpMatchArray): Promise<string> {
return `https://${match[34]}.mp4upload.com:${match[89]}/d/${match[88]}/video.mp4`
}
}*/
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=\S*(?=')/gm)
async match(match: RegExpMatchArray): Promise<string> {
return `https://streamtape.com/get_video?${match[0]}`
}
}
class Streamzz implements Match {
name = 'Streamzz'
id = 'streamzz'
reliability = Reliability.NORMAL
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.NORMAL
domains = [
'upstream.to'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
async match(match: RegExpMatchArray): Promise<string> {
return `https://${match[49]}.upstreamcdn.co/hls/${match[148]}/master.m3u8`
}
}
class Vidlox implements Match {
name = 'Vidlox'
id = 'vidlox'
reliability = Reliability.LOW
domains = [
'vidlox.me'
]
regex = new RegExp(/(?<=\[")\S+?(?=")/gm)
async match(match: RegExpMatchArray): Promise<string> {
return match[0]
}
}
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 Vivo implements Match {
name = 'Vivo'
id = 'vivo'
reliability = Reliability.LOW
domains = [
'vivo.sx'
]
regex = new RegExp(/(?<=source:\s')(\S+)(?=')/gms)
async match(match: RegExpMatchArray): Promise<string> {
return this.rot47(decodeURIComponent(match[0]))
}
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
rot47(encoded: string): string {
const s = []
for(let i = 0; i < encoded.length; i++) {
const j = encoded.charCodeAt(i)
if((j >= 33) && (j <= 126)) {
s[i] = String.fromCharCode(33+((j+ 14)%94))
} else {
s[i] = String.fromCharCode(j)
}
}
return s.join('')
}
}
class Voe implements Match {
name = 'Voe'
id = 'voe'
reliability = Reliability.HIGH
domains = [
'voe.sx',
'voeunblk.com'
]
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]
}
}
export const matches = [
new Evoload(),
new Mixdrop(),
new Newgrounds(),
new Streamtape(),
new Streamzz(),
new Upstream(),
new Vidlox(),
new Vidoza(),
new Vivo(),
new Voe(),
new Vupload()
]

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="popup.css">
<script src="../ext/popper.min.js"></script>
<script src="../ext/tippy-bundle.umd.min.js"></script>
<script src="../match.js"></script>
</head>
<body>
<div id="container">
<div id="all">
<div class="buttons">
<a>On</a>
<a>Off</a>
</div>
</div>
<hr>
<table id="sub-container">
</table>
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues/new">Something does not work as expected</a>
</div>
<script src="./popup.js"></script>
</body>
</html>

View File

@ -1,163 +0,0 @@
function enableAll(enable: boolean) {
// @ts-ignore
chrome.storage.local.set({'all': enable})
// @ts-ignore
for (let button of document.getElementById('sub-container').getElementsByTagName('a')) {
enable ? button.classList.remove('disabled') : button.classList.add('disabled')
}
}
function enableOne(website: string, enable: boolean) {
// @ts-ignore
chrome.storage.local.get(['disabled'], function (result) {
let disabled: string[] = Object.keys(result).length === 0 ? [] : result['disabled']
if (enable && disabled.indexOf(website) !== -1) {
disabled.splice(disabled.indexOf(website), 1)
} else if (!enable && disabled.indexOf(website) === -1) {
disabled.push(website)
} else {
return
}
// @ts-ignore
chrome.storage.local.set({'disabled': disabled})
})
}
// @ts-ignore
chrome.storage.local.get(['all', 'disabled'], function (result) {
let allDisabled = result['all'] !== undefined && !result['all']
let disabled = new Map()
if (allDisabled) {
// @ts-ignore
for (let match of matches) {
disabled.set(match[0], false)
}
} else {
if (Object.keys(result).indexOf('disabled') !== -1) {
for (let disable of result['disabled']) {
disabled.set(disable, false)
}
}
}
let subContainer = document.getElementById('sub-container')
// @ts-ignore
for (let match of matches) {
let row = document.createElement('tr')
let name = document.createElement('td')
let nameValue = document.createElement('p')
nameValue.innerText = match[0]
switch (match[3]) {
case 1: // low
nameValue.classList.add('low-reliability')
// @ts-ignore
tippy(nameValue, {
content: 'Low reliability: Errors may occur often'
})
break
case 2: // normal
nameValue.classList.add('normal-reliability')
// @ts-ignore
tippy(nameValue, {
content: 'Normal reliability: Save to use but errors may occur'
})
break
case 3: //high
nameValue.classList.add('high-reliability')
// @ts-ignore
tippy(nameValue, {
content: 'High reliability: Errors are very unlikely to happen'
})
break
}
let buttons = document.createElement('td')
buttons.classList.add('buttons')
let on = document.createElement('a')
on.innerText = 'On'
// @ts-ignore
let onTippy = tippy(on, {
content: `Enable ${match[0]}`,
onMount: () => {
if (on.classList.contains('active') || off.classList.contains('disabled')) {
onTippy.hide()
}
}
})
let off = document.createElement('a')
off.innerText = 'Off'
// @ts-ignore
let offTippy = tippy(off, {
content: `Disable ${match[0]}`,
onMount: () => {
if (off.classList.contains('active') || off.classList.contains('disabled')) {
offTippy.hide()
}
}
})
disabled.has(match[0]) ? off.classList.add('active') : on.classList.add('active')
if (allDisabled) {
on.classList.add('disabled')
off.classList.add('disabled')
}
on.onclick = function () {
if (!on.classList.contains('disabled')) {
enableOne(match[0], true)
on.classList.add('active')
off.classList.remove('active')
}
}
off.onclick = function () {
if (!off.classList.contains('disabled')) {
enableOne(match[0], false)
on.classList.remove('active')
off.classList.add('active')
}
}
name.append(nameValue)
buttons.append(on, off)
row.append(name, buttons)
subContainer.append(row)
}
let allButtons = document.getElementById('all').getElementsByTagName('a')
let allOn = allButtons[0]
allButtons[0].onclick = function () {
if (!allButtons[0].classList.contains('disabled')) {
enableAll(true)
allButtons[0].classList.add('active')
allButtons[1].classList.remove('active')
}
}
// @ts-ignore
let allOnTippy = tippy(allOn, {
content: 'Enable all websites',
onMount: () => {
if (allButtons[0].classList.contains('active')) {
allOnTippy.hide()
}
}
})
allButtons[1].onclick = function () {
if (!allButtons[1].classList.contains('disabled')) {
enableAll(false)
allButtons[0].classList.remove('active')
allButtons[1].classList.add('active')
}
}
// @ts-ignore
let allOffTippy = tippy(allButtons[1], {
content: 'Disable all websites',
onMount: () => {
if (allButtons[1].classList.contains('active')) {
allOffTippy.hide()
}
}
})
allDisabled ? allButtons[1].classList.add('active') : allButtons[0].classList.add('active')
})

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>m3u8</title>
<link rel="stylesheet" href="/res/hls.css">
<script src="/ext/hls.light.min.js"></script>
</head>
<body>
<video id="video"></video>
<p id="message" hidden></p>
<script src="/res/hls.js"></script>
</body>
</html>

View File

@ -1,16 +0,0 @@
body
background-color: #131313
html, body, video
width: 100%
height: 100%
margin: 0
video
position: absolute
top: 0
left: 0
#message
color: white
text-align: center

View File

@ -1,62 +0,0 @@
function showMessage(message: string) {
let messageElement = document.getElementById('message') as HTMLParagraphElement
messageElement.innerHTML = message
messageElement.hidden = false
document.getElementById('video').hidden = true
}
function loadHls() {
let url = window.location.hash.substring(1)
let video = document.getElementById('video') as HTMLVideoElement;
video.controls = true
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url
// @ts-ignore
} else if (Hls.isSupported()) {
// @ts-ignore
let hls = new Hls()
hls.loadSource(url)
hls.attachMedia(video)
let searchParams = new URLSearchParams(window.location.search)
let rawReliability = parseInt(searchParams.get('reliability'))
let thirdPartyFallback = setTimeout(() => {
let message: string
switch (rawReliability) {
case 1: // low
message = `The reliability for this domain is low, errors like this are common.
Try to choose another streaming provider (if existent) or deactivate the addon for this domain (${searchParams.get('domain')}) and try again`
break
case 2: // normal
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
break
case 3: // high
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>.
When you're using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
so if this is the case just try to reload the page and see if you it's working then`
break
}
// shows a message if hls could not be loaded
showMessage(`Could not load hls video. ${message}`)
}, rawReliability * 3000)
document.title = searchParams.get('domain')
// @ts-ignore
hls.on(Hls.Events.MANIFEST_PARSED, () => {
clearTimeout(thirdPartyFallback)
document.getElementById('video').hidden = false
document.getElementById('message').hidden = true
})
} else {
// shows a message if hls is not supported
showMessage(`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>`)
}
}
loadHls()

64
src/store/store.ts Normal file
View File

@ -0,0 +1,64 @@
import {Match, matches} from "../match/match";
async function storageGet(key: string): Promise<any> {
return new Promise((resolve) => {
chrome.storage.local.get(key, (value) => {
resolve(value[key])
})
})
}
async function storageSet(key: string, value: any) {
const obj = {}
obj[key] = value
await chrome.storage.local.set(obj)
}
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 ? value as unknown as boolean : 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)
}
}

View File

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

15
src/ui/player/player.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HLS</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" hidden></p>
</div>
</body>
</html>

22
src/ui/player/player.sass Normal file
View File

@ -0,0 +1,22 @@
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
height: 100%
#message
color: white
text-align: center

79
src/ui/player/player.ts Normal file
View File

@ -0,0 +1,79 @@
import {Match, matches, Reliability} from "../../match/match";
// @ts-ignore
import Hls from "hls.js";
function show_message(message: string) {
document.getElementById('message').innerText = message
document.getElementById('message-container').hidden = false
document.getElementById('video').hidden = true
}
async function play_native(url: string, match: Match) {
const video = document.getElementById('video') as HTMLVideoElement
video.controls = true
video.src = url
}
async function play_hls(url: string, match: Match) {
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)
const loaded = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(false)
}, match.reliability * 3000)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
resolve(true)
})
})
if (!loaded) {
let message: string
switch (match.reliability) {
case Reliability.LOW:
message = `The reliability for this domain is low, errors like this are common.
Try to choose another streaming provider (if existent) or deactivate the addon for this provider (${match.name}) and try again`
break
case Reliability.NORMAL:
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
break
case Reliability.HIGH:
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>.
When you're using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
so if this is the case just try to reload the page and see if it's working then`
break
}
show_message(`Could not load HLS video. ${message}`)
}
} 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 = urlQuery.get('url')
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/new">here</a>`)
return
}
document.title = match.name
url.endsWith('.m3u8') ? await play_hls(url, match) : await play_native(url, match)
}
main()

23
src/ui/popup/popup.html Normal file
View File

@ -0,0 +1,23 @@
<!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>
</div>
<hr>
<table id="sub-container">
</table>
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues/new">Something does not work</a>
</div>
</body>
</html>

View File

@ -9,6 +9,7 @@ a, p
color: white
font-size: 16px
margin: 5px 0
cursor: default
a
border: 1px solid #281515

96
src/ui/popup/popup.ts Normal file
View File

@ -0,0 +1,96 @@
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
import {matches, Reliability} from "../../match/match";
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 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()

154
tasks/build.ts Normal file
View File

@ -0,0 +1,154 @@
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('node-sass')
const sassPluginNodeImport = require('node-sass-package-importer')
const typescript = require('typescript')
function getDomains() {
// because nodejs is nodejs, the simple commented out code below cannot be used.
// thus, the following bloated regexes must be used
/*const manifestMatches = []
for (const m of matches) {
for (const domain of m.domains) {
manifestMatches.push(`*://*.${domain}/*`)
}
}
manifest['content_scripts']['matches'] = manifestMatches*/
let domains = []
const matchesRegex = new RegExp(/export\s+const\s+matches\s+=\s+(?<matches>\[.*?])/gms)
const matchesClassesRegex = new RegExp(/(?<!\/\/\s*)new\s+(?<class>\w+)\(\)/gms)
const matchTs = fs.readFileSync('src/match/match.ts')
const jsMatches = matchesRegex.exec(matchTs).groups.matches
let m
while ((m = matchesClassesRegex.exec(jsMatches))) {
if (m.index === matchesClassesRegex.lastIndex) {
matchesClassesRegex.lastIndex++
}
if (m.groups.class !== undefined) {
const classDomainsRegex = new RegExp('class\\s+' + m.groups.class + '.*?domains\\s*=\\s*(?<domains>\\[.*?])', 'gms')
let mm
while ((mm = classDomainsRegex.exec(matchTs))) {
if (mm.index === classDomainsRegex.lastIndex) {
classDomainsRegex.lastIndex++
}
if (mm.groups.domains !== undefined) {
const matches = []
for (const domain of JSON.parse(mm.groups.domains.replace(/'/g, '"', -1))) {
matches.push(domain)
}
domains = domains.concat(matches)
}
}
}
}
return domains
}
async function buildManifest() {
const manifest = JSON.parse(fs.readFileSync('src/manifest.json'))
manifest['version'] = process.env.npm_package_version
manifest['content_scripts'][0]['matches'] = getDomains().map((domain) => {return `*://*.${domain}/*`})
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.sass': 'build/ui/popup/popup.css',
'src/ui/player/player.sass': 'build/ui/player/player.css'
}
for (const [src, dst] of Object.entries(files)) {
const compiled = sass.renderSync({
file: src,
importer: sassPluginNodeImport()
})
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',
}
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()