mirror of
https://github.com/bytedream/stream-bypass.git
synced 2025-06-27 18:40:31 +02:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
48dc192964 | |||
7c45cacd36 | |||
00514e4e81 | |||
4c1d0d0b63 | |||
a920832945 | |||
7d8d8b6614 | |||
b4052834f2 | |||
9900c33243 | |||
98709bc934 | |||
f4cbdd3258 | |||
fa41a8de8e | |||
ce8bc855b9 | |||
03202b2a12 | |||
9f0e1b59ce | |||
d928d25e09 | |||
266771aa13 | |||
d56672d90f | |||
a9ea5fe4b2 | |||
bb3f5384d6 | |||
6989587161 | |||
dd9bf71a5c | |||
6da0050df4 | |||
1a7c22ec0e | |||
175862b098 | |||
fd5a532d0f | |||
8c43eedb23 | |||
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 |
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 ByteDream
|
Copyright (c) 2022-NOW 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
|
||||||
|
83
README.md
83
README.md
@ -27,7 +27,9 @@ A multi-browser addon / extension for multiple streaming providers which redirec
|
|||||||
•
|
•
|
||||||
<a href="#-supported-sites">Supported Sites 📜</a>
|
<a href="#-supported-sites">Supported Sites 📜</a>
|
||||||
•
|
•
|
||||||
<a href="#%EF%B8%8F-compiling">Compiling ⚙️</a>
|
<a href="#%EF%B8%8F-building">Building 🛠️</a>
|
||||||
|
•
|
||||||
|
<a href="#%EF%B8%8F-settings">Settings ⚙️</a>
|
||||||
•
|
•
|
||||||
<a href="#-license">License ⚖</a>
|
<a href="#-license">License ⚖</a>
|
||||||
</p>
|
</p>
|
||||||
@ -67,22 +69,26 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
|
|||||||
|
|
||||||
## 📜 Supported sites
|
## 📜 Supported sites
|
||||||
|
|
||||||
| Site | Supported | Note |
|
| Site | Supported | Note |
|
||||||
|-----------------------------------------------------------------------|-----------|--------------------------------------------------|
|
|-----------------------------------------------------------------------|-----------|--------------------------------------------------------------------------------------------------------------|
|
||||||
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ❌ | Reverse engineering the site costs too much time |
|
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | |
|
||||||
| [evoload.io](https://evoload.io) | ✔️ | |
|
| [filemoon.sx](https://filemoon.sx) | ✔ | |
|
||||||
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | |
|
| [mcloud.to](https://mcloud.to/) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) |
|
||||||
| [mp4upload.com](https://mp4upload.com) | ❌ | URL can be extracted but not played |
|
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | |
|
||||||
| [newgrounds.com](https://newgrounds.com) | ✔ | |
|
| [mp4upload.com](https://mp4upload.com) | ✔ | |
|
||||||
| [streamtape.com](https://streamtape.com) | ✔ | |
|
| [newgrounds.com](https://newgrounds.com) | ✔ | |
|
||||||
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
|
| [streamtape.com](https://streamtape.com) | ✔ | |
|
||||||
| [upstream.to](https://upstream.to) | ✔ | |
|
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
|
||||||
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
|
| [upstream.to](https://upstream.to) | ✔ | |
|
||||||
| [vidlox.me](https://vidlox.me) | ⚠ | Website down / Timeout |
|
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
|
||||||
| [vidoza.net](https://vidoza.net) | ✔ | |
|
| [vidoza.net](https://vidoza.net) | ✔ | |
|
||||||
| [vivo.sx](https://vivo.sx) | ⚠️ | Website down / Timeout |
|
| [vidstream.pro](https://vidstream.pro) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) |
|
||||||
| [voe.sx](https://voe.sx) / [voeunblk.com](https://voeunblk.com) | ✔ | |
|
| [voe.sx](https://voe.sx) | ✔ | |
|
||||||
| [vupload.com](https://vupload.com) | ✔ | |
|
| [vupload.com](https://vupload.com) | ✔ | |
|
||||||
|
| [kwik.cx](https://kwik.cx) | ✔ | |
|
||||||
|
| [dropload.io](https://dropload.io) | ✔ | |
|
||||||
|
| [supervideo.tv](https://supervideo.tv) | ✔ | |
|
||||||
|
| [goodstream.uno](https://goodstream.uno) | ✔ | |
|
||||||
|
|
||||||
- ✔️: Everything ok.
|
- ✔️: 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.
|
- ⚠: Included in the addon but will probably not work. See `Note` in this case, an explanation why will stand there in the most cases.
|
||||||
@ -90,9 +96,18 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## ⚙️ Compiling
|
<details>
|
||||||
|
<summary>Hall of dead sites</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://evoload.io">evoload.io</a> - Down</li>
|
||||||
|
<li><a href="https://vidlox.me">vidlox.me</a> - Reachable but empty</li>
|
||||||
|
<li><a href="https://vivo.sx">vivo.sx</a> - Down</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
|
||||||
If you want to compile the addon from source and not using the [installation](#installation) way, follow the instructions.
|
## 🛠️ Building
|
||||||
|
|
||||||
|
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- `npm` installed.
|
- `npm` installed.
|
||||||
@ -119,6 +134,36 @@ When using firefox, use the following:
|
|||||||
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.
|
||||||
|
|
||||||
|
|
||||||
|
## ⚙️ Settings
|
||||||
|
|
||||||
|
### <ins>ff2mpv: use mpv to directly play streams</ins>
|
||||||
|
ff2mpv is located at this repository: https://github.com/woodruffw/ff2mpv
|
||||||
|
|
||||||
|
Steps to get it set up:
|
||||||
|
- In the [Usage](https://github.com/woodruffw/ff2mpv#usage) section of the ff2mpv repository pick the installation instruction for your operating system (Linux/Windows/macOS; you do not need the browser addon).
|
||||||
|
- Scroll down to `Install manually`
|
||||||
|
- Follow instructions for Firefox/Chrome
|
||||||
|
- Edit the `ff2mpv.json` you created:
|
||||||
|
- Firefox: Add `{55dd42e8-3dd9-455a-b4fe-86664881b10c}` to `allowed_extensions` ->
|
||||||
|
```
|
||||||
|
"allowed_extensions": [
|
||||||
|
"ff2mpv@yossarian.net",
|
||||||
|
"{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- Chrome/Chromium:
|
||||||
|
- Go To: Settings -> Extensions
|
||||||
|
- Click on `Details` of the Stream Bypass extension and copy the ID
|
||||||
|
- Add `chrome-extension://your-id-here/` to `allowed_origins` ->
|
||||||
|
```
|
||||||
|
"allowed_origins": [
|
||||||
|
"chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/",
|
||||||
|
"chrome-extension://your-id-her/"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## ⚖ 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.
|
||||||
|
23
package.json
23
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stream-bypass",
|
"name": "stream-bypass",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -20,22 +20,21 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
|
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^22.0.0",
|
"@rollup/plugin-commonjs": "^24.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
"@rollup/plugin-typescript": "^8.3.3",
|
"@rollup/plugin-typescript": "^11.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
"@types/chrome": "^0.0.234",
|
||||||
"@typescript-eslint/parser": "^5.27.1",
|
|
||||||
"@types/chrome": "^0.0.190",
|
|
||||||
"@types/node-sass": "^4.11.2",
|
"@types/node-sass": "^4.11.2",
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||||
|
"@typescript-eslint/parser": "^5.27.1",
|
||||||
"eslint": "^8.17.0",
|
"eslint": "^8.17.0",
|
||||||
"hls.js": "^1.1.5",
|
"hls.js": "^1.1.5",
|
||||||
"node-sass": "^7.0.1",
|
"rollup": "^3.21.0",
|
||||||
"node-sass-package-importer": "^5.3.2",
|
"sass": "^1.62.1",
|
||||||
"rollup": "^2.75.6",
|
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"typescript": "^4.7.3",
|
"typescript": "^5.0.4",
|
||||||
"web-ext": "^7.0.0"
|
"web-ext": "^7.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
src/background.ts
Normal file
25
src/background.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {getMatch} from "./match/match";
|
||||||
|
import {storageDelete, storageGet, storageSet} from "./store/store";
|
||||||
|
import {Match} from "./match/matches";
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender) => {
|
||||||
|
if (message.action == "ff2mpv") {
|
||||||
|
chrome.runtime.sendNativeMessage("ff2mpv", {url: message.url})
|
||||||
|
.catch((error) => {console.error(error)})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await storageDelete('redirect')
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
urls: ['<all_urls>'],
|
||||||
|
types: ['main_frame', 'sub_frame']
|
||||||
|
})
|
46
src/index.ts
46
src/index.ts
@ -1,23 +1,41 @@
|
|||||||
import {matches} from "./match/match";
|
import {getMatch} from "./match/match";
|
||||||
import {getAllDisabled, getDisabled} from "./store/store";
|
import {storageDelete, storageGet, getSetting} from "./store/store";
|
||||||
|
import {Match, matches} from "./match/matches";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (await getAllDisabled()) {
|
let match: Match;
|
||||||
return
|
let redirect = false;
|
||||||
|
if ((match = await getMatch(window.location.host)) === undefined) {
|
||||||
|
let id: string
|
||||||
|
if ((id = await storageGet('redirect')) !== undefined) {
|
||||||
|
redirect = true
|
||||||
|
match = matches.find(m => m.id === id)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const match of matches) {
|
const re = document.body.innerHTML.match(match.regex)
|
||||||
if (!match.domains.some((v) => window.location.host.indexOf(v) !== -1) || ((await getDisabled()).some((v) => v === match))) {
|
if (re === null) return
|
||||||
continue
|
if (redirect) await storageDelete('redirect')
|
||||||
}
|
|
||||||
|
|
||||||
const re = document.body.innerHTML.match(match.regex)
|
const url = await match.match(re)
|
||||||
if (re === null) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = await match.match(re)
|
if (await getSetting("ff2mpv")) {
|
||||||
location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}`))
|
chrome.runtime.sendMessage({action: "ff2mpv", url: url})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.replace && !url.includes('.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}`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Stream Bypass",
|
"name": "Stream Bypass",
|
||||||
"author": "ByteDream",
|
"author": "ByteDream",
|
||||||
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
@ -14,19 +14,7 @@
|
|||||||
{
|
{
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
"matches": [
|
"matches": [
|
||||||
"*://*.evoload.io/*",
|
"<all_urls>"
|
||||||
"*://*.mixdrop.co/*",
|
|
||||||
"*://*.newgrounds.com/*",
|
|
||||||
"*://*.streamtape.com/*",
|
|
||||||
"*://*.streamzz.to/*",
|
|
||||||
"*://*.streamz.ws/*",
|
|
||||||
"*://*.upstream.to/*",
|
|
||||||
"*://*.vidlox.me/*",
|
|
||||||
"*://*.vidoza.net/*",
|
|
||||||
"*://*.vivo.sx/*",
|
|
||||||
"*://*.voe.sx/*",
|
|
||||||
"*://*.voeunblk.com/*",
|
|
||||||
"*://*.vupload.com/*"
|
|
||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"index.js"
|
"index.js"
|
||||||
@ -34,8 +22,19 @@
|
|||||||
"run_at": "document_end"
|
"run_at": "document_end"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage"
|
"storage",
|
||||||
|
"webRequest",
|
||||||
|
"nativeMessaging",
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"ui/player/*"
|
||||||
],
|
],
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
|
@ -1,231 +1,14 @@
|
|||||||
export enum Reliability {
|
import {Match, matches} from "./matches";
|
||||||
HIGH = 1,
|
import {getAllDisabled, getDisabled} from "../store/store";
|
||||||
NORMAL,
|
|
||||||
LOW,
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class Match {
|
export async function getMatch(host: string): Promise<Match | undefined> {
|
||||||
name: string
|
if (await getAllDisabled()) {
|
||||||
id: string
|
return undefined
|
||||||
reliability: Reliability
|
|
||||||
domains: string[]
|
|
||||||
regex: RegExp
|
|
||||||
abstract match(match: RegExpMatchArray): Promise<string>
|
|
||||||
|
|
||||||
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 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 Mp4Upload implements Match {
|
|
||||||
name = 'Mp4Upload'
|
|
||||||
id = 'mp4upload'
|
|
||||||
reliability = Reliability.LOW
|
|
||||||
domains = [
|
|
||||||
'mp4upload.com'
|
|
||||||
]
|
|
||||||
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=\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',
|
|
||||||
'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)
|
for (const match of matches) {
|
||||||
rot47(encoded: string): string {
|
if (match.domains.some(v => host.indexOf(v) !== -1) && !((await getDisabled()).some(v => v === match))) {
|
||||||
const s = []
|
return match
|
||||||
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 Mixdrop(),
|
|
||||||
new Newgrounds(),
|
|
||||||
new Streamtape(),
|
|
||||||
new Streamzz(),
|
|
||||||
new Upstream(),
|
|
||||||
new Vidlox(),
|
|
||||||
new Vidoza(),
|
|
||||||
new Vivo(),
|
|
||||||
new Voe(),
|
|
||||||
new Vupload()
|
|
||||||
]
|
|
||||||
|
294
src/match/matches.ts
Normal file
294
src/match/matches.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import {unPack} from "./unpack";
|
||||||
|
|
||||||
|
export enum Reliability {
|
||||||
|
HIGH,
|
||||||
|
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.NORMAL
|
||||||
|
domains = [
|
||||||
|
'doodstream.com',
|
||||||
|
'dood.pm',
|
||||||
|
'dood.ws',
|
||||||
|
'dood.wf',
|
||||||
|
'dood.cx',
|
||||||
|
'dood.sh',
|
||||||
|
'dood.watch',
|
||||||
|
'dood.to',
|
||||||
|
'dood.so',
|
||||||
|
'dood.la',
|
||||||
|
'dood.re'
|
||||||
|
]
|
||||||
|
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 Filemoon implements Match {
|
||||||
|
name = 'Filemoon'
|
||||||
|
id = 'filemoon'
|
||||||
|
reliability = Reliability.HIGH
|
||||||
|
domains = [
|
||||||
|
'filemoon.sx',
|
||||||
|
'filemoon.in'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mixdrop implements Match {
|
||||||
|
name = 'Mixdrop'
|
||||||
|
id = 'mixdrop'
|
||||||
|
reliability = Reliability.HIGH
|
||||||
|
domains = [
|
||||||
|
'mixdrop.co',
|
||||||
|
'mixdrop.to',
|
||||||
|
'mixdrop.ch',
|
||||||
|
'mixdrop.bz',
|
||||||
|
'mixdrop.gl'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)[0]
|
||||||
|
return `https:${url}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mp4Upload implements Match {
|
||||||
|
name = 'Mp4Upload'
|
||||||
|
id = 'mp4upload'
|
||||||
|
reliability = Reliability.HIGH
|
||||||
|
domains = [
|
||||||
|
'mp4upload.com'
|
||||||
|
]
|
||||||
|
replace = true
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=player.src\(").*(?=")/)[0]
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.LOW
|
||||||
|
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.HIGH
|
||||||
|
domains = [
|
||||||
|
'upstream.to'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Kwik implements Match {
|
||||||
|
name = 'Kwik'
|
||||||
|
id = 'kwik'
|
||||||
|
reliability = Reliability.HIGH
|
||||||
|
domains = [
|
||||||
|
'kwik.cx'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
console.log(match[0]);
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=source=').*(?=')/)[0]
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DropLoad implements Match {
|
||||||
|
name = 'Dropload'
|
||||||
|
id = 'dropload'
|
||||||
|
reliability = Reliability.HIGH
|
||||||
|
domains = [
|
||||||
|
'dropload.io'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SuperVideo implements Match {
|
||||||
|
name = 'Supervideo'
|
||||||
|
id = 'supervideo'
|
||||||
|
reliability = Reliability.HIGH
|
||||||
|
domains = [
|
||||||
|
'supervideo.tv'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
let unpacked = await unPack(match[0])
|
||||||
|
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GoodStream implements Match {
|
||||||
|
name = 'Goodstream'
|
||||||
|
id = 'goodstream'
|
||||||
|
reliability = Reliability.NORMAL
|
||||||
|
domains = [
|
||||||
|
'goodstream.uno'
|
||||||
|
]
|
||||||
|
regex = new RegExp(/(?<=file:\s+").*(?=")/g)
|
||||||
|
|
||||||
|
async match(match: RegExpMatchArray): Promise<string> {
|
||||||
|
return match[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const matches = [
|
||||||
|
new Doodstream(),
|
||||||
|
new Filemoon(),
|
||||||
|
new Mixdrop(),
|
||||||
|
new Mp4Upload(),
|
||||||
|
new Newgrounds(),
|
||||||
|
new Streamtape(),
|
||||||
|
new Streamzz(),
|
||||||
|
new Upstream(),
|
||||||
|
new Vidoza(),
|
||||||
|
new Voe(),
|
||||||
|
new Vupload(),
|
||||||
|
new Kwik(),
|
||||||
|
new DropLoad(),
|
||||||
|
new SuperVideo(),
|
||||||
|
new GoodStream()
|
||||||
|
]
|
84
src/match/unpack.ts
Normal file
84
src/match/unpack.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
function runInPageContext(to_execute: string) {
|
||||||
|
// Adapted from: https://github.com/arikw/extension-page-context
|
||||||
|
const doc = document;
|
||||||
|
|
||||||
|
// test that we are running with the allow-scripts permission
|
||||||
|
try { window.sessionStorage; } catch (ignore) { return null; }
|
||||||
|
|
||||||
|
// returned value container
|
||||||
|
const resultMessageId = parseInt('' + Math.floor((Math.random() * 100) + 1) + ((new Date()).getTime()));
|
||||||
|
|
||||||
|
// prepare script container
|
||||||
|
let scriptElm = doc.createElement('script');
|
||||||
|
scriptElm.setAttribute("type", "application/javascript");
|
||||||
|
|
||||||
|
const code = `
|
||||||
|
(
|
||||||
|
async function () {
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
id: ${resultMessageId}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
response.result = JSON.stringify(await (${to_execute})() ); // run script
|
||||||
|
} catch(err) {
|
||||||
|
response.error = JSON.stringify(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.postMessage(response, '*');
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
`;
|
||||||
|
|
||||||
|
// inject the script
|
||||||
|
scriptElm.textContent = code;
|
||||||
|
|
||||||
|
// run the script
|
||||||
|
doc.documentElement.appendChild(scriptElm);
|
||||||
|
|
||||||
|
// clean up script element
|
||||||
|
scriptElm.remove();
|
||||||
|
|
||||||
|
// create a "flat" promise
|
||||||
|
let resolve, reject;
|
||||||
|
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
||||||
|
|
||||||
|
|
||||||
|
// resolve on result
|
||||||
|
function onResult(event) {
|
||||||
|
const data = Object(event.data);
|
||||||
|
if (data.id === resultMessageId) {
|
||||||
|
window.removeEventListener('message', onResult);
|
||||||
|
if (data.error !== undefined) {
|
||||||
|
return reject(JSON.parse(data.error));
|
||||||
|
}
|
||||||
|
return resolve((data.result !== undefined) ? JSON.parse(data.result) : undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', onResult);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unPack = async (packed: String): Promise<string> => {
|
||||||
|
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
|
||||||
|
|
||||||
|
let context = `
|
||||||
|
{
|
||||||
|
eval: function (c) {
|
||||||
|
packed = c;
|
||||||
|
},
|
||||||
|
window: {},
|
||||||
|
document: {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const to_execute = `function() { let packed = ""; with(${context}) { ` + packed + '}; return packed; }'
|
||||||
|
|
||||||
|
const res = await runInPageContext(to_execute);
|
||||||
|
|
||||||
|
|
||||||
|
return (res+"").replace(/;/g, ";\n").replace(/{/g, "\n{\n").replace(/}/g, "\n}\n").replace(/\n;\n/g, ";\n").replace(/\n\\n/g, "\n");
|
||||||
|
}
|
27
src/store/settings.ts
Normal file
27
src/store/settings.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { storageSet, storageGet } from "./store"
|
||||||
|
|
||||||
|
export class Setting {
|
||||||
|
name: string
|
||||||
|
info_url?: string
|
||||||
|
|
||||||
|
constructor(name: string, info_url?: string) {
|
||||||
|
this.name = name
|
||||||
|
this.info_url = info_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async enable() {
|
||||||
|
await storageSet(this.name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async disable() {
|
||||||
|
await storageSet(this.name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_status() {
|
||||||
|
return await storageGet(this.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Settings = [
|
||||||
|
new Setting("ff2mpv", "https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams")
|
||||||
|
]
|
@ -1,6 +1,6 @@
|
|||||||
import {Match, matches} from "../match/match";
|
import {Match, matches} from "../match/matches";
|
||||||
|
|
||||||
async function storageGet(key: string): Promise<any> {
|
export async function storageGet(key: string): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
chrome.storage.local.get(key, (value) => {
|
chrome.storage.local.get(key, (value) => {
|
||||||
resolve(value[key])
|
resolve(value[key])
|
||||||
@ -8,12 +8,16 @@ async function storageGet(key: string): Promise<any> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storageSet(key: string, value: any) {
|
export async function storageSet(key: string, value: any) {
|
||||||
const obj = {}
|
const obj = {}
|
||||||
obj[key] = value
|
obj[key] = value
|
||||||
await chrome.storage.local.set(obj)
|
await chrome.storage.local.set(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function storageDelete(key: string) {
|
||||||
|
await chrome.storage.local.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getDisabled(): Promise<Match[]> {
|
export async function getDisabled(): Promise<Match[]> {
|
||||||
const localMatches = []
|
const localMatches = []
|
||||||
|
|
||||||
@ -29,7 +33,7 @@ export async function getDisabled(): Promise<Match[]> {
|
|||||||
|
|
||||||
export async function getAllDisabled(): Promise<boolean> {
|
export async function getAllDisabled(): Promise<boolean> {
|
||||||
const value = await storageGet('all')
|
const value = await storageGet('all')
|
||||||
return value !== undefined ? value as unknown as boolean : false
|
return value !== undefined ? String(value).toLowerCase() === 'true' : false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function enableAll() {
|
export async function enableAll() {
|
||||||
@ -62,3 +66,5 @@ export async function disable(match: Match) {
|
|||||||
await storageSet('disabled', disabled)
|
await storageSet('disabled', disabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getSetting = storageGet
|
@ -2,14 +2,16 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>HLS</title>
|
<title>Stream Bypass</title>
|
||||||
<link rel="stylesheet" href="player.css">
|
<link rel="stylesheet" href="player.css">
|
||||||
<script src="player.js" defer></script>
|
<script src="player.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<video id="video"></video>
|
<video id="video"></video>
|
||||||
<div id="message-container">
|
<div id="message-container">
|
||||||
<p id="message" hidden></p>
|
<p id="message"></p>
|
||||||
|
<br>
|
||||||
|
<p>Open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a></p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
body
|
|
||||||
background-color: #131313
|
|
||||||
|
|
||||||
html, body, video
|
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
margin: 0
|
|
||||||
|
|
||||||
video
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
left: 0
|
|
||||||
|
|
||||||
#message-container
|
|
||||||
display: flex
|
|
||||||
justify-content: center
|
|
||||||
align-items: center
|
|
||||||
height: 100%
|
|
||||||
|
|
||||||
#message
|
|
||||||
color: white
|
|
||||||
text-align: center
|
|
34
src/ui/player/player.scss
Normal file
34
src/ui/player/player.scss
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,20 @@
|
|||||||
import {Match, matches, Reliability} from "../../match/match";
|
import {matches} from "../../match/matches";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
|
|
||||||
function show_message(message: string) {
|
function show_message(message: string) {
|
||||||
document.getElementById('message').innerText = message
|
document.getElementById('message').innerText = message
|
||||||
document.getElementById('message-container').hidden = false
|
document.getElementById('message-container').style.visibility = 'visible'
|
||||||
document.getElementById('video').hidden = true
|
document.getElementById('video').hidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function play_native(url: string, match: Match) {
|
async function play_native(url: string) {
|
||||||
const video = document.getElementById('video') as HTMLVideoElement
|
const video = document.getElementById('video') as HTMLVideoElement
|
||||||
video.controls = true
|
video.controls = true
|
||||||
video.src = url
|
video.src = url
|
||||||
}
|
}
|
||||||
|
|
||||||
async function play_hls(url: string, match: Match) {
|
async function play_hls(url: string) {
|
||||||
const video = document.getElementById('video') as HTMLVideoElement
|
const video = document.getElementById('video') as HTMLVideoElement
|
||||||
video.controls = true
|
video.controls = true
|
||||||
|
|
||||||
@ -26,36 +26,6 @@ async function play_hls(url: string, match: Match) {
|
|||||||
})
|
})
|
||||||
hls.loadSource(url)
|
hls.loadSource(url)
|
||||||
hls.attachMedia(video)
|
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 {
|
} 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>')
|
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>')
|
||||||
}
|
}
|
||||||
@ -64,16 +34,17 @@ async function play_hls(url: string, match: Match) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
const urlQuery = new URLSearchParams(window.location.search)
|
const urlQuery = new URLSearchParams(window.location.search)
|
||||||
const id = urlQuery.get('id')
|
const id = urlQuery.get('id')
|
||||||
const url = urlQuery.get('url')
|
const url = decodeURIComponent(urlQuery.get('url'))
|
||||||
|
const domain = urlQuery.get('domain')
|
||||||
|
|
||||||
const match = matches.find((m) => m.id === id)
|
const match = matches.find((m) => m.id === id)
|
||||||
if (match === undefined) {
|
if (match === undefined) {
|
||||||
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>`)
|
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
document.title = match.name
|
document.title = `Stream Bypass (${domain})`
|
||||||
|
|
||||||
url.endsWith('.m3u8') ? await play_hls(url, match) : await play_native(url, match)
|
new URL(url).pathname.endsWith('.m3u8') ? await play_hls(url) : await play_native(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
@ -13,11 +13,19 @@
|
|||||||
<a>On</a>
|
<a>On</a>
|
||||||
<a>Off</a>
|
<a>Off</a>
|
||||||
</div>
|
</div>
|
||||||
|
<p>Hosters</p>
|
||||||
|
<table id="sub-container">
|
||||||
|
</table>
|
||||||
|
<hr class="upper-hr">
|
||||||
|
<p>Settings</p>
|
||||||
|
<table id="settings-container">
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<table id="sub-container">
|
<table id="sub-container">
|
||||||
</table>
|
</table>
|
||||||
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues/new">Something does not work</a>
|
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues">Something does not work</a>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
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
|
|
||||||
cursor: default
|
|
||||||
|
|
||||||
a
|
|
||||||
border: 1px solid #281515
|
|
||||||
cursor: pointer
|
|
||||||
font-weight: normal
|
|
||||||
padding: 5px 8px
|
|
||||||
|
|
||||||
&.active
|
|
||||||
background-color: rgba(255, 65, 65, 0.74)
|
|
||||||
cursor: default
|
|
||||||
|
|
||||||
&.disabled
|
|
||||||
background-color: grey
|
|
||||||
cursor: not-allowed
|
|
||||||
|
|
||||||
&#error
|
|
||||||
border: none
|
|
||||||
display: block
|
|
||||||
font-weight: lighter
|
|
||||||
font-size: 0.8rem
|
|
||||||
text-align: center
|
|
||||||
padding: 10px 0 5px 0
|
|
||||||
|
|
||||||
hr
|
|
||||||
margin: 3px 0
|
|
||||||
|
|
||||||
|
|
||||||
#all
|
|
||||||
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
|
|
76
src/ui/popup/popup.scss
Normal file
76
src/ui/popup/popup.scss
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
border: 1px solid #281515;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 5px 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: rgba(255, 65, 65, 0.74);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: grey;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&#error {
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
font-weight: lighter;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 3px 0;
|
||||||
|
|
||||||
|
&.upper-hr {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#all {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 10px 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
|
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
|
||||||
import {matches, Reliability} from "../../match/match";
|
import {matches, Reliability} from "../../match/matches";
|
||||||
|
import { Settings, Setting } from "../../store/settings";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const disabled = await getDisabled()
|
const disabled = await getDisabled()
|
||||||
@ -52,6 +53,51 @@ async function main() {
|
|||||||
subContainer.append(row)
|
subContainer.append(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settingsContainer = document.getElementById("settings-container")
|
||||||
|
for (const s of Settings) {
|
||||||
|
const row = document.createElement('tr')
|
||||||
|
|
||||||
|
const name = document.createElement('td')
|
||||||
|
const nameValue = document.createElement('p')
|
||||||
|
nameValue.innerText = s.name
|
||||||
|
|
||||||
|
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'
|
||||||
|
const info = document.createElement('a')
|
||||||
|
info.target = "_blank"
|
||||||
|
info.href = s.info_url
|
||||||
|
info.innerText = "🛈"
|
||||||
|
|
||||||
|
await s.get_status() ? on.classList.add('active') : off.classList.add('active')
|
||||||
|
|
||||||
|
on.onclick = async function () {
|
||||||
|
if (!on.classList.contains('disabled')) {
|
||||||
|
await s.enable()
|
||||||
|
on.classList.add('active')
|
||||||
|
off.classList.remove('active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
off.onclick = async function () {
|
||||||
|
if (!off.classList.contains('disabled')) {
|
||||||
|
await s.disable()
|
||||||
|
on.classList.remove('active')
|
||||||
|
off.classList.add('active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name.append(nameValue)
|
||||||
|
buttons.append(on, off)
|
||||||
|
if (s.info_url) {
|
||||||
|
buttons.append(info)
|
||||||
|
}
|
||||||
|
row.append(name, buttons)
|
||||||
|
settingsContainer.append(row)
|
||||||
|
}
|
||||||
|
|
||||||
const allOnButton = document.getElementById('all').getElementsByTagName('a')[0]
|
const allOnButton = document.getElementById('all').getElementsByTagName('a')[0]
|
||||||
const allOffButton = document.getElementById('all').getElementsByTagName('a')[1]
|
const allOffButton = document.getElementById('all').getElementsByTagName('a')[1]
|
||||||
|
|
||||||
|
@ -5,63 +5,14 @@ const rollupPluginCommonJs = require('@rollup/plugin-commonjs')
|
|||||||
const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default
|
const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default
|
||||||
const rollupPluginReplace = require('@rollup/plugin-replace')
|
const rollupPluginReplace = require('@rollup/plugin-replace')
|
||||||
const rollupPluginTypescript = require('@rollup/plugin-typescript')
|
const rollupPluginTypescript = require('@rollup/plugin-typescript')
|
||||||
const sass = require('node-sass')
|
const sass = require('sass')
|
||||||
const sassPluginNodeImport = require('node-sass-package-importer')
|
|
||||||
const typescript = require('typescript')
|
const typescript = require('typescript')
|
||||||
|
|
||||||
function getDomains() {
|
|
||||||
// because nodejs is nodejs, the simple commented out code below cannot be used.
|
|
||||||
// thus, the following bloated regexes must be used
|
|
||||||
/*const manifestMatches = []
|
|
||||||
for (const m of matches) {
|
|
||||||
for (const domain of m.domains) {
|
|
||||||
manifestMatches.push(`*://*.${domain}/*`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manifest['content_scripts']['matches'] = manifestMatches*/
|
|
||||||
|
|
||||||
let domains = []
|
|
||||||
|
|
||||||
const matchesRegex = new RegExp(/export\s+const\s+matches\s+=\s+(?<matches>\[.*?])/gms)
|
|
||||||
const matchesClassesRegex = new RegExp(/(?<!\/\/\s*)new\s+(?<class>\w+)\(\)/gms)
|
|
||||||
|
|
||||||
const matchTs = fs.readFileSync('src/match/match.ts')
|
|
||||||
const jsMatches = matchesRegex.exec(matchTs).groups.matches
|
|
||||||
let m
|
|
||||||
while ((m = matchesClassesRegex.exec(jsMatches))) {
|
|
||||||
if (m.index === matchesClassesRegex.lastIndex) {
|
|
||||||
matchesClassesRegex.lastIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m.groups.class !== undefined) {
|
|
||||||
const classDomainsRegex = new RegExp('class\\s+' + m.groups.class + '.*?domains\\s*=\\s*(?<domains>\\[.*?])', 'gms')
|
|
||||||
let mm
|
|
||||||
while ((mm = classDomainsRegex.exec(matchTs))) {
|
|
||||||
if (mm.index === classDomainsRegex.lastIndex) {
|
|
||||||
classDomainsRegex.lastIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mm.groups.domains !== undefined) {
|
|
||||||
const matches = []
|
|
||||||
for (const domain of JSON.parse(mm.groups.domains.replace(/'/g, '"', -1))) {
|
|
||||||
matches.push(domain)
|
|
||||||
}
|
|
||||||
domains = domains.concat(matches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildManifest() {
|
async function buildManifest() {
|
||||||
const manifest = JSON.parse(fs.readFileSync('src/manifest.json'))
|
const manifest = JSON.parse(fs.readFileSync('src/manifest.json'))
|
||||||
|
|
||||||
manifest['version'] = process.env.npm_package_version
|
manifest['version'] = process.env.npm_package_version
|
||||||
|
|
||||||
manifest['content_scripts'][0]['matches'] = getDomains().map((domain) => {return `*://*.${domain}/*`})
|
|
||||||
|
|
||||||
fs.writeFileSync('src/manifest.json', JSON.stringify(manifest, null, 2))
|
fs.writeFileSync('src/manifest.json', JSON.stringify(manifest, null, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,15 +46,12 @@ async function buildHtml() {
|
|||||||
|
|
||||||
async function buildCss() {
|
async function buildCss() {
|
||||||
const files = {
|
const files = {
|
||||||
'src/ui/popup/popup.sass': 'build/ui/popup/popup.css',
|
'src/ui/popup/popup.scss': 'build/ui/popup/popup.css',
|
||||||
'src/ui/player/player.sass': 'build/ui/player/player.css'
|
'src/ui/player/player.scss': 'build/ui/player/player.css'
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [src, dst] of Object.entries(files)) {
|
for (const [src, dst] of Object.entries(files)) {
|
||||||
const compiled = sass.renderSync({
|
const compiled = await sass.compileAsync(src)
|
||||||
file: src,
|
|
||||||
importer: sassPluginNodeImport()
|
|
||||||
})
|
|
||||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
||||||
fs.writeFileSync(dst, compiled.css)
|
fs.writeFileSync(dst, compiled.css)
|
||||||
}
|
}
|
||||||
@ -115,6 +63,7 @@ async function buildJs() {
|
|||||||
'src/ui/player/player.ts': 'build/ui/player/player.js',
|
'src/ui/player/player.ts': 'build/ui/player/player.js',
|
||||||
|
|
||||||
'src/index.ts': 'build/index.js',
|
'src/index.ts': 'build/index.js',
|
||||||
|
'src/background.ts': 'build/background.js'
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [src, dst] of Object.entries(files)) {
|
for (const [src, dst] of Object.entries(files)) {
|
||||||
|
Reference in New Issue
Block a user