93 Commits

Author SHA1 Message Date
5b8639ce6a web development :))))))))))))))))))))))))))) 2022-07-23 17:40:16 +02:00
841c824590 Fix native player error message 2022-07-23 17:28:47 +02:00
2055a3ea81 Update version to 2.1.2 2022-07-23 17:19:31 +02:00
0262d1853c Change error message layout and link color when video cannot be loaded 2022-07-22 13:55:28 +02:00
81da6600e6 Fix error message if video cannot be loaded 2022-07-22 13:55:03 +02:00
817f5b82f9 Remove unused import 2022-07-22 01:49:54 +02:00
9a17fb0d9b Fix domain sometimes not shown in window title when using native player 2022-07-22 01:41:50 +02:00
17f8aab216 Fix mixdrop not working 2022-07-22 01:34:34 +02:00
44d4c9cbcf Update supported websites 2022-07-20 11:56:08 +02:00
b34531b982 Bump version to 2.1.1 2022-07-20 11:55:57 +02:00
e699d3885c Change mp4upload reliability 2022-07-19 11:53:14 +02:00
396038a803 Fix replace video player height 2022-07-19 11:28:11 +02:00
bd64d4ed0b Add doodstream domain 2022-07-19 11:27:03 +02:00
a207c336b0 Add mp4upload support 2022-07-19 11:09:09 +02:00
2460657f2a Remove doodstream comment 2022-07-19 11:06:56 +02:00
698ed5ac3c Add support for doodstream 2022-07-19 11:03:16 +02:00
dc42220f09 Fix streamtape 2022-07-19 10:07:21 +02:00
e146649bbf Use window.location instead of location 2022-07-16 02:34:22 +02:00
424e34190c Rework title of built-in player 2022-07-16 02:32:23 +02:00
c5f4f8b246 Version to 2.1.0 2022-07-13 18:39:26 +02:00
e2b8d884af Fix streamtape regex 2022-07-13 18:21:03 +02:00
b07d0b4be6 Update supported sites table 2022-07-13 18:04:55 +02:00
2d0441997c Add doodstream stubs 2022-07-13 18:04:44 +02:00
53e040db46 Fix addon cannot be disabled via ui 2022-07-13 01:00:33 +02:00
6d1ff3fbea Fix player not working in non firefox browsers 2022-07-13 00:46:40 +02:00
4cf76eb62a Add functions to bypass websites that are redirected to (looking at you voe) 2022-07-13 00:45:52 +02:00
672b920f31 Rename match.ts to matches.ts 2022-07-12 22:51:37 +02:00
1e166b5ecc Add new voe domain (un-block-voe.net) 2022-07-04 12:27:14 +02:00
213b996755 Add new voe domain (voeun-block.net) 2022-06-26 14:08:56 +02:00
de0d8d5a41 Fix typo 2022-06-20 16:22:34 +02:00
1f34f74e11 Fix typo 2022-06-20 10:49:34 +02:00
60a2247b02 Merge pull request #2 from ByteDream/v2
Update to v2
2022-06-20 10:45:23 +02:00
88814fcc83 Fck I've deleted the wrong file 2022-06-20 10:39:55 +02:00
f908e8249b Remove background script from build file 2022-06-18 02:37:05 +02:00
8d57dfbc57 Update copyright year 2022-06-18 02:33:04 +02:00
3aa84ba709 Change Firefox Addon ... names to Firefox Store ... 2022-06-18 02:31:12 +02:00
9ecfbd44e9 Remove background script OwO forgot that 2022-06-18 02:22:38 +02:00
b1bea7687c Change reliability for vidlox 2022-06-18 02:21:34 +02:00
c3148a76a3 Fix typo 2022-06-18 01:50:48 +02:00
210aa5c263 Update supported websites 2022-06-18 01:40:39 +02:00
a55c455fcc Refactor, complete past changes & change manifest back to version 2 2022-06-18 01:29:47 +02:00
923787bedb Update icons 2022-06-18 01:29:19 +02:00
8990e25a72 Redirect to custom video player on every url 2022-06-18 01:13:54 +02:00
9b4bcc6c64 Update to manifest version 3 2022-06-17 00:23:29 +02:00
2e8cce10b5 Add streamz.ws 2022-06-16 23:43:48 +02:00
9d4b52c107 Add vidoza 2022-06-16 23:42:43 +02:00
806320a0de Fix streamzz and remove vidoza 2022-06-16 22:28:28 +02:00
774d3b52d7 Update readme 2022-06-16 19:47:11 +02:00
31787bb3e3 Use web-ext instead of custom bundle & add code check 2022-06-16 19:44:24 +02:00
e64feb5130 Remove readme build 2022-06-16 19:23:30 +02:00
aa51b5d95f Remove old and not working websites 2022-06-16 19:23:13 +02:00
27c03c0b5b Center hls error message 2022-06-16 19:17:44 +02:00
f48adc3db3 Exclude comment matches when matching domain matches 2022-06-16 16:09:35 +02:00
feaf843584 Fix package 2022-06-15 10:53:06 +02:00
0b6021cf52 FIx popup 2022-06-15 10:52:50 +02:00
8b375cae2c Fix build script 2022-06-15 10:52:20 +02:00
c9acb929cc Add readme build 2022-06-15 09:13:45 +02:00
7a69ac83e0 I hate nodejs. 2022-06-14 23:39:26 +02:00
a9a8609cb8 Lots of updates :o 2022-06-14 20:50:51 +02:00
e0d4f8747e Update .gitignore 2022-06-12 17:36:11 +02:00
f8213772c8 Fixed error report message 2022-03-04 20:59:34 +01:00
bce27f2beb Updated firefox download notice :)))) 2022-02-04 16:10:06 +01:00
7bfae612f9 Updated version 2022-02-04 15:46:44 +01:00
106f4ffda3 Re-added content_security_policy to fix hls playing 2022-02-04 15:40:16 +01:00
b83d287a42 Added newgrounds.com 2022-02-04 15:40:00 +01:00
a39418e506 Added prefix wildcards to url matching 2022-02-04 15:39:22 +01:00
0fc06a78a7 Updated version 2022-02-02 17:42:34 +01:00
90bed8b774 Removed content_security_policy 2022-02-02 09:50:07 +01:00
ac31945372 Removed upstream.com 2022-02-01 22:07:51 +01:00
1683bf2035 Fixed readme build 2022-02-01 22:06:44 +01:00
af54a28737 Hls error style changes & typo fixes 2022-02-01 21:57:12 +01:00
0f8d323c3c Added comment passing on extracting matching sites 2022-02-01 21:55:55 +01:00
4c1bfbde62 Updated vupload.com regex (mp4 => hls) 2022-02-01 21:00:20 +01:00
9889cf79a2 Added badges 2022-02-01 20:49:13 +01:00
230eb074e9 Added gitignore 2022-02-01 20:36:31 +01:00
2a9c00c3a8 Typo fix 2022-01-10 16:10:48 +01:00
16cf6688ec Updated direct download links using smartrelease :3 2022-01-10 15:16:49 +01:00
4edc3d5f84 Update v1.4.2 2022-01-06 17:32:44 +01:00
e5552910b2 Hls player works now; fixed vupload.com regex 2022-01-06 17:32:30 +01:00
2db8381df7 Added link to issues 2022-01-06 17:30:47 +01:00
55eac3c010 Firefox addon store link reactivation 2021-11-23 12:30:24 +01:00
d93a5ea784 Static 3rd party library version 2021-11-18 20:34:22 +01:00
e41bc2b8ef Update README.md 2021-11-18 17:18:21 +01:00
052c0d1e78 firefox addon fix 2021-11-06 13:40:06 +01:00
1e7b9b3d8d Fixed empty supported website 2021-10-23 23:07:27 +02:00
fbb9a1d67b Added website support completion for README.md 2021-10-23 23:03:30 +02:00
126aeee1f7 Added automatically browser recognizing for README.md manipulation 2021-10-23 22:20:56 +02:00
0916f1c637 2 new websites + indicator for redirect reliability 2021-10-23 17:02:09 +02:00
99d4577d1d Removed deprecated comment 2021-10-23 00:15:26 +02:00
9929c48761 Added native m3u8 / hls streaming 2021-10-23 00:13:53 +02:00
2e13586ebb Added mcloud.to 2021-10-22 23:40:21 +02:00
6583b0c15c Updated version 2021-09-13 17:14:30 +02:00
038f708b36 Fixed vivo.sx / vivo.st regex and unwanted return 2021-09-13 17:13:49 +02:00
31 changed files with 1081 additions and 442 deletions

4
.gitignore vendored Normal file
View File

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

127
README.md
View File

@ -2,25 +2,48 @@
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">
<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">
</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">
</a>
</p>
<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-building">Building ⚙️</a>
<a href="#-license">License ⚖</a>
</p>
## 📝 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.
Supported streaming providers (for a complete list of all supported websites, see [here](SUPPORTED)):
- [streamtape.com](https://streamtape.com/)
- [vidoza.net](https://vidoza.net/)
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 (92.0)
- Chromium (92.0)
- Opera (78.0)
## Installing
## 📥 Installation
### Firefox
@ -28,7 +51,7 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
### Chromium / Google Chrome
1. Download the `stream-bypass-<version>.zip` file 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).
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.
@ -36,60 +59,66 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
### Opera
1. Download the `stream-bypass-<version>.zip` file 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).
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.
## 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) | ✔️ | |
| [evoload.io](https://evoload.io) | ✔️ | |
| [mixdrop.co](https://mixdrop.co) | ✔ | |
| [mp4upload.com](https://mp4upload.com) | ✔ | |
| [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.
## ⚙️ Building
### Docker
If you want to build 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,11 +0,0 @@
evoload.io
mixdrop.co
streamtape.com
streamzz.to
thevideome.com
vidlox.me
vidoza.net
vivo.st
vivo.sx
voe.sx
vupload.com

100
build.py
View File

@ -1,100 +0,0 @@
#!/usr/bin/python3
import argparse
import json
import sys
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:
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 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))
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('-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.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.1.3",
"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"
}
}

16
src/background.ts Normal file
View File

@ -0,0 +1,16 @@
import {getMatch} from "./match/match";
import {storageGet, storageSet} from "./store/store";
import {Match} from "./match/matches";
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)
}
}
}, {
urls: ['<all_urls>'],
types: ['main_frame', 'sub_frame']
})

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,44 +1,38 @@
function hasSuffix(content: string, suffix: string) {
return content.indexOf(suffix, content.length - suffix.length) !== -1
import {getMatch} from "./match/match";
import {storageDelete, storageGet} from "./store/store";
import {Match, matches} from "./match/matches";
async function main() {
let match: Match;
if ((match = await getMatch(window.location.host)) === undefined) {
let id: string
if ((id = await storageGet('redirect')) !== undefined) {
match = matches.find(m => m.id === id)
await storageDelete('redirect')
} else {
return
}
}
const re = document.body.innerHTML.match(match.regex)
if (re === null) {
return
}
const url = await match.match(re)
if (match.replace && !url.endsWith('.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}`))
}
}
// @ts-ignore
chrome.storage.local.get(['all', 'disabled'], function (result) {
let keys = Object.keys(result)
if (keys.indexOf('all') !== -1 && !result['all']) {
return
}
// @ts-ignore
for (let match of matches) {
if (window.location.href.indexOf(match[0]) !== -1) {
if (keys.indexOf('disabled') === -1 || result['disabled'].indexOf(match[0]) === -1) {
let regex = match[1] as RegExp
let matchClass = match[2] as Match
let re
if (regex !== null) {
if ((re = document.body.innerHTML.match(regex)) === null) {
continue
}
} else {
re = document.body.innerHTML.match(regex)
}
return
if (matchClass === null) {
if (regex === null) {
location.assign(document.body.innerHTML)
} else {
location.assign(hasSuffix(re[0], 'm3u8') ? `https://bharadwajpro.github.io/m3u8-player/player/#${re[0]}`: re[0])
}
} else {
matchClass.match(re).then(function (path) {
location.assign(hasSuffix(path, 'm3u8') ? `https://bharadwajpro.github.io/m3u8-player/player/#${path}`: path)
})
}
}
return
}
}
})
main()

View File

@ -2,8 +2,8 @@
"manifest_version": 2,
"name": "Stream Bypass",
"author": "ByteDream",
"description": "",
"version": "1.1.3",
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
"version": "2.1.3",
"homepage_url": "https://github.com/ByteDream/stream-bypass",
"browser_specific_settings": {
"gecko": {
@ -14,33 +14,37 @@
{
"all_frames": true,
"matches": [
"*://evoload.io/*",
"*://mixdrop.co/*",
"*://streamtape.com/*",
"*://streamzz.to/*",
"*://thevideome.com/*",
"*://vidlox.me/*",
"*://vidoza.net/*",
"*://vivo.st/*",
"*://vivo.sx/*",
"*://voe.sx/*",
"*://vupload.com/*"
"<all_urls>"
],
"js": [
"match.js",
"index.js"
]
],
"run_at": "document_end"
}
],
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"storage"
"storage",
"webRequest",
"<all_urls>"
],
"web_accessible_resources": [
"ui/player/*"
],
"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,78 +0,0 @@
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 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 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 Vivo implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return this.rot47(decodeURIComponent(match[1]))
}
// 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 Vupload implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://www3.megaupload.to/${match[0]}/v.mp4`
}
}
// every match HAS to be on an separate line
const matches = [
['evoload.io', null, new Evoload()],
['mixdrop.co', new RegExp(/(?<=\|)\w{2,}/gm), new Mixdrop()],
['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape()],
['streamzz.to', new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm), null],
['thevideome.com', new RegExp(/(?<=\|)\w{2,}/gm), new TheVideoMe()],
['vidlox.me', new RegExp(/(?<=\[")\S+?(?=")/gm), null],
['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null],
['vivo.st', new RegExp(/source:\s*'(\S+)'/gm), new Vivo()],
['vivo.sx', new RegExp(/source:\s*'(\S+)'/gm), new Vivo()],
['voe.sx', new RegExp(/https?:\/\/\S*m3u8(?=")/gm), null],
['vupload.com', new RegExp(/(?<=class\|)\w*/gm), new Vupload()]
]

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

@ -0,0 +1,14 @@
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
}
}
}

266
src/match/matches.ts Normal file
View File

@ -0,0 +1,266 @@
export enum Reliability {
HIGH = 1,
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.HIGH
domains = [
'doodstream.com',
'dood.pm',
'dood.ws'
]
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 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.NORMAL
domains = [
'mixdrop.co'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
async match(match: RegExpMatchArray): Promise<string> {
const prefix = /(?<=\/\/)[a|s](?=-)/.exec(document.body.innerHTML)[0]
const subdomain = match[1].length < match[2].length ? match[1] : match[2]
const domain = match.slice().sort((a, b) => b.length - a.length).find(m => /^[a-z]+$/.test(m))
const id = match[1].length > match[2].length ? match[1] : match[2]
const tld = match.find(m => ['net', 'io', 'to', 'sx', 'com'].indexOf(m) !== -1)
const s = match.slice().sort((a, b) => b.length - a.length).slice(1)[0]
const e_t = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
return `https://${prefix}-${subdomain}.${domain}.${tld}/v/${id}.mp4?s=${s}&e=${e_t}&_t=${e_t}`
}
}
class Mp4Upload implements Match {
name = 'Mp4Upload'
id = 'mp4upload'
reliability = Reliability.NORMAL
domains = [
'mp4upload.com'
]
replace = true
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=.*(?=')/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.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'
]
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 Doodstream(),
new Evoload(),
new Mixdrop(),
new Mp4Upload(),
new Newgrounds(),
new Streamtape(),
new Streamzz(),
new Upstream(),
new Vidlox(),
new Vidoza(),
new Vivo(),
new Voe(),
new Vupload()
]

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="popup.css">
<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>
</div>
<script src="./popup.js"></script>
</body>
</html>

View File

@ -1,103 +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]
let buttons = document.createElement('td')
buttons.classList.add('buttons')
let on = document.createElement('a')
on.innerText = 'On'
let off = document.createElement('a')
off.innerText = 'Off'
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')
allButtons[0].onclick = function () {
if (!allButtons[0].classList.contains('disabled')) {
enableAll(true)
allButtons[0].classList.add('active')
allButtons[1].classList.remove('active')
}
}
allButtons[1].onclick = function () {
if (!allButtons[1].classList.contains('disabled')) {
enableAll(false)
allButtons[0].classList.remove('active')
allButtons[1].classList.add('active')
}
}
allDisabled ? allButtons[1].classList.add('active') : allButtons[0].classList.add('active')
})

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

@ -0,0 +1,68 @@
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)
}
}

View File

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

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

@ -0,0 +1,17 @@
<!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/new">here</a></p>
</div>
</body>
</html>

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

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

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

@ -0,0 +1,80 @@
import {Match, matches, Reliability} 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, 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.
When you're using Tor, 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 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 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/new">here</a>`)
return
}
document.title = `Stream Bypass (${domain})`
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

@ -5,12 +5,11 @@ body
overflow-x: hidden
overflow-y: auto
a, p
color: white
font-size: 16px
margin: 5px 0
cursor: default
a
border: 1px solid #281515
@ -26,6 +25,13 @@ a
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
@ -35,3 +41,16 @@ hr
display: flex
justify-content: center
margin: 10px 0
.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

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/matches";
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()

107
tasks/build.ts Normal file
View File

@ -0,0 +1,107 @@
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')
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.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',
'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()