11 Commits

Author SHA1 Message Date
0916f1c637 2 new websites + indicator for redirect reliability 2021-10-23 17:02:09 +02:00
99d4577d1d Removed deprecated comment 2021-10-23 00:15:26 +02:00
9929c48761 Added native m3u8 / hls streaming 2021-10-23 00:13:53 +02:00
2e13586ebb Added mcloud.to 2021-10-22 23:40:21 +02:00
6583b0c15c Updated version 2021-09-13 17:14:30 +02:00
038f708b36 Fixed vivo.sx / vivo.st regex and unwanted return 2021-09-13 17:13:49 +02:00
f0f8bc9189 Added id 2021-09-13 01:57:41 +02:00
b45c1410f2 Added 7 new sites and general refactoring 2021-09-13 01:36:00 +02:00
4907448aa6 Add overflow 2021-09-13 01:14:00 +02:00
4f08fee7b4 Typo fix 2021-09-02 09:38:25 +02:00
3624317116 Update README.md 2021-09-01 22:18:20 +02:00
13 changed files with 331 additions and 34 deletions

View File

@ -24,11 +24,11 @@ The addon was tested on
### 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
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 `stream-bypass-<version>.zip` file from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that).
2. Go into your browser and type `chrome://extensions` in the address bar.
3. Turn the developer mode in the top right corner on.
4. Click Load unpacked.
@ -36,7 +36,7 @@ Install the `.xpi` (firefox addon) file from the [latest release](https://github
### 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 `stream-bypass-<version>.zip` file from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that).
2. Go into your browser and type `opera://extensions` in the address bar.
3. Turn the developer mode in the top right corner on.
4. Click Load unpacked.
@ -77,7 +77,7 @@ For this, you need [docker](https://www.docker.com/) to be installed.
```
- Compile
```
$ docker rum --rm -v build:/build stream-bypass
$ docker run --rm -v build:/build stream-bypass
```
The compiled output will remain in a (new created if not existing) `build` directory.

View File

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

18
build.py Executable file → Normal file
View File

@ -35,6 +35,13 @@ def write_manifest():
for content_script in manifest['content_scripts']:
content_script['matches'] = [f'*://{match}/*' for match in matches]
domains = []
for match in matches:
toplevel = match.split('.')[-1]
if toplevel not in domains:
domains.append(toplevel)
manifest['content_security_policy'] = f"script-src 'self' blob: https://cdn.jsdelivr.net https://unpkg.com {' '.join(f'*.{toplevel}' for toplevel in domains)}; object-src 'self'"
json.dump(manifest, open('src/manifest.json', 'w'), indent=2)
@ -55,18 +62,13 @@ def copy_built():
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:
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():
build_file.mkdir(parents=True)
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])

View File

@ -1,3 +1,7 @@
function hasSuffix(content: string, suffix: string) {
return content.indexOf(suffix, content.length - suffix.length) !== -1
}
// @ts-ignore
chrome.storage.local.get(['all', 'disabled'], function (result) {
let keys = Object.keys(result)
@ -6,16 +10,34 @@ chrome.storage.local.get(['all', 'disabled'], function (result) {
}
// @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 domain = match[0] as string
if (window.location.href.indexOf(domain) !== -1) {
if (keys.indexOf('disabled') === -1 || result['disabled'].indexOf(domain) === -1) {
let regex = match[1] as RegExp
let matchClass = match[2] as Match
let reliability = match[3] as Reliability
let re
if (regex === null) {
location.assign(matchClass === null ? document.body.innerHTML : matchClass.match(new RegExp('').exec(document.body.innerHTML)))
} else if ((re = regex.exec(document.body.innerHTML)) !== null) {
location.assign(matchClass === null ? re[0] : matchClass.match(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

View File

@ -3,26 +3,43 @@
"name": "Stream Bypass",
"author": "ByteDream",
"description": "",
"version": "1.0.0",
"version": "1.2.0",
"homepage_url": "https://github.com/ByteDream/stream-bypass",
"browser_specific_settings": {
"gecko": {
"id": "{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
}
},
"content_scripts": [
{
"all_frames": true,
"matches": [
"*://evoload.io/*",
"*://mcloud.to/*",
"*://mixdrop.co/*",
"*://streamtape.com/*",
"*://streamzz.to/*",
"*://thevideome.com/*",
"*://upstream.to/*",
"*://vidlox.me/*",
"*://vidstream.pro/*",
"*://vidoza.net/*",
"*://vivo.st/*",
"*://vivo.sx/*",
"*://voe.sx/*",
"*://vupload.com/*"
],
"js": [
"match.js",
"index.js"
]
],
"run_at": "document_end"
}
],
"permissions": [
"storage"
],
"content_security_policy": "script-src 'self' blob: https://cdn.jsdelivr.net https://unpkg.com *.io *.to *.co *.com *.me *.pro *.net *.st *.sx; object-src 'self'",
"browser_action": {
"default_icon": "icons/stream-bypass.png",
"default_title": "Stream Bypass",

View File

@ -1,16 +1,84 @@
enum Reliability {
LOW = 1,
NORMAL,
HIGH
}
interface Match {
match(match: RegExpMatchArray): string
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 Streamtape implements Match {
match(match: RegExpMatchArray): string {
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[48]}.upstreamcdn.co/hls/,${match.sort((a, b) => {return b.length - a.length})[0]},.urlset/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 {
match(match: RegExpMatchArray): string {
return this.rot47(decodeURIComponent(match[1]))
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)
@ -29,15 +97,35 @@ class Vivo implements Match {
}
class Vupload implements Match {
match(match: RegExpMatchArray): string {
async match(match: RegExpMatchArray): Promise<string> {
return `https://www3.megaupload.to/${match[0]}/v.mp4`
}
}
// every match HAS to be on an separate line
// 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 = [
['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()]
['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],
['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(/(?<=class\|)\w*/gm), new Vupload(), Reliability.NORMAL]
]

View File

@ -4,6 +4,8 @@
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="popup.css">
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
<script src="../match.js"></script>
</head>
<body>

View File

@ -2,14 +2,14 @@ body
background-color: #2b2a33
font-weight: bold
max-height: 500px
overflow-x: hidden
overflow-y: auto
a, p
color: white
font-size: 16px
margin: 5px 0
a
border: 1px solid #281515
cursor: pointer
@ -24,7 +24,6 @@ a
background-color: grey
cursor: not-allowed
hr
margin: 3px 0
@ -33,3 +32,16 @@ hr
display: flex
justify-content: center
margin: 10px 0
.low-reliability
text-decoration: underline
text-decoration-color: rgb(255, 0, 0)
.normal-reliability
text-decoration: underline
text-decoration-color: yellow
.high-reliability
text-decoration: underline
text-decoration-color: #00ff00

View File

@ -51,12 +51,53 @@ chrome.storage.local.get(['all', 'disabled'], function (result) {
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')
@ -85,6 +126,7 @@ chrome.storage.local.get(['all', 'disabled'], function (result) {
}
let allButtons = document.getElementById('all').getElementsByTagName('a')
let allOn = allButtons[0]
allButtons[0].onclick = function () {
if (!allButtons[0].classList.contains('disabled')) {
enableAll(true)
@ -92,6 +134,15 @@ chrome.storage.local.get(['all', 'disabled'], function (result) {
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)
@ -99,5 +150,14 @@ chrome.storage.local.get(['all', 'disabled'], function (result) {
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')
})

13
src/res/hls.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>m3u8</title>
<link rel="stylesheet" href="/res/hls.css">
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<video id="video"></video>
<p id="message" hidden></p>
<script src="/res/hls.js"></script>
</body>
</html>

9
src/res/hls.sass Normal file
View File

@ -0,0 +1,9 @@
html, body, video
width: 100%
height: 100%
margin: 0
video
position: absolute
top: 0
left: 0

60
src/res/hls.ts Normal file
View File

@ -0,0 +1,60 @@
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, so 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">here</a>.
When your 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)
// @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">here</a>`)
}
}
loadHls()

View File

@ -2,11 +2,13 @@
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"removeComments": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015.collection"
"es2015.collection",
"es2015.promise"
]
},
"exclude": [