mirror of
https://github.com/bytedream/stream-bypass.git
synced 2025-05-09 20:25:14 +02:00
Lots of updates :o
This commit is contained in:
parent
e0d4f8747e
commit
a9a8609cb8
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +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"]
|
54
README.md
54
README.md
@ -18,7 +18,7 @@ Additionally this enables you to download the video by right-clicking it and jus
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Supported streaming providers (for a complete list of all supported websites, see [here](SUPPORTED) or in [show all](#all-supported-websites) below):
|
||||
Supported streaming providers (for a complete list of all supported websites, click [show all](#all-supported-websites) down below):
|
||||
- [streamtape.com](https://streamtape.com)
|
||||
- [vivo.sx](https://vivo.sx)
|
||||
- [voe.sx](https://voe.sx)
|
||||
@ -40,7 +40,7 @@ Supported streaming providers (for a complete list of all supported websites, se
|
||||
<li><a href="https://vivo.sx">vivo.sx</a></li>
|
||||
<li><a href="https://voe.sx">voe.sx</a></li>
|
||||
<li><a href="https://vupload.com">vupload.com</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
---
|
||||
@ -50,12 +50,6 @@ Supported streaming providers (for a complete list of all supported websites, se
|
||||
<img src="example.gif" alt="">
|
||||
</details>
|
||||
|
||||
The addon was tested on
|
||||
- Firefox (96.0.3)
|
||||
- Ungoogled Chromium (97.0)
|
||||
- Vivaldi (5.0)
|
||||
- Opera (83.0)
|
||||
|
||||
## Installing
|
||||
|
||||
### Firefox
|
||||
@ -80,42 +74,18 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
|
||||
|
||||
## Compiling
|
||||
|
||||
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).
|
||||
Requirements:
|
||||
- `npm` installed.
|
||||
- A copy of this repository and a shell / console open in the copied directory.
|
||||
|
||||
### Manual
|
||||
If the requirements are satisfied, you can continue with the following commands:
|
||||
```shell
|
||||
# install all dependencies
|
||||
$ npm install
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### Docker
|
||||
|
||||
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.
|
||||
# build the extension source to a build/ directory
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
##### Install
|
||||
|
||||
|
14
SUPPORTED
14
SUPPORTED
@ -1,14 +0,0 @@
|
||||
evoload.io
|
||||
mcloud.to
|
||||
mixdrop.co
|
||||
newgrounds.com
|
||||
streamtape.com
|
||||
streamzz.to
|
||||
thevideome.com
|
||||
vidlox.me
|
||||
vidstream.pro
|
||||
vidoza.net
|
||||
vivo.st
|
||||
vivo.sx
|
||||
voe.sx
|
||||
vupload.com
|
158
build.py
158
build.py
@ -1,158 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
def load_matches():
|
||||
matched = []
|
||||
|
||||
indexed = False
|
||||
pattern = re.compile(r"(?<=\[')\S*(?=',)")
|
||||
for line in open('src/match.ts', 'r'):
|
||||
if not indexed:
|
||||
if 'constmatches=[' in line.replace(' ', ''):
|
||||
indexed = True
|
||||
else:
|
||||
match = pattern.findall(line)
|
||||
if match:
|
||||
if not line.strip().startswith('//'):
|
||||
matched.append(match[0])
|
||||
else:
|
||||
break
|
||||
|
||||
return matched
|
||||
|
||||
|
||||
def write_manifest():
|
||||
matches = load_matches()
|
||||
manifest = json.load(open('src/manifest.json', 'r'))
|
||||
|
||||
for content_script in manifest['content_scripts']:
|
||||
content_script['matches'] = [f'*://*.{match}/*' for match in matches]
|
||||
|
||||
json.dump(manifest, open('src/manifest.json', 'w'), indent=2)
|
||||
|
||||
|
||||
def write_supported():
|
||||
open('SUPPORTED', 'w').writelines([f'{match}\n' for match in load_matches()])
|
||||
|
||||
|
||||
def write_readme():
|
||||
firefox_pattern = re.compile(r'Mozilla Firefox (?P<version>.+)')
|
||||
chromium_pattern = re.compile(r'(?P<version>\d+\.\d+)')
|
||||
tested = {}
|
||||
|
||||
stdout, stderr = subprocess.Popen(['firefox', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
if stderr == b'':
|
||||
tested['Firefox'] = re.search(firefox_pattern, stdout.decode('utf-8').replace('\n', '')).group('version')
|
||||
|
||||
for command, name in {'chromium': 'Ungoogled Chromium', 'vivaldi-stable': 'Vivaldi', 'opera': 'Opera'}.items():
|
||||
stdout, stderr = subprocess.Popen([command, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
if stderr == b'':
|
||||
tested[name] = re.search(chromium_pattern, stdout.decode('utf-8').replace('\n', '')).group('version')
|
||||
|
||||
# it this the right syntax if i want to read and write to a file? * dreams in python3.10 *
|
||||
with open('README.md', 'r') as read_file:
|
||||
readme = read_file.read()
|
||||
|
||||
# adds all available websites
|
||||
all_providers_regex = r'(?<=<ul>\n)(.+?)(?=</ul>)'
|
||||
domains = filter(lambda domain: domain != '', open('SUPPORTED', 'r').read().split('\n'))
|
||||
all_providers = '\n'.join(f'\t\t<li><a href="https://{supported}">{supported}</a></li>' for supported in domains) + '\n'
|
||||
readme = re.sub(all_providers_regex, all_providers, readme, flags=re.DOTALL)
|
||||
|
||||
# adds all installed browsers to the tested browser section. i'm just to lazy to seek out all browser versions manually
|
||||
tested_browsers_regex = r'(?<=The addon was tested on\n)(.+?)(?=\n*## Installing)'
|
||||
tested_browsers = '\n'.join(f'- {name} ({version})' for name, version in tested.items())
|
||||
readme = re.sub(tested_browsers_regex, tested_browsers, readme, flags=re.DOTALL)
|
||||
|
||||
# rewrite the readme
|
||||
with open('README.md', 'w') as write_file:
|
||||
write_file.write(readme)
|
||||
write_file.close()
|
||||
read_file.close()
|
||||
|
||||
|
||||
def copy_built():
|
||||
if not shutil.which('tsc'):
|
||||
sys.stderr.write('The typescript compiler `tsc` could not be found')
|
||||
sys.exit(1)
|
||||
elif not shutil.which('sass'):
|
||||
sys.stderr.write('The sass compiler `sass` could not be found')
|
||||
sys.exit(1)
|
||||
|
||||
write_manifest()
|
||||
|
||||
subprocess.call(['tsc', '-p', 'src'])
|
||||
|
||||
build_path = Path('build')
|
||||
if not build_path.is_dir():
|
||||
build_path.mkdir()
|
||||
for file in Path('src').rglob('*'):
|
||||
build_file = build_path.joinpath(str(file)[4:])
|
||||
if file.is_dir():
|
||||
if not build_file.exists():
|
||||
build_file.mkdir(parents=True)
|
||||
elif file.suffix == '.sass':
|
||||
css_file = str(file)[:-4] + 'css'
|
||||
subprocess.call(['sass', '--no-source-map', file, css_file])
|
||||
shutil.copy(css_file, str(build_path.joinpath(css_file[4:])))
|
||||
elif file.name == 'tsconfig.json':
|
||||
continue
|
||||
elif file.suffix != '.ts':
|
||||
shutil.copy(str(file), str(build_file))
|
||||
|
||||
ext_path = Path('build', 'ext')
|
||||
if not ext_path.is_dir():
|
||||
ext_path.mkdir()
|
||||
|
||||
# download hls.js (version 1.1.1)
|
||||
with zipfile.ZipFile(io.BytesIO(urllib.request.urlopen('https://github.com/video-dev/hls.js/releases/download/v1.1.1/release.zip').read())) as z:
|
||||
open(ext_path.joinpath('hls.light.min.js'), 'wb').write(z.read('dist/hls.light.min.js'))
|
||||
z.close()
|
||||
|
||||
# download popperjs core (version 2.10.2)
|
||||
open(ext_path.joinpath('popper.min.js'), 'wb').write(urllib.request.urlopen('https://unpkg.com/@popperjs/core@2.10.2/dist/umd/popper.min.js').read())
|
||||
|
||||
# download tippy.js (version 6.3.7)
|
||||
open(ext_path.joinpath('tippy-bundle.umd.min.js'), 'wb').write(urllib.request.urlopen('https://unpkg.com/tippy.js@6.3.7/dist/tippy-bundle.umd.min.js').read())
|
||||
|
||||
|
||||
def clean_build():
|
||||
for file in Path('src').rglob('*'):
|
||||
if file.suffix in ['.js', '.css', '.map']:
|
||||
file.unlink()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-m', '--manifest', action='store_true', help='Builds the manifest.json file for addon information in ./src')
|
||||
parser.add_argument('-r', '--readme', action='store_true', help='Updates the README.md with the currently installed ')
|
||||
parser.add_argument('-s', '--supported', action='store_true', help='Builds the SUPPORTED file with all supported domains in the current directory')
|
||||
parser.add_argument('-b', '--build', action='store_true', help='Creates a ./build folder and builds all typescript / sass files')
|
||||
parser.add_argument('-c', '--clean', action='store_true', help='Cleans the ./src folder from .js, .css and .map files')
|
||||
|
||||
parsed = parser.parse_args()
|
||||
|
||||
if parsed.manifest:
|
||||
write_manifest()
|
||||
if parsed.readme:
|
||||
write_readme()
|
||||
if parsed.supported:
|
||||
write_supported()
|
||||
if parsed.build:
|
||||
copy_built()
|
||||
if parsed.clean:
|
||||
clean_build()
|
||||
|
||||
if not parsed.manifest and not parsed.supported and not parsed.build and not parsed.clean:
|
||||
print('\n'.join(load_matches()))
|
10823
package-lock.json
generated
Normal file
10823
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "stream-bypass",
|
||||
"version": "2.0.0",
|
||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "node tasks/build.ts",
|
||||
"bundle": "node tasks/build.ts && node tasks/bundle.ts"
|
||||
},
|
||||
"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",
|
||||
"typescript": "^4.7.3",
|
||||
"yazl": "^2.5.1"
|
||||
}
|
||||
}
|
59
src/index.ts
59
src/index.ts
@ -1,46 +1,25 @@
|
||||
function hasSuffix(content: string, suffix: string) {
|
||||
return content.indexOf(suffix, content.length - suffix.length) !== -1
|
||||
}
|
||||
import {matches} from "./match/match";
|
||||
import {getAllDisabled, getDisabled} from "./store/store";
|
||||
|
||||
// @ts-ignore
|
||||
chrome.storage.local.get(['all', 'disabled'], function (result) {
|
||||
let keys = Object.keys(result)
|
||||
if (keys.indexOf('all') !== -1 && !result['all']) {
|
||||
async function main() {
|
||||
if (await getAllDisabled()) {
|
||||
return
|
||||
}
|
||||
// @ts-ignore
|
||||
for (let match of matches) {
|
||||
let domain = match[0] as string
|
||||
if (window.location.href.indexOf(domain) !== -1) {
|
||||
if (keys.indexOf('disabled') === -1 || result['disabled'].indexOf(domain) === -1) {
|
||||
let regex = match[1] as RegExp
|
||||
let matchClass = match[2] as Match
|
||||
let reliability = match[3] as Reliability
|
||||
const disabled = await getDisabled()
|
||||
|
||||
let re
|
||||
if (regex !== null) {
|
||||
if ((re = document.body.innerHTML.match(regex)) === null) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
re = document.body.innerHTML.match(regex)
|
||||
}
|
||||
|
||||
if (matchClass === null) {
|
||||
if (regex === null) {
|
||||
location.assign(document.body.innerHTML)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
location.assign(hasSuffix(re[0], 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${re[0]}`) : re[0])
|
||||
}
|
||||
} else {
|
||||
matchClass.match(re).then(function (path) {
|
||||
// @ts-ignore
|
||||
location.assign(hasSuffix(path, 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${path}`) : path)
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
for (const match of matches) {
|
||||
if (disabled.some((v) => v == match) || !match.domains.some((v) => window.location.host.indexOf(v) !== -1)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const re = document.body.innerHTML.match(match.regex)
|
||||
if (re === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
const url = await match.match(re)
|
||||
location.assign(url.indexOf('.m3u8', url.length - 5) === -1 ? url : chrome.runtime.getURL(`ui/hls/hls.html?id=${match.id}&url=${encodeURIComponent(url)}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Stream Bypass",
|
||||
"author": "ByteDream",
|
||||
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
||||
"version": "1.6.0",
|
||||
"version": "2.0.0",
|
||||
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
@ -21,16 +21,17 @@
|
||||
"*://*.streamtape.com/*",
|
||||
"*://*.streamzz.to/*",
|
||||
"*://*.thevideome.com/*",
|
||||
"*://*.upstream.to/*",
|
||||
"*://*.vidlox.me/*",
|
||||
"*://*.vidstream.pro/*",
|
||||
"*://*.vidoza.net/*",
|
||||
"*://*.vivo.st/*",
|
||||
"*://*.vivo.sx/*",
|
||||
"*://*.voe.sx/*",
|
||||
"*://*.voeunblk.com/*",
|
||||
"*://*.vupload.com/*"
|
||||
],
|
||||
"js": [
|
||||
"match.js",
|
||||
"index.js"
|
||||
],
|
||||
"run_at": "document_end"
|
||||
@ -39,11 +40,10 @@
|
||||
"permissions": [
|
||||
"storage"
|
||||
],
|
||||
"content_security_policy": "script-src 'self' blob:; object-src 'self'",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/stream-bypass.png",
|
||||
"default_title": "Stream Bypass",
|
||||
"default_popup": "popup/popup.html"
|
||||
"default_popup": "ui/popup/popup.html"
|
||||
},
|
||||
"icons": {
|
||||
"128": "icons/stream-bypass.png"
|
||||
|
139
src/match.ts
139
src/match.ts
@ -1,139 +0,0 @@
|
||||
enum Reliability {
|
||||
LOW = 1,
|
||||
NORMAL,
|
||||
HIGH
|
||||
}
|
||||
|
||||
interface Match {
|
||||
match(match: RegExpMatchArray): Promise<string>
|
||||
}
|
||||
|
||||
class Evoload implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
||||
const response = await fetch('https://evoload.io/SecurePlayer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({code: code})
|
||||
})
|
||||
|
||||
const json = await response.json()
|
||||
return json['stream']['src']
|
||||
}
|
||||
}
|
||||
|
||||
class MCloud implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
||||
const response = await fetch(`https://mcloud.to/info/${code}?skey=${match[0]}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
referrer: `https://mcloud.to/embed/${code}`
|
||||
})
|
||||
const json = await response.json()
|
||||
return json['media']['sources'][0]['file']
|
||||
}
|
||||
}
|
||||
|
||||
class Mixdrop implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
|
||||
}
|
||||
}
|
||||
|
||||
class Newgrounds implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let id = window.location.pathname.split('/').slice(-1)[0]
|
||||
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
let json = await response.json()
|
||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
|
||||
}
|
||||
}
|
||||
|
||||
class Streamtape implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://streamtape.com/get_video?${match[0]}`
|
||||
}
|
||||
}
|
||||
|
||||
class TheVideoMe implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://thevideome.com/${match[5]}.mp4`
|
||||
}
|
||||
}
|
||||
|
||||
class Upstream implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://${match[47]}.upstreamcdn.co/hls/${match.sort((a, b) => {return b.length - a.length})[0]}/master.m3u8`
|
||||
}
|
||||
}
|
||||
|
||||
class Vidstream implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
||||
const response = await fetch(`https://vidstream.pro/info/${code}?skey=${match[0]}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
referrer: `https://vidstream.pro/embed/${code}`
|
||||
})
|
||||
const json = await response.json()
|
||||
return json['media']['sources'][0]['file']
|
||||
}
|
||||
}
|
||||
|
||||
class Vivo implements Match {
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return this.rot47(decodeURIComponent(match[0]))
|
||||
}
|
||||
|
||||
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
|
||||
rot47(encoded: string): string {
|
||||
const s = []
|
||||
for(let i = 0; i < encoded.length; i++) {
|
||||
const j = encoded.charCodeAt(i)
|
||||
if((j >= 33) && (j <= 126)) {
|
||||
s[i] = String.fromCharCode(33+((j+ 14)%94))
|
||||
} else {
|
||||
s[i] = String.fromCharCode(j)
|
||||
}
|
||||
}
|
||||
return s.join('')
|
||||
}
|
||||
}
|
||||
|
||||
// all domains to match. the matches must be structured like this:
|
||||
// [domain, regex match (can be null), class to call after match (can be null), reliability]
|
||||
// => the domain which should be redirected
|
||||
// => the regex gets called if the user visits a site with the given domain and matches the websites document body.
|
||||
// if the regex is null, the complete document body gets handled as one big regex match
|
||||
// => the class to call when the regex was parsed successfully. the class has to implement the `Match` interface.
|
||||
// if the class is null, the user gets redirected to the first regex match element
|
||||
// => the reliability shows how reliable a stream redirect is. for example, vivo.sx works nearly every time whereas
|
||||
// upstream.to works only sometimes because of a security mechanism they're using (CORS) which currently can't be bypassed
|
||||
//
|
||||
// every match HAS to be on an separate line (for automatically manifest generation)
|
||||
const matches = [
|
||||
['evoload.io', null, new Evoload(), Reliability.NORMAL],
|
||||
['mcloud.to', new RegExp(/(?<=')\w+(?=';)/gm), new MCloud(), Reliability.NORMAL],
|
||||
['mixdrop.co', new RegExp(/(?<=\|)\w{2,}/gm), new Mixdrop(), Reliability.HIGH],
|
||||
['newgrounds.com', null, new Newgrounds(), Reliability.HIGH],
|
||||
['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape(), Reliability.NORMAL],
|
||||
['streamzz.to', new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm), null, Reliability.NORMAL],
|
||||
['thevideome.com', new RegExp(/(?<=\|)\w{2,}/gm), new TheVideoMe(), Reliability.NORMAL],
|
||||
//['upstream.to', new RegExp(/(?<=\|)\w{2,}/gm), new Upstream(), Reliability.LOW],
|
||||
['vidlox.me', new RegExp(/(?<=\[")\S+?(?=")/gm), null, Reliability.NORMAL],
|
||||
['vidstream.pro', new RegExp(/(?<=')\w+(?=';)/gm), new Vidstream(), Reliability.LOW],
|
||||
['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null, Reliability.NORMAL],
|
||||
['vivo.st', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
|
||||
['vivo.sx', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
|
||||
['voe.sx', new RegExp(/https?:\/\/\S*m3u8(?=")/gm), null, Reliability.HIGH],
|
||||
['vupload.com', new RegExp(/(?<=src:\s?").+?(?=")/gm), null, Reliability.NORMAL]
|
||||
]
|
278
src/match/match.ts
Normal file
278
src/match/match.ts
Normal file
@ -0,0 +1,278 @@
|
||||
export enum Reliability {
|
||||
HIGH = 1,
|
||||
NORMAL,
|
||||
LOW,
|
||||
}
|
||||
|
||||
export abstract class Match {
|
||||
name: string
|
||||
id: string
|
||||
reliability: Reliability
|
||||
domains: string[]
|
||||
regex: RegExp
|
||||
abstract match(match: RegExpMatchArray): Promise<string>
|
||||
|
||||
locked?: boolean
|
||||
notice?: string
|
||||
}
|
||||
|
||||
class Evoload implements Match {
|
||||
name = 'Evoload'
|
||||
id = 'evoload'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'evoload.io'
|
||||
]
|
||||
regex = new RegExp(/.*/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
||||
const response = await fetch('https://evoload.io/SecurePlayer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({code: code})
|
||||
})
|
||||
|
||||
const json = await response.json()
|
||||
return json['stream']['src']
|
||||
}
|
||||
}
|
||||
|
||||
class MCloud implements Match {
|
||||
name = 'MCloud'
|
||||
id = 'mcloud'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'mcloud.to'
|
||||
]
|
||||
regex = new RegExp(/(?<=')\w+(?=';)/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
||||
const response = await fetch(`https://mcloud.to/info/${code}?skey=${match[0]}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
referrer: `https://mcloud.to/embed/${code}`
|
||||
})
|
||||
const json = await response.json()
|
||||
return json['media']['sources'][0]['file']
|
||||
}
|
||||
}
|
||||
|
||||
class Mixdrop implements Match {
|
||||
name = 'Mixdrop'
|
||||
id = 'mixdrop'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'mixdrop.co'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
|
||||
}
|
||||
}
|
||||
|
||||
class Newgrounds implements Match {
|
||||
name = 'Newgrounds'
|
||||
id = 'newgrounds'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'newgrounds.com'
|
||||
]
|
||||
regex = new RegExp(/.*/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let id = window.location.pathname.split('/').slice(-1)[0]
|
||||
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
let json = await response.json()
|
||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
|
||||
}
|
||||
}
|
||||
|
||||
class Streamtape implements Match {
|
||||
name = 'Streamtape'
|
||||
id = 'streamtape'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'streamtape.com'
|
||||
]
|
||||
regex = new RegExp(/id=\S*(?=')/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://streamtape.com/get_video?${match[0]}`
|
||||
}
|
||||
}
|
||||
|
||||
class Streamzz implements Match {
|
||||
name = 'Streamzz'
|
||||
id = 'streamzz'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'streamzz.to'
|
||||
]
|
||||
regex = new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class TheVideoMe implements Match {
|
||||
name = 'TheVideoMe'
|
||||
id = 'thevideome'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'thevideome.com'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://thevideome.com/${match[5]}.mp4`
|
||||
}
|
||||
}
|
||||
|
||||
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.NORMAL
|
||||
domains = [
|
||||
'vidlox.me'
|
||||
]
|
||||
regex = new RegExp(/(?<=\[")\S+?(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Vidstream implements Match {
|
||||
name = 'Vidstream'
|
||||
id = 'vidstream'
|
||||
reliability = Reliability.LOW
|
||||
domains = [
|
||||
'vidstream.pro'
|
||||
]
|
||||
regex = new RegExp(/(?<=')\w+(?=';)/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
||||
const response = await fetch(`https://vidstream.pro/info/${code}?skey=${match[0]}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
referrer: `https://vidstream.pro/embed/${code}`
|
||||
})
|
||||
const json = await response.json()
|
||||
return json['media']['sources'][0]['file']
|
||||
}
|
||||
}
|
||||
|
||||
class Vidoza implements Match {
|
||||
name = 'Vidoza'
|
||||
id = 'vidoza'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'vidoza.net'
|
||||
]
|
||||
regex = new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Vivo implements Match {
|
||||
name = 'Vivo'
|
||||
id = 'vivo'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'vivo.st',
|
||||
'vivo.sx'
|
||||
]
|
||||
regex = new RegExp(/(?<=source:\s')(\S+)(?=')/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return this.rot47(decodeURIComponent(match[0]))
|
||||
}
|
||||
|
||||
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
|
||||
rot47(encoded: string): string {
|
||||
const s = []
|
||||
for(let i = 0; i < encoded.length; i++) {
|
||||
const j = encoded.charCodeAt(i)
|
||||
if((j >= 33) && (j <= 126)) {
|
||||
s[i] = String.fromCharCode(33+((j+ 14)%94))
|
||||
} else {
|
||||
s[i] = String.fromCharCode(j)
|
||||
}
|
||||
}
|
||||
return s.join('')
|
||||
}
|
||||
}
|
||||
|
||||
class Voe implements Match {
|
||||
name = 'Voe'
|
||||
id = 'voe'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'voe.sx',
|
||||
'voeunblk.com'
|
||||
]
|
||||
regex = new RegExp(/https?:\/\/\S*m3u8(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Vupload implements Match {
|
||||
name = 'Vupload'
|
||||
id = 'vupload'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'vupload.com'
|
||||
]
|
||||
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
export const matches = [
|
||||
new Evoload(),
|
||||
new MCloud(),
|
||||
new Mixdrop(),
|
||||
new Newgrounds(),
|
||||
new Streamtape(),
|
||||
new Streamzz(),
|
||||
new TheVideoMe(),
|
||||
new Upstream(),
|
||||
new Vidlox(),
|
||||
new Vidoza(),
|
||||
new Vivo(),
|
||||
new Voe(),
|
||||
new Vupload()
|
||||
]
|
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
<script src="../ext/popper.min.js"></script>
|
||||
<script src="../ext/tippy-bundle.umd.min.js"></script>
|
||||
<script src="../match.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="all">
|
||||
<div class="buttons">
|
||||
<a>On</a>
|
||||
<a>Off</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<table id="sub-container">
|
||||
</table>
|
||||
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues/new">Something does not work as expected</a>
|
||||
</div>
|
||||
<script src="./popup.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,163 +0,0 @@
|
||||
function enableAll(enable: boolean) {
|
||||
// @ts-ignore
|
||||
chrome.storage.local.set({'all': enable})
|
||||
|
||||
// @ts-ignore
|
||||
for (let button of document.getElementById('sub-container').getElementsByTagName('a')) {
|
||||
enable ? button.classList.remove('disabled') : button.classList.add('disabled')
|
||||
}
|
||||
}
|
||||
|
||||
function enableOne(website: string, enable: boolean) {
|
||||
// @ts-ignore
|
||||
chrome.storage.local.get(['disabled'], function (result) {
|
||||
let disabled: string[] = Object.keys(result).length === 0 ? [] : result['disabled']
|
||||
if (enable && disabled.indexOf(website) !== -1) {
|
||||
disabled.splice(disabled.indexOf(website), 1)
|
||||
} else if (!enable && disabled.indexOf(website) === -1) {
|
||||
disabled.push(website)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
chrome.storage.local.set({'disabled': disabled})
|
||||
})
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
chrome.storage.local.get(['all', 'disabled'], function (result) {
|
||||
let allDisabled = result['all'] !== undefined && !result['all']
|
||||
let disabled = new Map()
|
||||
|
||||
if (allDisabled) {
|
||||
// @ts-ignore
|
||||
for (let match of matches) {
|
||||
disabled.set(match[0], false)
|
||||
}
|
||||
} else {
|
||||
if (Object.keys(result).indexOf('disabled') !== -1) {
|
||||
for (let disable of result['disabled']) {
|
||||
disabled.set(disable, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let subContainer = document.getElementById('sub-container')
|
||||
// @ts-ignore
|
||||
for (let match of matches) {
|
||||
let row = document.createElement('tr')
|
||||
|
||||
let name = document.createElement('td')
|
||||
let nameValue = document.createElement('p')
|
||||
nameValue.innerText = match[0]
|
||||
switch (match[3]) {
|
||||
case 1: // low
|
||||
nameValue.classList.add('low-reliability')
|
||||
// @ts-ignore
|
||||
tippy(nameValue, {
|
||||
content: 'Low reliability: Errors may occur often'
|
||||
})
|
||||
break
|
||||
case 2: // normal
|
||||
nameValue.classList.add('normal-reliability')
|
||||
// @ts-ignore
|
||||
tippy(nameValue, {
|
||||
content: 'Normal reliability: Save to use but errors may occur'
|
||||
})
|
||||
break
|
||||
case 3: //high
|
||||
nameValue.classList.add('high-reliability')
|
||||
// @ts-ignore
|
||||
tippy(nameValue, {
|
||||
content: 'High reliability: Errors are very unlikely to happen'
|
||||
})
|
||||
break
|
||||
}
|
||||
let buttons = document.createElement('td')
|
||||
buttons.classList.add('buttons')
|
||||
let on = document.createElement('a')
|
||||
on.innerText = 'On'
|
||||
// @ts-ignore
|
||||
let onTippy = tippy(on, {
|
||||
content: `Enable ${match[0]}`,
|
||||
onMount: () => {
|
||||
if (on.classList.contains('active') || off.classList.contains('disabled')) {
|
||||
onTippy.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
let off = document.createElement('a')
|
||||
off.innerText = 'Off'
|
||||
// @ts-ignore
|
||||
let offTippy = tippy(off, {
|
||||
content: `Disable ${match[0]}`,
|
||||
onMount: () => {
|
||||
if (off.classList.contains('active') || off.classList.contains('disabled')) {
|
||||
offTippy.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
disabled.has(match[0]) ? off.classList.add('active') : on.classList.add('active')
|
||||
if (allDisabled) {
|
||||
on.classList.add('disabled')
|
||||
off.classList.add('disabled')
|
||||
}
|
||||
|
||||
on.onclick = function () {
|
||||
if (!on.classList.contains('disabled')) {
|
||||
enableOne(match[0], true)
|
||||
on.classList.add('active')
|
||||
off.classList.remove('active')
|
||||
}
|
||||
}
|
||||
off.onclick = function () {
|
||||
if (!off.classList.contains('disabled')) {
|
||||
enableOne(match[0], false)
|
||||
on.classList.remove('active')
|
||||
off.classList.add('active')
|
||||
}
|
||||
}
|
||||
|
||||
name.append(nameValue)
|
||||
buttons.append(on, off)
|
||||
row.append(name, buttons)
|
||||
subContainer.append(row)
|
||||
}
|
||||
|
||||
let allButtons = document.getElementById('all').getElementsByTagName('a')
|
||||
let allOn = allButtons[0]
|
||||
allButtons[0].onclick = function () {
|
||||
if (!allButtons[0].classList.contains('disabled')) {
|
||||
enableAll(true)
|
||||
allButtons[0].classList.add('active')
|
||||
allButtons[1].classList.remove('active')
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
let allOnTippy = tippy(allOn, {
|
||||
content: 'Enable all websites',
|
||||
onMount: () => {
|
||||
if (allButtons[0].classList.contains('active')) {
|
||||
allOnTippy.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
allButtons[1].onclick = function () {
|
||||
if (!allButtons[1].classList.contains('disabled')) {
|
||||
enableAll(false)
|
||||
allButtons[0].classList.remove('active')
|
||||
allButtons[1].classList.add('active')
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
let allOffTippy = tippy(allButtons[1], {
|
||||
content: 'Disable all websites',
|
||||
onMount: () => {
|
||||
if (allButtons[1].classList.contains('active')) {
|
||||
allOffTippy.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
allDisabled ? allButtons[1].classList.add('active') : allButtons[0].classList.add('active')
|
||||
})
|
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>m3u8</title>
|
||||
<link rel="stylesheet" href="/res/hls.css">
|
||||
<script src="/ext/hls.light.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<p id="message" hidden></p>
|
||||
<script src="/res/hls.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,62 +0,0 @@
|
||||
function showMessage(message: string) {
|
||||
let messageElement = document.getElementById('message') as HTMLParagraphElement
|
||||
messageElement.innerHTML = message
|
||||
messageElement.hidden = false
|
||||
document.getElementById('video').hidden = true
|
||||
}
|
||||
|
||||
function loadHls() {
|
||||
let url = window.location.hash.substring(1)
|
||||
let video = document.getElementById('video') as HTMLVideoElement;
|
||||
|
||||
video.controls = true
|
||||
if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url
|
||||
// @ts-ignore
|
||||
} else if (Hls.isSupported()) {
|
||||
// @ts-ignore
|
||||
let hls = new Hls()
|
||||
hls.loadSource(url)
|
||||
hls.attachMedia(video)
|
||||
|
||||
let searchParams = new URLSearchParams(window.location.search)
|
||||
let rawReliability = parseInt(searchParams.get('reliability'))
|
||||
|
||||
let thirdPartyFallback = setTimeout(() => {
|
||||
let message: string
|
||||
|
||||
switch (rawReliability) {
|
||||
case 1: // low
|
||||
message = `The reliability for this domain is low, errors like this are common.
|
||||
Try to choose another streaming provider (if existent) or deactivate the addon for this domain (${searchParams.get('domain')}) and try again`
|
||||
break
|
||||
case 2: // normal
|
||||
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
|
||||
break
|
||||
case 3: // high
|
||||
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
|
||||
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>.
|
||||
When you're using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
|
||||
so if this is the case just try to reload the page and see if you it's working then`
|
||||
break
|
||||
}
|
||||
|
||||
// shows a message if hls could not be loaded
|
||||
showMessage(`Could not load hls video. ${message}`)
|
||||
}, rawReliability * 3000)
|
||||
|
||||
document.title = searchParams.get('domain')
|
||||
|
||||
// @ts-ignore
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
clearTimeout(thirdPartyFallback)
|
||||
document.getElementById('video').hidden = false
|
||||
document.getElementById('message').hidden = true
|
||||
})
|
||||
} else {
|
||||
// shows a message if hls is not supported
|
||||
showMessage(`Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>`)
|
||||
}
|
||||
}
|
||||
|
||||
loadHls()
|
55
src/store/store.ts
Normal file
55
src/store/store.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {Match, matches} from "../match/match";
|
||||
|
||||
async function storageGet(key: string): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.local.get(key, (value) => {
|
||||
resolve(value[key])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function storageSet(key: string, value: any) {
|
||||
const obj = {}
|
||||
obj[key] = value
|
||||
await chrome.storage.local.set(obj)
|
||||
}
|
||||
|
||||
export async function getDisabled(): Promise<Match[]> {
|
||||
const localMatches = []
|
||||
|
||||
for (const disabled of (await storageGet('disabled') as string[]) || []) {
|
||||
let m: Match
|
||||
if ((m = matches.find((v) => v.id === disabled)) !== undefined) {
|
||||
localMatches.push(m)
|
||||
}
|
||||
}
|
||||
|
||||
return localMatches
|
||||
}
|
||||
|
||||
export async function getAllDisabled(): Promise<boolean> {
|
||||
const value = await storageGet('all')
|
||||
return value !== undefined ? value as unknown as boolean : false
|
||||
}
|
||||
|
||||
export async function enableAll() {
|
||||
await storageSet('all', false)
|
||||
}
|
||||
|
||||
export async function disableAll() {
|
||||
await storageSet('all', true)
|
||||
}
|
||||
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"module": "esnext",
|
||||
"target": "es2019",
|
||||
"removeComments": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
@ -12,6 +12,6 @@
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
}
|
||||
"../node_modules"
|
||||
]
|
||||
}
|
||||
|
13
src/ui/hls/hls.html
Normal file
13
src/ui/hls/hls.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>HLS</title>
|
||||
<link rel="stylesheet" href="hls.css">
|
||||
<script src="hls.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<p id="message" hidden></p>
|
||||
</body>
|
||||
</html>
|
70
src/ui/hls/hls.ts
Normal file
70
src/ui/hls/hls.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {matches, Reliability} from "../../match/match";
|
||||
// @ts-ignore
|
||||
import Hls from "hls.js";
|
||||
|
||||
function show_message(message: string) {
|
||||
const messageElement = document.getElementById('message') as HTMLParagraphElement
|
||||
messageElement.innerText = message
|
||||
messageElement.hidden = false
|
||||
document.getElementById('video').hidden = true
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const urlQuery = new URLSearchParams(window.location.search)
|
||||
const id = urlQuery.get('id')
|
||||
const url = urlQuery.get('url')
|
||||
|
||||
const match = matches.find((m) => m.id === id)
|
||||
if (match === undefined) {
|
||||
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>`)
|
||||
return
|
||||
}
|
||||
document.title = match.name
|
||||
|
||||
const video = document.getElementById('video') as HTMLVideoElement
|
||||
video.controls = true
|
||||
|
||||
if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url
|
||||
} else if (Hls.isSupported()) {
|
||||
const hls = new Hls({
|
||||
enableWorker: false
|
||||
})
|
||||
hls.loadSource(url)
|
||||
hls.attachMedia(video)
|
||||
|
||||
const loaded = await new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(false)
|
||||
}, match.reliability * 3000)
|
||||
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
|
||||
if (!loaded) {
|
||||
let message: string
|
||||
switch (match.reliability) {
|
||||
case Reliability.LOW:
|
||||
message = `The reliability for this domain is low, errors like this are common.
|
||||
Try to choose another streaming provider (if existent) or deactivate the addon for this provider (${match.name}) and try again`
|
||||
break
|
||||
case Reliability.NORMAL:
|
||||
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
|
||||
break
|
||||
case Reliability.HIGH:
|
||||
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
|
||||
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>.
|
||||
When you're using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
|
||||
so if this is the case just try to reload the page and see if it's working then`
|
||||
break
|
||||
}
|
||||
show_message(`Could not load HLS video. ${message}`)
|
||||
}
|
||||
} else {
|
||||
show_message('Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>')
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
23
src/ui/popup/popup.html
Normal file
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>
|
96
src/ui/popup/popup.ts
Normal file
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/match";
|
||||
|
||||
async function main() {
|
||||
const disabled = await getDisabled()
|
||||
|
||||
const subContainer = document.getElementById('sub-container')
|
||||
for (const m of matches) {
|
||||
const row = document.createElement('tr')
|
||||
|
||||
const name = document.createElement('td')
|
||||
const nameValue = document.createElement('p')
|
||||
nameValue.innerText = m.name
|
||||
switch (m.reliability) {
|
||||
case Reliability.LOW:
|
||||
nameValue.classList.add('low-reliability')
|
||||
break
|
||||
case Reliability.NORMAL:
|
||||
nameValue.classList.add('normal-reliability')
|
||||
break
|
||||
case Reliability.HIGH:
|
||||
nameValue.classList.add('high-reliability')
|
||||
break
|
||||
}
|
||||
|
||||
const buttons = document.createElement('td')
|
||||
buttons.classList.add('buttons')
|
||||
const on = document.createElement('a')
|
||||
on.innerText = 'On'
|
||||
const off = document.createElement('a')
|
||||
off.innerText = 'Off'
|
||||
disabled.find((v) => v.id === m.id) === undefined ? on.classList.add('active') : off.classList.add('active')
|
||||
|
||||
on.onclick = async function () {
|
||||
if (!on.classList.contains('disabled')) {
|
||||
await enable(m)
|
||||
on.classList.add('active')
|
||||
off.classList.remove('active')
|
||||
}
|
||||
}
|
||||
off.onclick = async function () {
|
||||
if (!off.classList.contains('disabled')) {
|
||||
await disable(m)
|
||||
on.classList.remove('active')
|
||||
off.classList.add('active')
|
||||
}
|
||||
}
|
||||
|
||||
name.append(nameValue)
|
||||
buttons.append(on, off)
|
||||
row.append(name, buttons)
|
||||
subContainer.append(row)
|
||||
}
|
||||
|
||||
const allOnButton = document.getElementById('all').getElementsByTagName('a')[0]
|
||||
const allOffButton = document.getElementById('all').getElementsByTagName('a')[1]
|
||||
|
||||
if (await getAllDisabled()) {
|
||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
||||
for (let i = 0; i < allBtns.length; i++) {
|
||||
allBtns[i].classList.add('disabled')
|
||||
}
|
||||
allOffButton.classList.add('active')
|
||||
} else {
|
||||
allOnButton.classList.add('active')
|
||||
}
|
||||
|
||||
allOnButton.onclick = async () => {
|
||||
if (!allOnButton.classList.contains('active')) {
|
||||
allOnButton.classList.add('active')
|
||||
allOffButton.classList.remove('active')
|
||||
|
||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
||||
for (let i = 0; i < allBtns.length; i++) {
|
||||
allBtns[i].classList.remove('disabled')
|
||||
}
|
||||
|
||||
await enableAll()
|
||||
}
|
||||
}
|
||||
allOffButton.onclick = async () => {
|
||||
if (!allOffButton.classList.contains('active')) {
|
||||
allOffButton.classList.add('active')
|
||||
allOnButton.classList.remove('active')
|
||||
|
||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
||||
for (let i = 0; i < allBtns.length; i++) {
|
||||
allBtns[i].classList.add('disabled')
|
||||
}
|
||||
|
||||
await disableAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
110
tasks/build.ts
Normal file
110
tasks/build.ts
Normal file
@ -0,0 +1,110 @@
|
||||
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')
|
||||
|
||||
// because of javascript magic, require and import cannot be used at the same time
|
||||
/*async function build_manifest() {
|
||||
const manifest = JSON.parse(await fs.readFileSync('src/manifest.json'))
|
||||
|
||||
const manifestMatches = []
|
||||
|
||||
for (const m of matches) {
|
||||
for (const domain of m.domains) {
|
||||
manifestMatches.push(`*://*.${domain}/*`)
|
||||
}
|
||||
}
|
||||
|
||||
manifest['content_scripts']['matches'] = manifestMatches
|
||||
|
||||
await fs.writeFileSync('src/manifest.json', JSON.stringify(manifest))
|
||||
}*/
|
||||
|
||||
async function build_misc() {
|
||||
const files = {
|
||||
'src/manifest.json': 'build/manifest.json',
|
||||
'src/icons/stream-bypass.png': 'build/icons/stream-bypass.png'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
||||
fs.copyFileSync(src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
async function build_html() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.html': 'build/ui/popup/popup.html',
|
||||
'src/ui/hls/hls.html': 'build/ui/hls/hls.html'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
||||
fs.copyFileSync(src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
async function build_css() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.sass': 'build/ui/popup/popup.css',
|
||||
'src/ui/hls/hls.sass': 'build/ui/hls/hls.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 build_js() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.ts': 'build/ui/popup/popup.js',
|
||||
'src/ui/hls/hls.ts': 'build/ui/hls/hls.js',
|
||||
|
||||
'src/index.ts': 'build/index.js'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
const bundle = await rollup.rollup({
|
||||
input: src,
|
||||
plugins: [
|
||||
rollupPluginNodeResolve({
|
||||
browser: true
|
||||
}),
|
||||
rollupPluginReplace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
}),
|
||||
rollupPluginTypescript({
|
||||
typescript,
|
||||
tsconfig: 'src/tsconfig.json'
|
||||
}),
|
||||
rollupPluginCommonJs({
|
||||
extensions: ['.js', '.ts']
|
||||
})
|
||||
]
|
||||
})
|
||||
await bundle.write({
|
||||
file: dst,
|
||||
strict: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function build() {
|
||||
await build_misc()
|
||||
await build_html()
|
||||
await build_css()
|
||||
await build_js()
|
||||
}
|
||||
|
||||
build()
|
27
tasks/bundle.ts
Normal file
27
tasks/bundle.ts
Normal file
@ -0,0 +1,27 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const yazl = require('yazl')
|
||||
|
||||
function walkDirectory(dir, callback) {
|
||||
for (const file of fs.readdirSync(dir)) {
|
||||
const filePath = path.join(dir, file)
|
||||
fs.statSync(filePath).isDirectory() ? walkDirectory(filePath, callback) : callback(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
async function bundle_zip() {
|
||||
const zipfile = new yazl.ZipFile()
|
||||
walkDirectory('build', (path) => {
|
||||
zipfile.addFile(path, path.substring(6))
|
||||
})
|
||||
zipfile.end()
|
||||
|
||||
fs.mkdirSync('dist', {recursive: true})
|
||||
zipfile.outputStream.pipe(fs.createWriteStream(`dist/stream_bypass-v${process.env.npm_package_version}.zip`))
|
||||
}
|
||||
|
||||
async function bundle() {
|
||||
await bundle_zip()
|
||||
}
|
||||
|
||||
bundle()
|
Loading…
x
Reference in New Issue
Block a user