9 Commits

Author SHA1 Message Date
48dc192964 Update version 2023-08-08 14:25:48 +02:00
7c45cacd36 add ff2mpv setting + new hosters (#14)
* add Kwik, use unpacker to improve reliabilty

* use packer for filemoon

* use packer for upstream

* add ff2mpv setting + functionality

* Update README.md

* get rid of @types/webextension-polyfill

* Revert "Update README.md"

This reverts commit affb600096.

* Update README.md

* Update info link for ff2mpv

* add kwik to hosters in readme

* removes console.logs

* Delete package-lock.json

* add package-lock.json to .gitignore

* unpack without using eval

* Merge main branch into here

* Add Dropload Hoster

* Add Supervideo Hoster

* Add GoodStream Hoster

* Add hosters to readme and delete console.logs

* Delete package-lock.json

* Fix ff2mpv info url

* Update readme

---------

Co-authored-by: bytedream <bytedream@protonmail.com>
2023-08-08 13:50:18 +02:00
00514e4e81 Update license year 2023-04-29 00:13:39 +02:00
4c1d0d0b63 Update dependencies 2023-04-29 00:12:00 +02:00
a920832945 Add Kwik and improve reliabilty for several other hosters (#12)
* add Kwik, use unpacker to improve reliabilty

* use packer for filemoon

* use packer for upstream

* Update README.md

* Revert "Update README.md"

This reverts commit affb600096.

* add kwik to hosters in readme

* unpack without using eval
2023-04-28 18:19:01 +02:00
7d8d8b6614 Merge pull request #9 from bachig26/master
Update matches.ts
2023-01-19 13:27:04 +01:00
b4052834f2 Update matches.ts
- add dood.re
2023-01-19 05:02:40 +01:00
9900c33243 Merge pull request #7 from bachig26/patch-1
added more domains
2023-01-11 08:10:40 +01:00
98709bc934 added more domains
- additional domains to mixdrop and doodstream
2023-01-11 01:07:48 +01:00
14 changed files with 349 additions and 55 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 ByteDream
Copyright (c) 2022-NOW ByteDream
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -27,7 +27,9 @@ A multi-browser addon / extension for multiple streaming providers which redirec
<a href="#-supported-sites">Supported Sites 📜</a>
<a href="#%EF%B8%8F-building">Building </a>
<a href="#%EF%B8%8F-building">Building 🛠</a>
<a href="#%EF%B8%8F-settings">Settings ⚙️</a>
<a href="#-license">License ⚖</a>
</p>
@ -83,6 +85,10 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
| [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) | ✔ | |
| [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.
- ⚠: Included in the addon but will probably not work. See `Note` in this case, an explanation why will stand there in the most cases.
@ -99,7 +105,7 @@ Some sites put much effort in obfuscating their code / how they receive the vide
</ul>
</details>
## Building
## 🛠 Building
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
@ -128,6 +134,36 @@ When using firefox, use the following:
3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
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
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.

View File

@ -1,6 +1,6 @@
{
"name": "stream-bypass",
"version": "2.1.7",
"version": "2.2.0",
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
"main": "src/index.ts",
"scripts": {
@ -20,22 +20,21 @@
},
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^22.0.0",
"@rollup/plugin-node-resolve": "^14.0.1",
"@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.196",
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.1.0",
"@types/chrome": "^0.0.234",
"@types/node-sass": "^4.11.2",
"@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"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",
"rollup": "^3.21.0",
"sass": "^1.62.1",
"tippy.js": "^6.3.7",
"typescript": "^4.7.3",
"typescript": "^5.0.4",
"web-ext": "^7.0.0"
}
}

View File

@ -2,6 +2,13 @@ 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) {

View File

@ -1,5 +1,5 @@
import {getMatch} from "./match/match";
import {storageDelete, storageGet} from "./store/store";
import {storageDelete, storageGet, getSetting} from "./store/store";
import {Match, matches} from "./match/matches";
async function main() {
@ -21,7 +21,11 @@ async function main() {
const url = await match.match(re)
if (match.replace && !url.endsWith('.m3u8')) {
if (await getSetting("ff2mpv")) {
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%'

View File

@ -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": "2.1.6",
"version": "2.2.0",
"homepage_url": "https://github.com/ByteDream/stream-bypass",
"browser_specific_settings": {
"gecko": {
@ -30,6 +30,7 @@
"permissions": [
"storage",
"webRequest",
"nativeMessaging",
"<all_urls>"
],
"web_accessible_resources": [

View File

@ -1,3 +1,5 @@
import {unPack} from "./unpack";
export enum Reliability {
HIGH,
NORMAL,
@ -24,7 +26,14 @@ class Doodstream implements Match {
'doodstream.com',
'dood.pm',
'dood.ws',
'dood.wf'
'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)
@ -46,56 +55,52 @@ class Filemoon implements Match {
id = 'filemoon'
reliability = Reliability.HIGH
domains = [
'filemoon.sx'
'filemoon.sx',
'filemoon.in'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
const start_idx = match.indexOf('moon')
const prefix = `${match[start_idx]}-${match[start_idx-1]}-${match[start_idx-2]}-${match[start_idx-3]}`
const time = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
const offset = !isNaN(parseInt(match[start_idx-12])) && parseInt(match[start_idx-12]).toString().length == match[start_idx-12].length ? 0 : -1
return `https://${prefix}.filemoon.${match[start_idx-4]}/${match[start_idx-5]}/${match[start_idx-6]}/${match[start_idx-7]}/${match[start_idx-8]}/master.m3u8?t=${match[start_idx-11]}${offset != 0 ? `-${match[start_idx-12]}` : ''}&s=${time}&e=${match[start_idx + offset - 12]}&sp=${match[start_idx + offset - 18]}`
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
return url
}
}
class Mixdrop implements Match {
name = 'Mixdrop'
id = 'mixdrop'
reliability = Reliability.NORMAL
reliability = Reliability.HIGH
domains = [
'mixdrop.co'
'mixdrop.co',
'mixdrop.to',
'mixdrop.ch',
'mixdrop.bz',
'mixdrop.gl'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
const prefix = /(?<=\/\/)[a|s](?=-)/.exec(document.body.innerHTML)[0]
const subdomain = match[1].length < match[2].length ? match[1] : match[2]
const domain = match.slice().sort((a, b) => b.length - a.length).find(m => /^[a-z]+$/.test(m))
const id = match[1].length > match[2].length ? match[1] : match[2]
const tld = match.find(m => ['net', 'io', 'to', 'sx', 'com'].indexOf(m) !== -1)
const s = match.slice().sort((a, b) => b.length - a.length).slice(1)[0]
const e_t = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
return `https://${prefix}-${subdomain}.${domain}.${tld}/v/${id}.mp4?s=${s}&e=${e_t}&_t=${e_t}`
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.NORMAL
reliability = Reliability.HIGH
domains = [
'mp4upload.com'
]
replace = true
regex = new RegExp(/(?<=\|)\w{2,}/gm)
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
let id = match.slice().reduce((a, b) => a.length >= b.length ? a : b)
return `https://www4.mp4upload.com:282/d/${id}/video.mp4`
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=player.src\(").*(?=")/)[0]
return url
}
}
@ -152,14 +157,16 @@ class Streamzz implements Match {
class Upstream implements Match {
name = 'Upstream'
id = 'upstream'
reliability = Reliability.NORMAL
reliability = Reliability.HIGH
domains = [
'upstream.to'
]
regex = new RegExp(/(?<=\|)\w{2,}/gm)
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
async match(match: RegExpMatchArray): Promise<string> {
return `https://${match[49]}.upstreamcdn.co/hls/${match[148]}/master.m3u8`
let unpacked = await unPack(match[0])
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
return url
}
}
@ -205,6 +212,69 @@ class Vupload implements Match {
}
}
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(),
@ -216,5 +286,9 @@ export const matches = [
new Upstream(),
new Vidoza(),
new Voe(),
new Vupload()
new Vupload(),
new Kwik(),
new DropLoad(),
new SuperVideo(),
new GoodStream()
]

84
src/match/unpack.ts Normal file
View 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
View 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")
]

View File

@ -66,3 +66,5 @@ export async function disable(match: Match) {
await storageSet('disabled', disabled)
}
}
export const getSetting = storageGet

View File

@ -13,8 +13,16 @@
<a>On</a>
<a>Off</a>
</div>
<p>Hosters</p>
<table id="sub-container">
</table>
<hr class="upper-hr">
<p>Settings</p>
<table id="settings-container">
</table>
</div>
<hr>
<table id="sub-container">
</table>
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues">Something does not work</a>

View File

@ -40,16 +40,26 @@ a {
}
hr {
margin: 3px 0;
&.upper-hr {
width: 100%;
}
}
#all {
display: flex;
justify-content: center;
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);

View File

@ -1,5 +1,6 @@
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
import {matches, Reliability} from "../../match/matches";
import { Settings, Setting } from "../../store/settings";
async function main() {
const disabled = await getDisabled()
@ -52,6 +53,51 @@ async function main() {
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 allOffButton = document.getElementById('all').getElementsByTagName('a')[1]

View File

@ -5,8 +5,7 @@ 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 sass = require('sass')
const typescript = require('typescript')
async function buildManifest() {
@ -52,10 +51,7 @@ async function buildCss() {
}
for (const [src, dst] of Object.entries(files)) {
const compiled = sass.renderSync({
file: src,
importer: sassPluginNodeImport()
})
const compiled = await sass.compileAsync(src)
fs.mkdirSync(path.dirname(dst), {recursive: true})
fs.writeFileSync(dst, compiled.css)
}