From 0916f1c6371b50143566d01da2d321dc56f545f0 Mon Sep 17 00:00:00 2001 From: bytedream Date: Sat, 23 Oct 2021 17:02:09 +0200 Subject: [PATCH] 2 new websites + indicator for redirect reliability --- SUPPORTED | 2 ++ build.py | 2 +- src/index.ts | 10 ++++--- src/manifest.json | 9 ++++--- src/match.ts | 64 +++++++++++++++++++++++++++++++++++--------- src/popup/popup.html | 2 ++ src/popup/popup.sass | 16 ++++++++--- src/popup/popup.ts | 60 +++++++++++++++++++++++++++++++++++++++++ src/res/hls.html | 10 +++---- src/res/hls.sass | 7 +++-- src/res/hls.ts | 42 ++++++++++++++++++++++++++++- src/tsconfig.json | 4 ++- 12 files changed, 195 insertions(+), 33 deletions(-) diff --git a/SUPPORTED b/SUPPORTED index 9462a14..f7db220 100644 --- a/SUPPORTED +++ b/SUPPORTED @@ -4,7 +4,9 @@ mixdrop.co streamtape.com streamzz.to thevideome.com +upstream.to vidlox.me +vidstream.pro vidoza.net vivo.st vivo.sx diff --git a/build.py b/build.py index 7543562..ff986b3 100644 --- a/build.py +++ b/build.py @@ -40,7 +40,7 @@ def write_manifest(): 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 {' '.join(f'*.{toplevel}' for toplevel in domains)}; object-src 'self'" + 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) diff --git a/src/index.ts b/src/index.ts index 2e1f8d2..c244c16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,10 +10,12 @@ 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) { @@ -29,12 +31,12 @@ chrome.storage.local.get(['all', 'disabled'], function (result) { location.assign(document.body.innerHTML) } else { // @ts-ignore - location.assign(hasSuffix(re[0], 'm3u8') ? chrome.runtime.getURL(`res/hls.html#${re[0]}`) : re[0]) + 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#${path}`) : path) + location.assign(hasSuffix(path, 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${path}`) : path) }) } } diff --git a/src/manifest.json b/src/manifest.json index fa133c4..b3bb93b 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -3,7 +3,7 @@ "name": "Stream Bypass", "author": "ByteDream", "description": "", - "version": "1.1.4", + "version": "1.2.0", "homepage_url": "https://github.com/ByteDream/stream-bypass", "browser_specific_settings": { "gecko": { @@ -20,7 +20,9 @@ "*://streamtape.com/*", "*://streamzz.to/*", "*://thevideome.com/*", + "*://upstream.to/*", "*://vidlox.me/*", + "*://vidstream.pro/*", "*://vidoza.net/*", "*://vivo.st/*", "*://vivo.sx/*", @@ -30,13 +32,14 @@ "js": [ "match.js", "index.js" - ] + ], + "run_at": "document_end" } ], "permissions": [ "storage" ], - "content_security_policy": "script-src 'self' blob: https://cdn.jsdelivr.net *.io *.to *.co *.com *.me *.net *.st *.sx; object-src 'self'", + "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", diff --git a/src/match.ts b/src/match.ts index 35c5bd8..89e3bdb 100644 --- a/src/match.ts +++ b/src/match.ts @@ -1,3 +1,9 @@ +enum Reliability { + LOW = 1, + NORMAL, + HIGH +} + interface Match { match(match: RegExpMatchArray): Promise } @@ -50,6 +56,26 @@ class TheVideoMe implements Match { } } +class Upstream implements Match { + async match(match: RegExpMatchArray): Promise { + 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 { + 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 { return this.rot47(decodeURIComponent(match[0])) @@ -76,18 +102,30 @@ class Vupload implements Match { } } -// 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 = [ - ['evoload.io', null, new Evoload()], - ['mcloud.to', new RegExp(/(?<=')\w+(?=';)/gm), new MCloud()], - ['mixdrop.co', new RegExp(/(?<=\|)\w{2,}/gm), new Mixdrop()], - ['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape()], - ['streamzz.to', new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm), null], - ['thevideome.com', new RegExp(/(?<=\|)\w{2,}/gm), new TheVideoMe()], - ['vidlox.me', new RegExp(/(?<=\[")\S+?(?=")/gm), null], - ['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null], - ['vivo.st', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo()], - ['vivo.sx', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo()], - ['voe.sx', new RegExp(/https?:\/\/\S*m3u8(?=")/gm), null], - ['vupload.com', new RegExp(/(?<=class\|)\w*/gm), new Vupload()] + ['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] ] diff --git a/src/popup/popup.html b/src/popup/popup.html index 097c75b..db13593 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -4,6 +4,8 @@ Title + + diff --git a/src/popup/popup.sass b/src/popup/popup.sass index 309da76..e1adfc7 100644 --- a/src/popup/popup.sass +++ b/src/popup/popup.sass @@ -5,13 +5,11 @@ body overflow-x: hidden overflow-y: auto - a, p color: white font-size: 16px margin: 5px 0 - a border: 1px solid #281515 cursor: pointer @@ -26,7 +24,6 @@ a background-color: grey cursor: not-allowed - hr margin: 3px 0 @@ -35,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 diff --git a/src/popup/popup.ts b/src/popup/popup.ts index e23cc6e..e9dc161 100644 --- a/src/popup/popup.ts +++ b/src/popup/popup.ts @@ -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') }) diff --git a/src/res/hls.html b/src/res/hls.html index 1af5518..1739f81 100644 --- a/src/res/hls.html +++ b/src/res/hls.html @@ -1,13 +1,13 @@ - - m3u8 + + - - - + + + \ No newline at end of file diff --git a/src/res/hls.sass b/src/res/hls.sass index b7c47ea..c12671a 100644 --- a/src/res/hls.sass +++ b/src/res/hls.sass @@ -1,6 +1,9 @@ html, body, video - height: 100% width: 100% + height: 100% + margin: 0 video - margin: auto \ No newline at end of file + position: absolute + top: 0 + left: 0 \ No newline at end of file diff --git a/src/res/hls.ts b/src/res/hls.ts index 7e60350..05d5cd8 100644 --- a/src/res/hls.ts +++ b/src/res/hls.ts @@ -1,3 +1,10 @@ +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; @@ -11,9 +18,42 @@ function loadHls() { 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 here. + When your 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 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 - document.getElementById('not-supported').hidden = false + showMessage(`Failed to play m3u8 video (hls is not supported). Try again or create a new issue here`) } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 292fd97..17aecd3 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -2,11 +2,13 @@ "compilerOptions": { "module": "commonjs", "target": "es2015", + "removeComments": true, "lib": [ "dom", "es5", "scripthost", - "es2015.collection" + "es2015.collection", + "es2015.promise" ] }, "exclude": [