Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
e027c2e09e | |||
f9a0656d4d | |||
382d8b1268 | |||
5b8639ce6a | |||
841c824590 | |||
2055a3ea81 | |||
0262d1853c | |||
81da6600e6 | |||
817f5b82f9 | |||
9a17fb0d9b | |||
17f8aab216 | |||
44d4c9cbcf | |||
b34531b982 | |||
e699d3885c | |||
396038a803 | |||
bd64d4ed0b | |||
a207c336b0 | |||
2460657f2a | |||
698ed5ac3c | |||
dc42220f09 | |||
e146649bbf | |||
424e34190c | |||
c5f4f8b246 | |||
e2b8d884af | |||
b07d0b4be6 | |||
2d0441997c | |||
53e040db46 | |||
6d1ff3fbea | |||
4cf76eb62a | |||
672b920f31 | |||
1e166b5ecc | |||
213b996755 | |||
de0d8d5a41 | |||
1f34f74e11 | |||
60a2247b02 | |||
88814fcc83 | |||
f908e8249b | |||
8d57dfbc57 | |||
3aa84ba709 | |||
9ecfbd44e9 | |||
b1bea7687c | |||
c3148a76a3 | |||
210aa5c263 | |||
a55c455fcc | |||
923787bedb | |||
8990e25a72 | |||
9b4bcc6c64 | |||
2e8cce10b5 | |||
9d4b52c107 | |||
806320a0de | |||
774d3b52d7 | |||
31787bb3e3 | |||
e64feb5130 | |||
aa51b5d95f | |||
27c03c0b5b | |||
f48adc3db3 | |||
feaf843584 | |||
0b6021cf52 | |||
8b375cae2c | |||
c9acb929cc | |||
7a69ac83e0 | |||
a9a8609cb8 | |||
e0d4f8747e | |||
f8213772c8 | |||
bce27f2beb | |||
7bfae612f9 | |||
106f4ffda3 | |||
b83d287a42 | |||
a39418e506 | |||
0fc06a78a7 | |||
90bed8b774 | |||
ac31945372 | |||
1683bf2035 | |||
af54a28737 | |||
0f8d323c3c | |||
4c1bfbde62 | |||
9889cf79a2 | |||
230eb074e9 | |||
2a9c00c3a8 | |||
16cf6688ec | |||
4edc3d5f84 | |||
e5552910b2 | |||
2db8381df7 | |||
55eac3c010 | |||
d93a5ea784 | |||
e41bc2b8ef | |||
052c0d1e78 | |||
1e7b9b3d8d | |||
fbb9a1d67b | |||
126aeee1f7 | |||
0916f1c637 | |||
99d4577d1d | |||
9929c48761 | |||
2e13586ebb | |||
6583b0c15c | |||
038f708b36 | |||
f0f8bc9189 | |||
b45c1410f2 | |||
4907448aa6 | |||
4f08fee7b4 | |||
3624317116 |
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.idea/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
@ -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"]
|
|
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021 ByteDream
|
Copyright (c) 2022 ByteDream
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
127
README.md
@ -2,33 +2,56 @@
|
|||||||
|
|
||||||
A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.
|
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 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.
|
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.
|
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/)
|
|
||||||
|
|
||||||
<details id="example">
|
<details id="example">
|
||||||
<summary><b>How it's working</b></summary>
|
<summary><b>How it's working:</b></summary>
|
||||||
<img src="example.gif" alt="">
|
<img src="example.gif" alt="">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
The addon was tested on
|
## 📥 Installation
|
||||||
- Firefox (92.0)
|
|
||||||
- Chromium (92.0)
|
|
||||||
- Opera (78.0)
|
|
||||||
|
|
||||||
## Installing
|
|
||||||
|
|
||||||
### Firefox
|
### Firefox
|
||||||
|
|
||||||
Install the `.xpi` (firefox addon) file from the [latest release](https://github.com/ByteDream/vivosx-source-redirector/releases/latest).
|
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/).
|
||||||
|
|
||||||
### Chromium / Google Chrome
|
### Chromium / Google Chrome
|
||||||
|
|
||||||
1. Download the `stream-bypass-<version>.zip` file from the [latest release](https://github.com/ByteDream/vivosx-source-redirector/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.
|
2. Go into your browser and type `chrome://extensions` in the address bar.
|
||||||
3. Turn the developer mode in the top right corner on.
|
3. Turn the developer mode in the top right corner on.
|
||||||
4. Click Load unpacked.
|
4. Click Load unpacked.
|
||||||
@ -36,60 +59,66 @@ Install the `.xpi` (firefox addon) file from the [latest release](https://github
|
|||||||
|
|
||||||
### Opera
|
### Opera
|
||||||
|
|
||||||
1. Download the `stream-bypass-<version>.zip` file from the [latest release](https://github.com/ByteDream/vivosx-source-redirector/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.
|
2. Go into your browser and type `opera://extensions` in the address bar.
|
||||||
3. Turn the developer mode in the top right corner on.
|
3. Turn the developer mode in the top right corner on.
|
||||||
4. Click Load unpacked.
|
4. Click Load unpacked.
|
||||||
5. Choose the cloned / unzipped directory.
|
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.
|
| Site | Supported | Note |
|
||||||
- Compile it [manual](#manual).
|
|-----------------------------------------------------------------------|-----------|--------------------------------------------------|
|
||||||
- Compile it using [docker](#docker).
|
| [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.
|
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.
|
||||||
- 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.
|
|
||||||
|
|
||||||
If you want to keep it a little cleaner, you additionally need [python3](https://www.python.org).
|
## ⚙️ Building
|
||||||
- Compile everything with one line
|
|
||||||
```
|
|
||||||
$ python3 build.py -b -c
|
|
||||||
```
|
|
||||||
The compiled output will remain in a (new created if not existing) `build` directory.
|
|
||||||
|
|
||||||
### 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.
|
Requirements:
|
||||||
- Build the docker image
|
- `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
|
||||||
```
|
```
|
||||||
$ docker build -t stream-bypass .
|
|
||||||
```
|
|
||||||
- Compile
|
|
||||||
```
|
|
||||||
$ docker rum --rm -v build:/build stream-bypass
|
|
||||||
```
|
|
||||||
The compiled output will remain in a (new created if not existing) `build` directory.
|
|
||||||
|
|
||||||
##### Install
|
##### 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).
|
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
|
When using firefox, use the following:
|
||||||
1. Type `about:debugging` in the browser's address bar.
|
1. Type `about:debugging` in the browser's address bar.
|
||||||
2. Select 'This Firefox' tab (maybe named different, depending on your language).
|
2. Select 'This Firefox' tab (maybe named different, depending on your language).
|
||||||
3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
|
3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
|
||||||
4. Choose any file in the directory where the compiled sources are.
|
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.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.
|
||||||
|
105
build.py
@ -1,105 +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 build_path.is_dir():
|
|
||||||
for file in build_path.rglob('*'):
|
|
||||||
if file.is_dir():
|
|
||||||
shutil.rmtree(str(file))
|
|
||||||
else:
|
|
||||||
file.unlink()
|
|
||||||
else:
|
|
||||||
build_path.mkdir()
|
|
||||||
for file in Path('src').rglob('*'):
|
|
||||||
build_file = build_path.joinpath(str(file)[4:])
|
|
||||||
if file.is_dir():
|
|
||||||
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
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "stream-bypass",
|
||||||
|
"version": "2.1.4",
|
||||||
|
"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
@ -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
@ -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
After Width: | Height: | Size: 7.6 KiB |
BIN
src/icons/disabled_48.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
77
src/icons/logo.svg
Normal 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
After Width: | Height: | Size: 7.0 KiB |
BIN
src/icons/logo_48.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 16 KiB |
54
src/index.ts
@ -1,24 +1,38 @@
|
|||||||
// @ts-ignore
|
import {getMatch} from "./match/match";
|
||||||
chrome.storage.local.get(['all', 'disabled'], function (result) {
|
import {storageDelete, storageGet} from "./store/store";
|
||||||
let keys = Object.keys(result)
|
import {Match, matches} from "./match/matches";
|
||||||
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
|
async function main() {
|
||||||
if (regex === null) {
|
let match: Match;
|
||||||
location.assign(matchClass === null ? document.body.innerHTML : matchClass.match(new RegExp('').exec(document.body.innerHTML)))
|
let redirect = false;
|
||||||
} else if ((re = regex.exec(document.body.innerHTML)) !== null) {
|
if ((match = await getMatch(window.location.host)) === undefined) {
|
||||||
location.assign(matchClass === null ? re[0] : matchClass.match(re))
|
let id: string
|
||||||
}
|
if ((id = await storageGet('redirect')) !== undefined) {
|
||||||
}
|
redirect = true
|
||||||
|
match = matches.find(m => m.id === id)
|
||||||
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
const re = document.body.innerHTML.match(match.regex)
|
||||||
|
if (re === null) return
|
||||||
|
if (redirect) await storageDelete('redirect')
|
||||||
|
|
||||||
|
const url = await match.match(re)
|
||||||
|
|
||||||
|
if (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}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
@ -2,33 +2,49 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Stream Bypass",
|
"name": "Stream Bypass",
|
||||||
"author": "ByteDream",
|
"author": "ByteDream",
|
||||||
"description": "",
|
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
||||||
"version": "1.0.0",
|
"version": "2.1.3",
|
||||||
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
"matches": [
|
"matches": [
|
||||||
"*://streamtape.com/*",
|
"<all_urls>"
|
||||||
"*://vidoza.net/*",
|
|
||||||
"*://vivo.sx/*",
|
|
||||||
"*://vupload.com/*"
|
|
||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"match.js",
|
|
||||||
"index.js"
|
"index.js"
|
||||||
]
|
],
|
||||||
|
"run_at": "document_end"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage"
|
"storage",
|
||||||
|
"webRequest",
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"ui/player/*"
|
||||||
],
|
],
|
||||||
"browser_action": {
|
"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_title": "Stream Bypass",
|
||||||
"default_popup": "popup/popup.html"
|
"default_popup": "ui/popup/popup.html"
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"128": "icons/stream-bypass.png"
|
"48": "icons/logo_48.png",
|
||||||
|
"128": "icons/logo_128.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
43
src/match.ts
@ -1,43 +0,0 @@
|
|||||||
interface Match {
|
|
||||||
match(match: RegExpMatchArray): string
|
|
||||||
}
|
|
||||||
|
|
||||||
class Streamtape implements Match {
|
|
||||||
match(match: RegExpMatchArray): string {
|
|
||||||
return `https://streamtape.com/get_video?${match[0]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vivo implements Match {
|
|
||||||
match(match: RegExpMatchArray): 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 {
|
|
||||||
match(match: RegExpMatchArray): string {
|
|
||||||
return `https://www3.megaupload.to/${match[0]}/v.mp4`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// every match HAS to be on an separate line
|
|
||||||
const matches = [
|
|
||||||
['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape()],
|
|
||||||
['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null],
|
|
||||||
['vivo.sx', new RegExp(/source:\s*'(\S+)'/gm), new Vivo()],
|
|
||||||
['vupload.com', new RegExp(/(?<=class\|)\w*/gm), new Vupload()]
|
|
||||||
]
|
|
14
src/match/match.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
267
src/match/matches.ts
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
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',
|
||||||
|
'dood.wf'
|
||||||
|
]
|
||||||
|
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()
|
||||||
|
]
|
@ -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>
|
|
@ -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
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,18 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "esnext",
|
||||||
"target": "es2015",
|
"target": "es2019",
|
||||||
|
"removeComments": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5",
|
"es5",
|
||||||
"scripthost",
|
"scripthost",
|
||||||
"es2015.collection"
|
"es2015.collection",
|
||||||
]
|
"es2015.promise"
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"../node_modules"
|
||||||
],
|
]
|
||||||
}
|
}
|
17
src/ui/player/player.html
Normal 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
@ -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
@ -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
@ -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>
|
@ -2,13 +2,14 @@ body
|
|||||||
background-color: #2b2a33
|
background-color: #2b2a33
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
max-height: 500px
|
max-height: 500px
|
||||||
|
overflow-x: hidden
|
||||||
|
overflow-y: auto
|
||||||
|
|
||||||
a, p
|
a, p
|
||||||
color: white
|
color: white
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
margin: 5px 0
|
margin: 5px 0
|
||||||
|
cursor: default
|
||||||
|
|
||||||
a
|
a
|
||||||
border: 1px solid #281515
|
border: 1px solid #281515
|
||||||
@ -24,6 +25,13 @@ a
|
|||||||
background-color: grey
|
background-color: grey
|
||||||
cursor: not-allowed
|
cursor: not-allowed
|
||||||
|
|
||||||
|
&#error
|
||||||
|
border: none
|
||||||
|
display: block
|
||||||
|
font-weight: lighter
|
||||||
|
font-size: 0.8rem
|
||||||
|
text-align: center
|
||||||
|
padding: 10px 0 5px 0
|
||||||
|
|
||||||
hr
|
hr
|
||||||
margin: 3px 0
|
margin: 3px 0
|
||||||
@ -33,3 +41,16 @@ hr
|
|||||||
display: flex
|
display: flex
|
||||||
justify-content: center
|
justify-content: center
|
||||||
margin: 10px 0
|
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
@ -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
@ -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()
|