Compare commits
169 Commits
Author | SHA1 | Date | |
---|---|---|---|
607326e6d6 | |||
47f6de2de8 | |||
d159fb0aa4 | |||
290733a85a | |||
84eb32ab61 | |||
927a4e3ae3 | |||
5926177920 | |||
9a29316641 | |||
c300125f64 | |||
59f2ffec57 | |||
6dff691c25 | |||
c4085f9ac8 | |||
422702b16f | |||
c3dad3681c | |||
6eb3259ec5 | |||
68e445a738 | |||
e864bc6100 | |||
3ac73ac3b4 | |||
6aaf960c28 | |||
8a13dea681 | |||
8d575241fe | |||
50f400b8b9 | |||
f6fcfd354a | |||
c081127d35 | |||
416fceba88 | |||
a9cf03c176 | |||
8852318483 | |||
7067aaf4a0 | |||
bf723e2ed6 | |||
9c80362a1c | |||
ae738ff32f | |||
16d4f2956a | |||
|
f6c6102436 | ||
|
f1525817b1 | ||
1e1d8cdfa6 | |||
47eed2d12b | |||
67e03eafaa | |||
db0ccd9b56 | |||
fd3eda9c61 | |||
e522916585 | |||
b9514284da | |||
06beaa6ebe | |||
6a56c37dd2 | |||
018b381732 | |||
696ec22471 | |||
e379113aa4 | |||
84605ceb30 | |||
67c492db47 | |||
ae2f196b06 | |||
298e9308ce | |||
3cd4c6b6b7 | |||
6286acf85c | |||
94eee79218 | |||
2f186bda07 | |||
1c9f95cebc | |||
c57cd03407 | |||
64cf565da3 | |||
bf8a7eb602 | |||
c643a39e2d | |||
fcfbc41fdb | |||
1251f079f5 | |||
ccfc77167b | |||
a10066458f | |||
6a8c705b06 | |||
76a5bac7fd | |||
3aca863a4b | |||
fe14edc0ef | |||
fffe23638c | |||
5214228a72 | |||
7000d5a08b | |||
a21f799e7d | |||
1fb85313dd | |||
6e7410b088 | |||
|
0054f5da0e | ||
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 | ||
|
60a2247b02 | ||
88814fcc83 | |||
f908e8249b | |||
8d57dfbc57 | |||
3aa84ba709 | |||
9ecfbd44e9 | |||
b1bea7687c | |||
c3148a76a3 | |||
210aa5c263 | |||
a55c455fcc | |||
923787bedb | |||
8990e25a72 | |||
9b4bcc6c64 | |||
2e8cce10b5 | |||
9d4b52c107 | |||
806320a0de | |||
774d3b52d7 | |||
31787bb3e3 | |||
e64feb5130 | |||
aa51b5d95f | |||
27c03c0b5b | |||
f48adc3db3 | |||
feaf843584 | |||
0b6021cf52 | |||
8b375cae2c | |||
c9acb929cc | |||
7a69ac83e0 | |||
a9a8609cb8 | |||
e0d4f8747e | |||
f8213772c8 | |||
|
bce27f2beb | ||
7bfae612f9 | |||
106f4ffda3 | |||
b83d287a42 | |||
a39418e506 |
56
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install nodejs
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- manifest_version: 2
|
||||||
|
- manifest_version: 3
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install nodejs
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
MANIFEST_VERSION: ${{ matrix.manifest_version }}
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: stream-bypass-mv${{ matrix.manifest_version }}
|
||||||
|
path: ./dist
|
||||||
|
if-no-files-found: error
|
5
.gitignore
vendored
@ -1 +1,4 @@
|
|||||||
build/
|
.idea/
|
||||||
|
dist/
|
||||||
|
release/
|
||||||
|
node_modules/
|
||||||
|
2
.npmrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
auto-install-peers=false
|
||||||
|
strict-peer-dependencies=false
|
8
.prettierignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
package-lock.json
|
17
.prettierrc.cjs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/** @type {import("prettier").Config} */
|
||||||
|
module.exports = {
|
||||||
|
useTabs: true,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'none',
|
||||||
|
printWidth: 100,
|
||||||
|
plugins: ['prettier-plugin-svelte', '@ianvs/prettier-plugin-sort-imports'],
|
||||||
|
/* prettier-plugin-svelte */
|
||||||
|
overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
|
||||||
|
/* @ianvs/prettier-plugin-sort-imports */
|
||||||
|
importOrder: ['^~/(.*)$', '^./(.*)$', ''],
|
||||||
|
importOrderParserPlugins: ['typescript'],
|
||||||
|
importOrderTypeScriptVersion: '5.0.0',
|
||||||
|
importOrderCaseSensitive: false
|
||||||
|
};
|
@ -1,9 +0,0 @@
|
|||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add python3 nodejs npm
|
|
||||||
|
|
||||||
RUN npm install -g typescript sass
|
|
||||||
|
|
||||||
COPY [".", "."]
|
|
||||||
|
|
||||||
CMD ["python3", "build.py", "-b", "-c"]
|
|
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021 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
|
||||||
|
226
README.md
@ -2,129 +2,175 @@
|
|||||||
|
|
||||||
A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.
|
A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.
|
||||||
|
|
||||||
This addon replaces the video player from this sides with the native player build-in into the browser or redirects directly to the source video.
|
|
||||||
This has the advantage, that no advertising or popups are shown when trying to interact with the video (playing, skipping, ...) or some sites are showing them even if you do nothing.
|
|
||||||
Additionally this enables you to download the video by right-clicking it and just choose the download option.
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<a href="https://github.com/bytedream/stream-bypass/releases/latest">
|
||||||
|
<img src="https://img.shields.io/github/v/release/ByteDream/stream-bypass?label=Version&style=flat-square" alt="Version">
|
||||||
|
</a>
|
||||||
<a href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
|
<a href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
|
||||||
<img src="https://img.shields.io/amo/users/stream-bypass?label=Firefox%20Addon%20Store&style=flat-square" alt="Firefox Addon Store">
|
<img src="https://img.shields.io/amo/users/stream-bypass?label=Firefox%20Users&style=flat-square" alt="Firefox Addon Store">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/ByteDream/stream-bypass/releases/latest">
|
<a href="https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb">
|
||||||
|
<img src="https://img.shields.io/chrome-web-store/users/ddfpfjomnakfckhmilacnbokdaknamdb?style=flat-square&label=Chrome%20Users" alt="Chrome Store">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/bytedream/stream-bypass/releases/latest">
|
||||||
<img src="https://img.shields.io/github/downloads/ByteDream/stream-bypass/total?label=GitHub%20Downloads&style=flat-square" alt="GitHub Downloads">
|
<img src="https://img.shields.io/github/downloads/ByteDream/stream-bypass/total?label=GitHub%20Downloads&style=flat-square" alt="GitHub Downloads">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/gUWwekeNNg">
|
|
||||||
<img src="https://img.shields.io/discord/915659846836162561?label=discord&style=flat-square" alt="Discord">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Supported streaming providers (for a complete list of all supported websites, see [here](SUPPORTED) or in [show all](#all-supported-websites) below):
|
<p align="center">
|
||||||
- [streamtape.com](https://streamtape.com)
|
<a href="#-introduction">Introduction 📝</a>
|
||||||
- [vivo.sx](https://vivo.sx)
|
•
|
||||||
- [voe.sx](https://voe.sx)
|
<a href="#-installation">Installation 📥</a>
|
||||||
|
•
|
||||||
|
<a href="#-features">Features ✨</a>
|
||||||
|
•
|
||||||
|
<a href="#-supported-websites">Supported Websites 📜</a>
|
||||||
|
•
|
||||||
|
<a href="#%EF%B8%8F-building">Building 🛠️</a>
|
||||||
|
•
|
||||||
|
<a href="#%EF%B8%8F-settings">Settings ⚙️</a>
|
||||||
|
•
|
||||||
|
<a href="#-license">License ⚖</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<details id="all-supported-websites">
|
## 📝 Introduction
|
||||||
<summary><b>Show all</b></summary>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://evoload.io">evoload.io</a></li>
|
|
||||||
<li><a href="https://mcloud.to">mcloud.to</a></li>
|
|
||||||
<li><a href="https://mixdrop.co">mixdrop.co</a></li>
|
|
||||||
<li><a href="https://streamtape.com">streamtape.com</a></li>
|
|
||||||
<li><a href="https://streamzz.to">streamzz.to</a></li>
|
|
||||||
<li><a href="https://thevideome.com">thevideome.com</a></li>
|
|
||||||
<li><a href="https://vidlox.me">vidlox.me</a></li>
|
|
||||||
<li><a href="https://vidstream.pro">vidstream.pro</a></li>
|
|
||||||
<li><a href="https://vidoza.net">vidoza.net</a></li>
|
|
||||||
<li><a href="https://vivo.st">vivo.st</a></li>
|
|
||||||
<li><a href="https://vivo.sx">vivo.sx</a></li>
|
|
||||||
<li><a href="https://voe.sx">voe.sx</a></li>
|
|
||||||
<li><a href="https://vupload.com">vupload.com</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
This addon replaces the video player from this sides with the native player build-in into the browser or redirects directly to the source video.
|
||||||
|
This has the advantage, that no advertising or popups are shown when trying to interact with the video (playing, skipping, ...) or some sites are showing them even if you do nothing.
|
||||||
|
Additionally, this enables you to download the video by right-clicking it and just choose the download option.
|
||||||
|
|
||||||
<details id="example">
|
<details id="example">
|
||||||
<summary><b>How it's working</b></summary>
|
<summary><b>How it's working:</b></summary>
|
||||||
<img src="example.gif" alt="">
|
<img src="example.gif" alt="">
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
The addon was tested on
|
## 📥 Installation
|
||||||
- Firefox (96.0.3)
|
|
||||||
- Ungoogled Chromium (97.0)
|
|
||||||
- Vivaldi (5.0)
|
|
||||||
- Opera (83.0)
|
|
||||||
|
|
||||||
## Installing
|
### Official browser stores
|
||||||
|
|
||||||
### Firefox
|
The best way to install the extension are the official browser extension stores:
|
||||||
|
|
||||||
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/).
|
- [Firefox Addon Store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/) (Firefox for Android is supported too!)
|
||||||
|
- [Chrome Web Store](https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb)
|
||||||
|
|
||||||
### Chromium / Google Chrome
|
### Manual installation
|
||||||
|
|
||||||
1. Download the zipfile from the [latest release](https://smartrelease.bytedream.org/github/ByteDream/stream-bypass/stream_bypass-{tag}.zip) and unzip it (with [7zip](https://www.7-zip.org/) or something like that).
|
- Firefox
|
||||||
2. Go into your browser and type `chrome://extensions` in the address bar.
|
- Download `stream-bypass-<version>-mv2.zip` 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)
|
||||||
3. Turn the developer mode in the top right corner on.
|
- Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar
|
||||||
4. Click Load unpacked.
|
- Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the unzipped directory
|
||||||
5. Choose the cloned / unzipped directory.
|
- Chromium / Google Chrome
|
||||||
|
> As nearly every browser other than Firefox is based on Chromium, this should be the same for most of them
|
||||||
|
- Download `stream-bypass-<version>-mv3.zip` 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)
|
||||||
|
- Go into your browser and type `chrome://extensions` in the address bar
|
||||||
|
- Turn on the developer mode by checking the switch in the top right corner
|
||||||
|
- Click `Load unpacked` and choose the unzipped directory
|
||||||
|
|
||||||
### Opera
|
## ✨ Features
|
||||||
|
|
||||||
1. Download the zipfile from the [latest release](https://smartrelease.bytedream.org/github/ByteDream/stream-bypass/stream_bypass-{tag}.zip) and unzip it (with [7zip](https://www.7-zip.org/) or something like that).
|
| Feature | Firefox | Chrome | Firefox for Android |
|
||||||
2. Go into your browser and type `opera://extensions` in the address bar.
|
| --------------------------------------------------------------------------------------------------------------------------------- | ------- | ------ | ------------------- |
|
||||||
3. Turn the developer mode in the top right corner on.
|
| Replace site-speicifc video player with browser native video player | ✔ | ✔ | ✔ |
|
||||||
4. Click Load unpacked.
|
| Support websites that are accessed via a redirect | ✔ | ❌ | ✔ |
|
||||||
5. Choose the cloned / unzipped directory.
|
| Open video in mpv (with [ff2mpv](https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams)) | ✔ | ✔ | ❌ |
|
||||||
|
|
||||||
## Compiling
|
- ✔️: Supported.
|
||||||
|
- ❌: Not supported.
|
||||||
|
|
||||||
If you want to use / install the addon from source, you have to compile the `typescript` and `sass` files yourself.
|
## 📜 Supported websites
|
||||||
- Compile it [manual](#manual).
|
|
||||||
- Compile it using [docker](#docker).
|
|
||||||
|
|
||||||
### Manual
|
| Site | Firefox & Firefox for Android | Chrome & Chromium based |
|
||||||
|
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||||
|
| [dropload.io](https://dropload.io) | ✔ | ✔ |
|
||||||
|
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔ | ⚠ (redirect probably required) |
|
||||||
|
| [filemoon.to](https://filemoon.to) | ✔ | ✔ |
|
||||||
|
| [goodstream.uno](https://goodstream.uno) | ✔ | ✔ |
|
||||||
|
| [kwik.cx](https://kwik.cx) | ✔ | ✔ |
|
||||||
|
| [loadx.ws](https://loadx.ws) | ✔ | ❌ (background request always required) |
|
||||||
|
| [luluvdo.com](https://luluvdo.com) | ✔ | ❌ (background request always required) |
|
||||||
|
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | ✔ |
|
||||||
|
| [mp4upload.com](https://mp4upload.com) | ✔ | ✔ |
|
||||||
|
| [newgrounds.com](https://newgrounds.com) | ✔ | ✔ |
|
||||||
|
| [streama2z.com](https://streama2z.com) | ✔ | ❌ (redirect always required) |
|
||||||
|
| [streamtape.com](https://streamtape.com) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) |
|
||||||
|
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | ✔ |
|
||||||
|
| [supervideo.tv](https://supervideo.tv) | ✔ | ✔ |
|
||||||
|
| [upstream.to](https://upstream.to) | ✔ | ✔ |
|
||||||
|
| [vidmoly.to](https://vidmoly.me) | ✔ | ✔ |
|
||||||
|
| [vidoza.net](https://vidoza.net) | ✔ | ✔ |
|
||||||
|
| [voe.sx](https://voe.sx) | ✔ | ❌ (redirect always required) |
|
||||||
|
| [vupload.com](https://vupload.com) | ✔ | ✔ |
|
||||||
|
|
||||||
For compiling everything bare bones, you need [typescript](https://www.typescriptlang.org/) and [sass](https://sass-lang.com/) installed.
|
- ✔️: Everything ok.
|
||||||
- Compile typescript
|
- ⚠: Works with limitations.
|
||||||
|
- ❌: Not supported.
|
||||||
|
|
||||||
|
_This table might not be 100% accurate, it isn't actively monitored if the addon works for every website!_
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 🛠️ Building
|
||||||
|
|
||||||
|
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- `npm` installed.
|
||||||
|
- A copy of this repository and a shell / console open in the copied directory.
|
||||||
|
|
||||||
|
If the requirements are satisfied, you can continue with the following commands:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# install all dependencies
|
||||||
|
$ npm install
|
||||||
|
|
||||||
|
# build the extension source to the dist/ directory
|
||||||
|
$ npm run build
|
||||||
|
|
||||||
|
# same as build + more optimizations and browser specific settings at release/
|
||||||
|
$ npm run release:firefox # or "release:chrome" to create a release for chromium based browsers
|
||||||
```
|
```
|
||||||
$ tsc -p src
|
|
||||||
```
|
|
||||||
- Compile sass (replace `<path to sass file>` with every `.sass` file in the `src` directory)
|
|
||||||
```
|
|
||||||
$ sass --no-source-map <path to sass file>
|
|
||||||
```
|
|
||||||
The compiled output will be in the `src` directory.
|
|
||||||
|
|
||||||
If you want to keep it a little cleaner, you additionally need [python3](https://www.python.org).
|
|
||||||
- Compile everything with one line
|
|
||||||
```
|
|
||||||
$ python3 build.py -b -c
|
|
||||||
```
|
|
||||||
The compiled output will remain in a (new created if not existing) `build` directory.
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
For this, you need [docker](https://www.docker.com/) to be installed.
|
|
||||||
- Build the docker image
|
|
||||||
```
|
|
||||||
$ docker build -t stream-bypass .
|
|
||||||
```
|
|
||||||
- Compile
|
|
||||||
```
|
|
||||||
$ docker run --rm -v build:/build stream-bypass
|
|
||||||
```
|
|
||||||
The compiled output will remain in a (new created if not existing) `build` directory.
|
|
||||||
|
|
||||||
##### Install
|
##### Install
|
||||||
|
|
||||||
If you want to use the addon in Chromium or any browser which is based on it (almost every other, Google Chrome, Opera, ...), follow the steps in [installing](#installing).
|
If you want to use the addon in Chromium or any browser which is based on it, follow the steps in [installation](#-installation).
|
||||||
When using firefox, use the following
|
When using firefox, use the following:
|
||||||
|
|
||||||
1. Type `about:debugging` in the browser's address bar.
|
1. Type `about:debugging` in the browser's address bar.
|
||||||
2. Select 'This Firefox' tab (maybe named different, depending on your language).
|
2. Select 'This Firefox' tab (maybe named different, depending on your language).
|
||||||
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.
|
||||||
|
|
||||||
## License
|
## ⚙️ 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://ddfpfjomnakfckhmilacnbokdaknamdb/` to `allowed_origins` ->
|
||||||
|
```
|
||||||
|
"allowed_origins": [
|
||||||
|
"chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/",
|
||||||
|
"chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚖ 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.
|
||||||
|
13
SUPPORTED
@ -1,13 +0,0 @@
|
|||||||
evoload.io
|
|
||||||
mcloud.to
|
|
||||||
mixdrop.co
|
|
||||||
streamtape.com
|
|
||||||
streamzz.to
|
|
||||||
thevideome.com
|
|
||||||
vidlox.me
|
|
||||||
vidstream.pro
|
|
||||||
vidoza.net
|
|
||||||
vivo.st
|
|
||||||
vivo.sx
|
|
||||||
voe.sx
|
|
||||||
vupload.com
|
|
158
build.py
@ -1,158 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
import urllib.request
|
|
||||||
import zipfile
|
|
||||||
from pathlib import Path
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def load_matches():
|
|
||||||
matched = []
|
|
||||||
|
|
||||||
indexed = False
|
|
||||||
pattern = re.compile(r"(?<=\[')\S*(?=',)")
|
|
||||||
for line in open('src/match.ts', 'r'):
|
|
||||||
if not indexed:
|
|
||||||
if 'constmatches=[' in line.replace(' ', ''):
|
|
||||||
indexed = True
|
|
||||||
else:
|
|
||||||
match = pattern.findall(line)
|
|
||||||
if match:
|
|
||||||
if not line.strip().startswith('//'):
|
|
||||||
matched.append(match[0])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
return matched
|
|
||||||
|
|
||||||
|
|
||||||
def write_manifest():
|
|
||||||
matches = load_matches()
|
|
||||||
manifest = json.load(open('src/manifest.json', 'r'))
|
|
||||||
|
|
||||||
for content_script in manifest['content_scripts']:
|
|
||||||
content_script['matches'] = [f'*://{match}/*' for match in matches]
|
|
||||||
|
|
||||||
json.dump(manifest, open('src/manifest.json', 'w'), indent=2)
|
|
||||||
|
|
||||||
|
|
||||||
def write_supported():
|
|
||||||
open('SUPPORTED', 'w').writelines([f'{match}\n' for match in load_matches()])
|
|
||||||
|
|
||||||
|
|
||||||
def write_readme():
|
|
||||||
firefox_pattern = re.compile(r'Mozilla Firefox (?P<version>.+)')
|
|
||||||
chromium_pattern = re.compile(r'(?P<version>\d+\.\d+)')
|
|
||||||
tested = {}
|
|
||||||
|
|
||||||
stdout, stderr = subprocess.Popen(['firefox', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
|
||||||
if stderr == b'':
|
|
||||||
tested['Firefox'] = re.search(firefox_pattern, stdout.decode('utf-8').replace('\n', '')).group('version')
|
|
||||||
|
|
||||||
for command, name in {'chromium': 'Ungoogled Chromium', 'vivaldi-stable': 'Vivaldi', 'opera': 'Opera'}.items():
|
|
||||||
stdout, stderr = subprocess.Popen([command, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
|
||||||
if stderr == b'':
|
|
||||||
tested[name] = re.search(chromium_pattern, stdout.decode('utf-8').replace('\n', '')).group('version')
|
|
||||||
|
|
||||||
# it this the right syntax if i want to read and write to a file? * dreams in python3.10 *
|
|
||||||
with open('README.md', 'r') as read_file:
|
|
||||||
readme = read_file.read()
|
|
||||||
|
|
||||||
# adds all available websites
|
|
||||||
all_providers_regex = r'(?<=<ul>\n)(.+?)(?=</ul>)'
|
|
||||||
domains = filter(lambda domain: domain != '', open('SUPPORTED', 'r').read().split('\n'))
|
|
||||||
all_providers = '\n'.join(f'\t\t<li><a href="https://{supported}">{supported}</a></li>' for supported in domains) + '\n'
|
|
||||||
readme = re.sub(all_providers_regex, all_providers, readme, flags=re.DOTALL)
|
|
||||||
|
|
||||||
# adds all installed browsers to the tested browser section. i'm just to lazy to seek out all browser versions manually
|
|
||||||
tested_browsers_regex = r'(?<=The addon was tested on\n)(.+?)(?=\n*## Installing)'
|
|
||||||
tested_browsers = '\n'.join(f'- {name} ({version})' for name, version in tested.items())
|
|
||||||
readme = re.sub(tested_browsers_regex, tested_browsers, readme, flags=re.DOTALL)
|
|
||||||
|
|
||||||
# rewrite the readme
|
|
||||||
with open('README.md', 'w') as write_file:
|
|
||||||
write_file.write(readme)
|
|
||||||
write_file.close()
|
|
||||||
read_file.close()
|
|
||||||
|
|
||||||
|
|
||||||
def copy_built():
|
|
||||||
if not shutil.which('tsc'):
|
|
||||||
sys.stderr.write('The typescript compiler `tsc` could not be found')
|
|
||||||
sys.exit(1)
|
|
||||||
elif not shutil.which('sass'):
|
|
||||||
sys.stderr.write('The sass compiler `sass` could not be found')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
write_manifest()
|
|
||||||
|
|
||||||
subprocess.call(['tsc', '-p', 'src'])
|
|
||||||
|
|
||||||
build_path = Path('build')
|
|
||||||
if not build_path.is_dir():
|
|
||||||
build_path.mkdir()
|
|
||||||
for file in Path('src').rglob('*'):
|
|
||||||
build_file = build_path.joinpath(str(file)[4:])
|
|
||||||
if file.is_dir():
|
|
||||||
if not build_file.exists():
|
|
||||||
build_file.mkdir(parents=True)
|
|
||||||
elif file.suffix == '.sass':
|
|
||||||
css_file = str(file)[:-4] + 'css'
|
|
||||||
subprocess.call(['sass', '--no-source-map', file, css_file])
|
|
||||||
shutil.copy(css_file, str(build_path.joinpath(css_file[4:])))
|
|
||||||
elif file.name == 'tsconfig.json':
|
|
||||||
continue
|
|
||||||
elif file.suffix != '.ts':
|
|
||||||
shutil.copy(str(file), str(build_file))
|
|
||||||
|
|
||||||
ext_path = Path('build', 'ext')
|
|
||||||
if not ext_path.is_dir():
|
|
||||||
ext_path.mkdir()
|
|
||||||
|
|
||||||
# download hls.js (version 1.1.1)
|
|
||||||
with zipfile.ZipFile(io.BytesIO(urllib.request.urlopen('https://github.com/video-dev/hls.js/releases/download/v1.1.1/release.zip').read())) as z:
|
|
||||||
open(ext_path.joinpath('hls.light.min.js'), 'wb').write(z.read('dist/hls.light.min.js'))
|
|
||||||
z.close()
|
|
||||||
|
|
||||||
# download popperjs core (version 2.10.2)
|
|
||||||
open(ext_path.joinpath('popper.min.js'), 'wb').write(urllib.request.urlopen('https://unpkg.com/@popperjs/core@2.10.2/dist/umd/popper.min.js').read())
|
|
||||||
|
|
||||||
# download tippy.js (version 6.3.7)
|
|
||||||
open(ext_path.joinpath('tippy-bundle.umd.min.js'), 'wb').write(urllib.request.urlopen('https://unpkg.com/tippy.js@6.3.7/dist/tippy-bundle.umd.min.js').read())
|
|
||||||
|
|
||||||
|
|
||||||
def clean_build():
|
|
||||||
for file in Path('src').rglob('*'):
|
|
||||||
if file.suffix in ['.js', '.css', '.map']:
|
|
||||||
file.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('-m', '--manifest', action='store_true', help='Builds the manifest.json file for addon information in ./src')
|
|
||||||
parser.add_argument('-r', '--readme', action='store_true', help='Updates the README.md with the currently installed ')
|
|
||||||
parser.add_argument('-s', '--supported', action='store_true', help='Builds the SUPPORTED file with all supported domains in the current directory')
|
|
||||||
parser.add_argument('-b', '--build', action='store_true', help='Creates a ./build folder and builds all typescript / sass files')
|
|
||||||
parser.add_argument('-c', '--clean', action='store_true', help='Cleans the ./src folder from .js, .css and .map files')
|
|
||||||
|
|
||||||
parsed = parser.parse_args()
|
|
||||||
|
|
||||||
if parsed.manifest:
|
|
||||||
write_manifest()
|
|
||||||
if parsed.readme:
|
|
||||||
write_readme()
|
|
||||||
if parsed.supported:
|
|
||||||
write_supported()
|
|
||||||
if parsed.build:
|
|
||||||
copy_built()
|
|
||||||
if parsed.clean:
|
|
||||||
clean_build()
|
|
||||||
|
|
||||||
if not parsed.manifest and not parsed.supported and not parsed.build and not parsed.clean:
|
|
||||||
print('\n'.join(load_matches()))
|
|
40
eslint.config.mjs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default ts.config(
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs['flat/recommended'],
|
||||||
|
prettier,
|
||||||
|
...svelte.configs['flat/prettier'],
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'no-undef': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'.DS_Store',
|
||||||
|
'node_modules',
|
||||||
|
'dist',
|
||||||
|
'release',
|
||||||
|
'.idea',
|
||||||
|
'.env',
|
||||||
|
'.env.*',
|
||||||
|
'!.env.example',
|
||||||
|
'package-lock.json'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
9556
package-lock.json
generated
Normal file
51
package.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "stream-bypass",
|
||||||
|
"version": "3.1.6",
|
||||||
|
"displayName": "Stream Bypass",
|
||||||
|
"author": "bytedream",
|
||||||
|
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"watch": "vite build --watch --mode development --minify false",
|
||||||
|
"dev": "vite",
|
||||||
|
"serve:firefox": "web-ext run --start-url \"about:debugging#/runtime/this-firefox\" --source-dir ./dist/",
|
||||||
|
"serve:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
|
||||||
|
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/bytedream/stream-bypass.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/bytedream/stream-bypass/issues"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||||
|
"@samrum/vite-plugin-web-extension": "^5.1.1",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
|
"@types/chrome": "^0.0.320",
|
||||||
|
"@types/firefox-webext-browser": "^120.0.4",
|
||||||
|
"eslint": "^9.26.0",
|
||||||
|
"eslint-config-prettier": "^10.1.3",
|
||||||
|
"eslint-plugin-svelte": "^3.5.1",
|
||||||
|
"hls.js": "^1.6.2",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
|
"sass": "^1.87.0",
|
||||||
|
"svelte": "^5.28.2",
|
||||||
|
"svelte-check": "^4.1.7",
|
||||||
|
"svelte-preprocess": "^6.0.3",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"typescript-eslint": "^8.32.0",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"web-ext": "^8.6.0"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
|
}
|
1
public/icons/stream-bypass.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:#fff;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/stream-bypass@128px.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
public/icons/stream-bypass@16px.png
Normal file
After Width: | Height: | Size: 713 B |
BIN
public/icons/stream-bypass@32px.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/stream-bypass@48px.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
public/icons/stream-bypass@96px.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
1
public/icons/stream-bypass_disabled.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:gray;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/stream-bypass_disabled@128px.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
public/icons/stream-bypass_disabled@16px.png
Normal file
After Width: | Height: | Size: 792 B |
BIN
public/icons/stream-bypass_disabled@32px.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/icons/stream-bypass_disabled@48px.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
public/icons/stream-bypass_disabled@96px.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
39
src/entries/background/mv2.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import './shared';
|
||||||
|
|
||||||
|
import { getMatch, type Match } from '~/lib/match';
|
||||||
|
import { Redirect, UrlReferer } from '~/lib/settings';
|
||||||
|
|
||||||
|
chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
async (details) => {
|
||||||
|
const referer = await UrlReferer.get(new URL(details.url).hostname);
|
||||||
|
if (!referer) return;
|
||||||
|
|
||||||
|
await UrlReferer.delete(new URL(details.url).hostname);
|
||||||
|
|
||||||
|
details.requestHeaders!.push({
|
||||||
|
name: 'Referer',
|
||||||
|
value: `https://${referer}/`
|
||||||
|
});
|
||||||
|
|
||||||
|
return { requestHeaders: details.requestHeaders };
|
||||||
|
},
|
||||||
|
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] },
|
||||||
|
['blocking', 'requestHeaders']
|
||||||
|
);
|
||||||
|
|
||||||
|
chrome.webRequest.onBeforeRedirect.addListener(
|
||||||
|
async (details) => {
|
||||||
|
// check if redirects origins from a previous redirect
|
||||||
|
if ((await Redirect.get()) == null) {
|
||||||
|
let match: Match | null;
|
||||||
|
if ((match = await getMatch(new URL(details.url).hostname)) !== null) {
|
||||||
|
await Redirect.set(match);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await Redirect.delete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ urls: ['<all_urls>'], types: ['main_frame', 'sub_frame'] }
|
||||||
|
);
|
1
src/entries/background/mv3.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import './shared';
|
5
src/entries/background/shared.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
chrome.runtime.onMessage.addListener(async (message) => {
|
||||||
|
if (message.action == 'ff2mpv') {
|
||||||
|
await chrome.runtime.sendNativeMessage('ff2mpv', { url: message.url });
|
||||||
|
}
|
||||||
|
});
|
104
src/entries/contentScript/main.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { getMatch, MatchMediaType, type Match } from '~/lib/match';
|
||||||
|
import { Other, Redirect } from '~/lib/settings';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let match: Match | null;
|
||||||
|
let redirect = false;
|
||||||
|
if ((match = await getMatch(window.location.host)) === null) {
|
||||||
|
if ((match = await Redirect.get()) === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redirect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// some sites have a javascript based redirect, e.g. example.com redirects to example.org by changing
|
||||||
|
// window.location.href instead of a 3XX http redirect. an empty body is a sign that such a javascript redirect
|
||||||
|
// occurred
|
||||||
|
if (document.body == null) {
|
||||||
|
await Redirect.set(match);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let re = null;
|
||||||
|
for (const regex of match.regex) {
|
||||||
|
if ((re = document.body.innerHTML.match(regex)) !== null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (re === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
await Redirect.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: string | null;
|
||||||
|
let urlType: MatchMediaType | null;
|
||||||
|
try {
|
||||||
|
const matchResult = await match.match(re);
|
||||||
|
if (matchResult && typeof matchResult === 'string') {
|
||||||
|
url = matchResult;
|
||||||
|
urlType = url.includes('.m3u8') ? MatchMediaType.Hls : MatchMediaType.Native;
|
||||||
|
} else if (matchResult && typeof matchResult === 'object') {
|
||||||
|
if (MatchMediaType.Hls in matchResult) {
|
||||||
|
url = matchResult[MatchMediaType.Hls];
|
||||||
|
urlType = MatchMediaType.Hls;
|
||||||
|
} else if (MatchMediaType.Native in matchResult) {
|
||||||
|
url = matchResult[MatchMediaType.Native];
|
||||||
|
urlType = MatchMediaType.Native;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
if (!url || !urlType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application
|
||||||
|
if (await Other.getFf2mpv()) {
|
||||||
|
await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.replace && urlType != MatchMediaType.Hls) {
|
||||||
|
// this destroys all intervals that may spawn popups or events
|
||||||
|
let intervalId = window.setInterval(() => {}, 0);
|
||||||
|
while (intervalId--) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
let timeoutId = window.setTimeout(() => {}, 0);
|
||||||
|
while (timeoutId--) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear completed document
|
||||||
|
document.documentElement.innerHTML = '';
|
||||||
|
|
||||||
|
document.body.style.backgroundColor = '#131313';
|
||||||
|
|
||||||
|
// video player
|
||||||
|
const player = document.createElement('video');
|
||||||
|
player.style.width = '100%';
|
||||||
|
player.style.height = '100%';
|
||||||
|
player.controls = true;
|
||||||
|
player.src = url;
|
||||||
|
|
||||||
|
// add video player to document body
|
||||||
|
document.body.style.margin = '0';
|
||||||
|
document.body.append(player);
|
||||||
|
} else {
|
||||||
|
window.location.assign(
|
||||||
|
chrome.runtime.getURL(
|
||||||
|
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
|
||||||
|
window.location.hostname
|
||||||
|
}&type=${urlType}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
65
src/entries/player/Player.svelte
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { play } from '~/entries/player/player';
|
||||||
|
|
||||||
|
let errorMessage: string | null = $state(null);
|
||||||
|
|
||||||
|
let videoElem: HTMLVideoElement;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
await play(videoElem);
|
||||||
|
videoElem.controls = true;
|
||||||
|
} catch (e) {
|
||||||
|
errorMessage = e as string;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
|
<video id="video" bind:this={videoElem}></video>
|
||||||
|
{#if errorMessage}
|
||||||
|
<div id="message-container">
|
||||||
|
<p>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html errorMessage}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- eslint-disable -->
|
||||||
|
<style lang="scss" global>
|
||||||
|
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;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
& a,
|
||||||
|
& a:visited {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
17
src/entries/player/player.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Stream Bypass</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
import { mount } from 'svelte';
|
||||||
|
import Player from '~/entries/player/Player.svelte';
|
||||||
|
|
||||||
|
mount(Player, {
|
||||||
|
target: document.body
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
47
src/entries/player/player.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import Hls from 'hls.js';
|
||||||
|
import { matches, MatchMediaType } from '~/lib/match';
|
||||||
|
import { UrlReferer } from '~/lib/settings';
|
||||||
|
|
||||||
|
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
|
||||||
|
await UrlReferer.set(new URL(url).hostname, domain);
|
||||||
|
|
||||||
|
videoElem.src = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function playHls(url: string, domain: string, videoElem: HTMLVideoElement) {
|
||||||
|
if (videoElem.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
videoElem.src = url;
|
||||||
|
} else if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls({
|
||||||
|
enableWorker: false,
|
||||||
|
xhrSetup: async (xhr: XMLHttpRequest, url: string) => {
|
||||||
|
await UrlReferer.set(new URL(url).hostname, domain);
|
||||||
|
xhr.open('GET', url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(videoElem);
|
||||||
|
} else {
|
||||||
|
throw '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>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function play(videoElem: HTMLVideoElement) {
|
||||||
|
const urlQuery = new URLSearchParams(window.location.search);
|
||||||
|
const id = urlQuery.get('id') as string;
|
||||||
|
const url = decodeURIComponent(urlQuery.get('url') as string);
|
||||||
|
const domain = urlQuery.get('domain') as string;
|
||||||
|
const type = urlQuery.get('type') as MatchMediaType;
|
||||||
|
|
||||||
|
const match = matches[id];
|
||||||
|
if (match === undefined) {
|
||||||
|
throw `Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`;
|
||||||
|
}
|
||||||
|
document.title = `Stream Bypass (${domain})`;
|
||||||
|
|
||||||
|
if (type === MatchMediaType.Hls) {
|
||||||
|
await playHls(url, domain, videoElem);
|
||||||
|
} else if (type === MatchMediaType.Native) {
|
||||||
|
await playNative(url, domain, videoElem);
|
||||||
|
}
|
||||||
|
}
|
165
src/entries/popup/Popup.svelte
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { matches, type Match } from '~/lib/match';
|
||||||
|
import { Hosters, Other } from '~/lib/settings';
|
||||||
|
import Toggle from './toggle.svelte';
|
||||||
|
|
||||||
|
let hostersEnabled: boolean;
|
||||||
|
let hosters: (Match & { active: boolean; disabled: boolean })[] = [];
|
||||||
|
(async () => {
|
||||||
|
hostersEnabled = !(await Hosters.getAllDisabled());
|
||||||
|
|
||||||
|
const disabled = await Hosters.getDisabled();
|
||||||
|
hosters = Object.values(matches).map((m: any) => {
|
||||||
|
m['active'] = disabled.findIndex((p) => p.id == m.id) == -1;
|
||||||
|
return m;
|
||||||
|
}) as typeof hosters;
|
||||||
|
})();
|
||||||
|
|
||||||
|
let isMobile: boolean;
|
||||||
|
(async () => {
|
||||||
|
isMobile = (await browser.runtime.getPlatformInfo()).os === 'android';
|
||||||
|
})();
|
||||||
|
|
||||||
|
let ff2mpvEnabled: boolean;
|
||||||
|
(async () => {
|
||||||
|
ff2mpvEnabled = (await Other.getFf2mpv()) as boolean;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main
|
||||||
|
style={isMobile
|
||||||
|
? 'height: 100vh; display: flex; flex-direction: column; align-items: center'
|
||||||
|
: 'height: 500px'}
|
||||||
|
>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Hoster</legend>
|
||||||
|
<div class="setting-container" style={isMobile ? 'grid-column-gap: 5rem' : ''}>
|
||||||
|
<label for="hosters-enabled">Enabled</label>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
bind:checked={hostersEnabled}
|
||||||
|
id="hosters-enabled"
|
||||||
|
onChange={() => Hosters.setAll(hostersEnabled)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
{#each hosters as hoster, i (hoster.id)}
|
||||||
|
<label for="hoster-{i}" style="cursor: {hostersEnabled ? 'pointer' : 'default'}"
|
||||||
|
>{hoster.name}</label
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
bind:checked={hoster.active}
|
||||||
|
disabled={!hostersEnabled}
|
||||||
|
id="hoster-{i}"
|
||||||
|
onChange={async () => {
|
||||||
|
if (hoster.active) {
|
||||||
|
await Hosters.enable(hoster);
|
||||||
|
} else {
|
||||||
|
await Hosters.disable(hoster);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Toggle>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{#if !isMobile}
|
||||||
|
<fieldset>
|
||||||
|
<legend>Other</legend>
|
||||||
|
<div class="setting-container">
|
||||||
|
<label for="ff2mpv">ff2mpv</label>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
bind:checked={ff2mpvEnabled}
|
||||||
|
id="ff2mpv"
|
||||||
|
onChange={async () => {
|
||||||
|
ff2mpvEnabled = !ff2mpvEnabled;
|
||||||
|
if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) {
|
||||||
|
await Other.setFf2mpv(ff2mpvEnabled);
|
||||||
|
ff2mpvEnabled = !ff2mpvEnabled;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></Toggle>
|
||||||
|
<a
|
||||||
|
class="info-questionmark"
|
||||||
|
href="https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams"
|
||||||
|
>?</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{/if}
|
||||||
|
<a id="report-notice" href="https://github.com/ByteDream/stream-bypass/issues"
|
||||||
|
>Report issues or requests</a
|
||||||
|
>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- eslint-disable -->
|
||||||
|
<style lang="scss" global>
|
||||||
|
body {
|
||||||
|
background-color: #2b2a33;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border-radius: 5px;
|
||||||
|
border-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#report-notice {
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
font-weight: lighter;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-column-gap: 5px;
|
||||||
|
grid-row-gap: 4px;
|
||||||
|
align-items: end;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& > label {
|
||||||
|
height: 34px;
|
||||||
|
margin: 0;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > hr {
|
||||||
|
grid-column: 1 / span 2;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-questionmark {
|
||||||
|
display: inline-block;
|
||||||
|
transform: translateX(-40%) translateY(-100%);
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
18
src/entries/popup/index.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html style="overflow-y: hidden" lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Stream Bypass</title>
|
||||||
|
</head>
|
||||||
|
<body style="overflow-y: scroll">
|
||||||
|
<script type="module">
|
||||||
|
import { mount } from 'svelte';
|
||||||
|
import Popup from '~/entries/popup/Popup.svelte';
|
||||||
|
|
||||||
|
mount(Popup, {
|
||||||
|
target: document.body
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
68
src/entries/popup/toggle.svelte
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<!-- https://flowbite.com/docs/forms/toggle/ -->
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
checked?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
id?: string | null;
|
||||||
|
onChange?: () => void;
|
||||||
|
children?: Snippet;
|
||||||
|
};
|
||||||
|
let { checked = $bindable(), disabled, id = null, onChange, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="toggle">
|
||||||
|
{@render children?.()}
|
||||||
|
<input type="checkbox" {id} bind:checked {disabled} onchange={onChange} />
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style lang="scss" global>
|
||||||
|
.toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input {
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&:checked + span {
|
||||||
|
background: limegreen;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled + span {
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: relative;
|
||||||
|
width: 2.75rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: #cf0000;
|
||||||
|
border-radius: 9999px;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
inset-inline-start: 2px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 9999px;
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input:disabled) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Before Width: | Height: | Size: 16 KiB |
46
src/index.ts
@ -1,46 +0,0 @@
|
|||||||
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)
|
|
||||||
if (keys.indexOf('all') !== -1 && !result['all']) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
for (let match of matches) {
|
|
||||||
let domain = match[0] as string
|
|
||||||
if (window.location.href.indexOf(domain) !== -1) {
|
|
||||||
if (keys.indexOf('disabled') === -1 || result['disabled'].indexOf(domain) === -1) {
|
|
||||||
let regex = match[1] as RegExp
|
|
||||||
let matchClass = match[2] as Match
|
|
||||||
let reliability = match[3] as Reliability
|
|
||||||
|
|
||||||
let re
|
|
||||||
if (regex !== null) {
|
|
||||||
if ((re = document.body.innerHTML.match(regex)) === null) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
re = document.body.innerHTML.match(regex)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchClass === null) {
|
|
||||||
if (regex === null) {
|
|
||||||
location.assign(document.body.innerHTML)
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
location.assign(hasSuffix(re[0], 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${re[0]}`) : re[0])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matchClass.match(re).then(function (path) {
|
|
||||||
// @ts-ignore
|
|
||||||
location.assign(hasSuffix(path, 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${path}`) : path)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
441
src/lib/match.ts
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
import { Hosters, Redirect, TmpHost } from './settings';
|
||||||
|
import { lastPathSegment } from './util/extract';
|
||||||
|
import { unpack } from './util/userspace';
|
||||||
|
|
||||||
|
export interface Match {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
domains: string[];
|
||||||
|
replace?: boolean;
|
||||||
|
regex: RegExp[];
|
||||||
|
notice?: string;
|
||||||
|
|
||||||
|
match(
|
||||||
|
match: RegExpMatchArray
|
||||||
|
): Promise<
|
||||||
|
string | { [MatchMediaType.Hls]: string } | { [MatchMediaType.Native]: string } | null
|
||||||
|
>;
|
||||||
|
|
||||||
|
// allow other properties that may be implemented by the objects that use this interface declaration
|
||||||
|
[other: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MatchMediaType {
|
||||||
|
Hls = 'hls',
|
||||||
|
Native = 'native'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Doodstream: Match = {
|
||||||
|
name: 'Doodstream',
|
||||||
|
id: 'doodstream',
|
||||||
|
domains: [
|
||||||
|
'doodstream.com',
|
||||||
|
'dood.pm',
|
||||||
|
'dood.ws',
|
||||||
|
'dood.wf',
|
||||||
|
'dood.cx',
|
||||||
|
'dood.sh',
|
||||||
|
'dood.watch',
|
||||||
|
'dood.work',
|
||||||
|
'dood.to',
|
||||||
|
'dood.so',
|
||||||
|
'dood.la',
|
||||||
|
'dood.li',
|
||||||
|
'dood.re',
|
||||||
|
'dood.yt',
|
||||||
|
'doods.pro',
|
||||||
|
'ds2play.com',
|
||||||
|
'dooood.com',
|
||||||
|
'd000d.com'
|
||||||
|
],
|
||||||
|
replace: true,
|
||||||
|
regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
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()}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DropLoad: Match = {
|
||||||
|
name: 'Dropload',
|
||||||
|
id: 'dropload',
|
||||||
|
domains: ['dropload.io'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Filemoon: Match = {
|
||||||
|
name: 'Filemoon',
|
||||||
|
id: 'filemoon',
|
||||||
|
domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'],
|
||||||
|
regex: [/(?<=<iframe\s*src=")\S*(?=")/s, /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
replace: true,
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
if (window.location.host.startsWith('filemoon')) {
|
||||||
|
await TmpHost.set(new URL(match[0]).host, Filemoon);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await TmpHost.delete();
|
||||||
|
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=file:")\S*(?=")/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GoodStream: Match = {
|
||||||
|
name: 'Goodstream',
|
||||||
|
id: 'goodstream',
|
||||||
|
domains: ['goodstream.uno'],
|
||||||
|
regex: [/(?<=file:\s+").*(?=")/g],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Kwik: Match = {
|
||||||
|
name: 'Kwik',
|
||||||
|
id: 'kwik',
|
||||||
|
domains: ['kwik.cx'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=source=').*(?=')/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadX: Match = {
|
||||||
|
name: 'LoadX',
|
||||||
|
id: 'loadx',
|
||||||
|
domains: ['loadx.ws'],
|
||||||
|
regex: [/./gm],
|
||||||
|
|
||||||
|
match: async () => {
|
||||||
|
const hash = encodeURIComponent(lastPathSegment(window.location.href));
|
||||||
|
const response = await fetch(
|
||||||
|
`https://${window.location.host}/player/index.php?data=${hash}&do=getVideo`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseJson = await response.json();
|
||||||
|
const videoSource: string = responseJson['videoSource'];
|
||||||
|
|
||||||
|
// extension of extracted url is '.txt', so we have to manually specify that it's a hls
|
||||||
|
return { [MatchMediaType.Hls]: videoSource.replace('\\/', '/') };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Luluvdo: Match = {
|
||||||
|
name: 'Luluvdo',
|
||||||
|
id: 'luluvdo',
|
||||||
|
domains: ['luluvdo.com'],
|
||||||
|
regex: [/./gm],
|
||||||
|
|
||||||
|
match: async () => {
|
||||||
|
const requestBody = new FormData();
|
||||||
|
requestBody.set('op', 'embed');
|
||||||
|
requestBody.set('file_code', lastPathSegment(window.location.href));
|
||||||
|
const response = await fetch(`https://${window.location.host}/dl`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: requestBody,
|
||||||
|
referrer: window.location.href
|
||||||
|
});
|
||||||
|
|
||||||
|
let unpacked;
|
||||||
|
|
||||||
|
const responseText = await response.text();
|
||||||
|
const evalMatch = responseText.match(/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms)!;
|
||||||
|
// sometimes is packed, sometimes it's not. looks like someone forgets to obfuscate the code when pushing to
|
||||||
|
// production
|
||||||
|
if (evalMatch) {
|
||||||
|
unpacked = await unpack(evalMatch[0]);
|
||||||
|
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||||
|
} else {
|
||||||
|
unpacked = responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mixdrop: Match = {
|
||||||
|
name: 'Mixdrop',
|
||||||
|
id: 'mixdrop',
|
||||||
|
domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
|
||||||
|
return `https:${url}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mp4Upload: Match = {
|
||||||
|
name: 'Mp4Upload',
|
||||||
|
id: 'mp4upload',
|
||||||
|
domains: ['mp4upload.com'],
|
||||||
|
replace: true,
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Newgrounds: Match = {
|
||||||
|
name: 'Newgrounds',
|
||||||
|
id: 'newgrounds',
|
||||||
|
domains: ['newgrounds.com'],
|
||||||
|
regex: [/.*/gm],
|
||||||
|
|
||||||
|
match: async () => {
|
||||||
|
const id = window.location.pathname.split('/').slice(-1)[0];
|
||||||
|
const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const json = await response.json();
|
||||||
|
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StreamA2z: Match = {
|
||||||
|
name: 'Stream2Az',
|
||||||
|
id: 'stream2az',
|
||||||
|
domains: ['streama2z.com', 'streama2z.xyz'],
|
||||||
|
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
|
||||||
|
await Redirect.set(StreamA2z);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Streamtape: Match = {
|
||||||
|
name: 'Streamtape',
|
||||||
|
id: 'streamtape',
|
||||||
|
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
|
||||||
|
regex: [/id=.*(?=')/gm],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
let i = 0;
|
||||||
|
while (i < match.length) {
|
||||||
|
if (match[++i - 1] == match[i]) {
|
||||||
|
return `https://streamtape.com/get_video?${match[i]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the old method as fallback
|
||||||
|
return `https://streamtape.com/get_video?${match.reverse()[0]}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Streamzz: Match = {
|
||||||
|
name: 'Streamzz',
|
||||||
|
id: 'streamzz',
|
||||||
|
domains: ['streamzz.to', 'streamz.ws'],
|
||||||
|
regex: [/(?<=\|)\w{2,}/gm],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
|
||||||
|
match.sort((a, b) => b.length - a.length)[0]
|
||||||
|
}.dll`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SuperVideo: Match = {
|
||||||
|
name: 'Supervideo',
|
||||||
|
id: 'supervideo',
|
||||||
|
domains: ['supervideo.cc', 'supervideo.tv'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Upstream: Match = {
|
||||||
|
name: 'Upstream',
|
||||||
|
id: 'upstream',
|
||||||
|
domains: ['upstream.to'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Vidmoly: Match = {
|
||||||
|
name: 'Vidmoly',
|
||||||
|
id: 'vidmoly',
|
||||||
|
domains: ['vidmoly.me', 'vidmoly.to'],
|
||||||
|
regex: [/(?<=file:").+\.m3u8/gm],
|
||||||
|
replace: true,
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Vidoza: Match = {
|
||||||
|
name: 'Vidoza',
|
||||||
|
id: 'vidoza',
|
||||||
|
domains: ['vidoza.net', 'videzz.net'],
|
||||||
|
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||||
|
replace: true,
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Voe: Match = {
|
||||||
|
name: 'Voe',
|
||||||
|
id: 'voe',
|
||||||
|
domains: ['voe.sx'],
|
||||||
|
regex: [
|
||||||
|
// voe.sx
|
||||||
|
/(?<=window\.location\.href\s=\s')\S*(?=')/gm,
|
||||||
|
// whatever site voe.sx redirects to
|
||||||
|
/(?<=<script type="application\/json">).*(?=<\/script>)/m
|
||||||
|
],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
if (window.location.host === 'voe.sx') {
|
||||||
|
const redirectUrl = new URL(match[0]);
|
||||||
|
await TmpHost.set(redirectUrl.host, Voe);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
let json = match[0];
|
||||||
|
json = JSON.parse(json);
|
||||||
|
|
||||||
|
let deobfuscated = json[0];
|
||||||
|
deobfuscated = this.rot13(deobfuscated);
|
||||||
|
deobfuscated = this.removeSpecialSequences(deobfuscated);
|
||||||
|
deobfuscated = atob(deobfuscated);
|
||||||
|
deobfuscated = this.shiftString(deobfuscated);
|
||||||
|
deobfuscated = deobfuscated.split('').reverse().join('');
|
||||||
|
deobfuscated = atob(deobfuscated);
|
||||||
|
|
||||||
|
const payload = JSON.parse(deobfuscated);
|
||||||
|
|
||||||
|
return payload['source'];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rot13: function (encrypted: string) {
|
||||||
|
let decrypted = '';
|
||||||
|
for (let i = 0; i < encrypted.length; i++) {
|
||||||
|
let char = encrypted.charCodeAt(i);
|
||||||
|
if (char >= 65 && char <= 90) {
|
||||||
|
char = ((char - 65 + 13) % 26) + 65;
|
||||||
|
} else if (char >= 97 && char <= 122) {
|
||||||
|
char = ((char - 97 + 13) % 26) + 97;
|
||||||
|
}
|
||||||
|
decrypted += String.fromCharCode(char);
|
||||||
|
}
|
||||||
|
return decrypted;
|
||||||
|
},
|
||||||
|
removeSpecialSequences: function (input: string) {
|
||||||
|
return input
|
||||||
|
.replaceAll(/@\$/g, '')
|
||||||
|
.replaceAll(/\^\^/g, '')
|
||||||
|
.replaceAll(/~@/g, '')
|
||||||
|
.replaceAll(/%\?/g, '')
|
||||||
|
.replaceAll(/\*~/g, '')
|
||||||
|
.replaceAll(/!!/g, '')
|
||||||
|
.replaceAll(/#&/g, '');
|
||||||
|
},
|
||||||
|
shiftString: function (input: string) {
|
||||||
|
let shifted = '';
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const char = input.charCodeAt(i);
|
||||||
|
const shiftedChar = char - 3;
|
||||||
|
shifted += String.fromCharCode(shiftedChar);
|
||||||
|
}
|
||||||
|
return shifted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Vupload: Match = {
|
||||||
|
name: 'Vupload',
|
||||||
|
id: 'vupload',
|
||||||
|
domains: ['vupload.com'],
|
||||||
|
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||||
|
|
||||||
|
match: async function (match: RegExpMatchArray) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const matches = {
|
||||||
|
[Doodstream.id]: Doodstream,
|
||||||
|
[DropLoad.id]: DropLoad,
|
||||||
|
[Filemoon.id]: Filemoon,
|
||||||
|
[GoodStream.id]: GoodStream,
|
||||||
|
[Kwik.id]: Kwik,
|
||||||
|
[LoadX.id]: LoadX,
|
||||||
|
[Luluvdo.id]: Luluvdo,
|
||||||
|
[Mixdrop.id]: Mixdrop,
|
||||||
|
[Mp4Upload.id]: Mp4Upload,
|
||||||
|
[Newgrounds.id]: Newgrounds,
|
||||||
|
[StreamA2z.id]: StreamA2z,
|
||||||
|
[Streamtape.id]: Streamtape,
|
||||||
|
[Streamzz.id]: Streamzz,
|
||||||
|
[SuperVideo.id]: SuperVideo,
|
||||||
|
[Upstream.id]: Upstream,
|
||||||
|
[Vidmoly.id]: Vidmoly,
|
||||||
|
[Vidoza.id]: Vidoza,
|
||||||
|
[Voe.id]: Voe,
|
||||||
|
[Vupload.id]: Vupload
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getMatch(domain: string): Promise<Match | null> {
|
||||||
|
if (await Hosters.getAllDisabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const match of Object.values(matches)) {
|
||||||
|
if (
|
||||||
|
match.domains.indexOf(domain) !== -1 &&
|
||||||
|
!(await Hosters.getDisabled().then((d) => d.find((p) => p.id == match.id)))
|
||||||
|
) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmpHost = await TmpHost.get();
|
||||||
|
if (tmpHost && tmpHost[0] === domain) {
|
||||||
|
return tmpHost[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
112
src/lib/settings.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { matches, type Match } from './match';
|
||||||
|
|
||||||
|
export const Hosters = {
|
||||||
|
getDisabled: async () => {
|
||||||
|
const disabled = (await storageGet('hosters.disabled', [])) as string[];
|
||||||
|
return disabled.map((id) => matches[id]).filter((m) => m !== undefined);
|
||||||
|
},
|
||||||
|
disable: async (match: Match) => {
|
||||||
|
const disabled = (await storageGet('hosters.disabled', [])) as string[];
|
||||||
|
const index = disabled.indexOf(match.id);
|
||||||
|
if (index === -1) {
|
||||||
|
disabled.push(match.id);
|
||||||
|
await storageSet('hosters.disabled', disabled);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enable: async (match: Match) => {
|
||||||
|
const disabled = (await storageGet('hosters.disabled', [])) as string[];
|
||||||
|
const index = disabled.indexOf(match.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
disabled.splice(index, 1);
|
||||||
|
await storageSet('hosters.disabled', disabled);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAllDisabled: async () => {
|
||||||
|
return await storageGet<boolean>('hosters.allDisabled', false);
|
||||||
|
},
|
||||||
|
setAll: async (enable: boolean) => {
|
||||||
|
await storageSet('hosters.allDisabled', !enable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Redirect = {
|
||||||
|
get: async (): Promise<Match | null> => {
|
||||||
|
return matches[(await storageGet('redirect')) as string] || null;
|
||||||
|
},
|
||||||
|
set: async (match: Match) => {
|
||||||
|
await storageSet('redirect', match.id);
|
||||||
|
},
|
||||||
|
delete: async () => {
|
||||||
|
await storageDelete('redirect');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TmpHost = {
|
||||||
|
get: async (): Promise<[string, Match] | null> => {
|
||||||
|
const tmphost = await storageGet<[string, number]>('tmphost');
|
||||||
|
if (tmphost === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [tmphost[0], matches[tmphost[1]]];
|
||||||
|
},
|
||||||
|
set: async (domain: string, match: Match) => {
|
||||||
|
await storageSet('tmphost', [domain, match.id]);
|
||||||
|
},
|
||||||
|
delete: async () => {
|
||||||
|
await storageDelete('tmphost');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UrlReferer = {
|
||||||
|
get: async (url: string): Promise<string | null> => {
|
||||||
|
return (await storageGet(`urlReferer.${url}`)) || null;
|
||||||
|
},
|
||||||
|
set: async (url: string, referer: string) => {
|
||||||
|
await storageSet(`urlReferer.${url}`, referer);
|
||||||
|
},
|
||||||
|
delete: async (url: string) => {
|
||||||
|
await storageDelete(`urlReferer.${url}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Other = {
|
||||||
|
getFf2mpv: async () => {
|
||||||
|
return await storageGet('other.ff2mpv', false);
|
||||||
|
},
|
||||||
|
setFf2mpv: async (enable: boolean) => {
|
||||||
|
await storageSet('other.ff2mpv', enable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
|
||||||
|
let resolve: (value: T | undefined) => void;
|
||||||
|
const promise = new Promise<T | undefined>((r) => (resolve = r));
|
||||||
|
|
||||||
|
chrome.storage.local.get(key, (entry) => {
|
||||||
|
const value = entry[key];
|
||||||
|
resolve(value === undefined ? defaultValue : value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function storageSet<T>(key: string, value: T) {
|
||||||
|
let resolve: () => void;
|
||||||
|
const promise = new Promise<void>((r) => (resolve = r));
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
chrome.storage.local.set(obj, () => resolve());
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function storageDelete(key: string) {
|
||||||
|
let resolve: () => void;
|
||||||
|
const promise = new Promise<void>((r) => (resolve = r));
|
||||||
|
|
||||||
|
chrome.storage.local.remove(key, () => resolve());
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
6
src/lib/util/extract.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function lastPathSegment(path: string): string {
|
||||||
|
while (path.endsWith('/')) {
|
||||||
|
path = path.slice(0, -1);
|
||||||
|
}
|
||||||
|
return path.substring(path.lastIndexOf('/') + 1);
|
||||||
|
}
|
92
src/lib/util/userspace.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
|
||||||
|
export async function unpack(packed: string): Promise<string> {
|
||||||
|
const context = `
|
||||||
|
{
|
||||||
|
eval: function (c) {
|
||||||
|
packed = c;
|
||||||
|
},
|
||||||
|
window: {},
|
||||||
|
document: {}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const toExecute = `
|
||||||
|
function() {
|
||||||
|
let packed = "";
|
||||||
|
with(${context}) {
|
||||||
|
${packed}
|
||||||
|
};
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const res = (await runInPageContext(toExecute)) as string;
|
||||||
|
return res
|
||||||
|
.replace(/;/g, ';\n')
|
||||||
|
.replace(/{/g, '\n{\n')
|
||||||
|
.replace(/}/g, '\n}\n')
|
||||||
|
.replace(/\n;\n/g, ';\n')
|
||||||
|
.replace(/\n\\n/g, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from: https://github.com/arikw/extension-page-context
|
||||||
|
async function runInPageContext<T>(toExecute: string): Promise<T | null> {
|
||||||
|
// test that we are running with the allow-scripts permission
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
window.sessionStorage;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returned value container
|
||||||
|
const resultMessageId = crypto.randomUUID();
|
||||||
|
|
||||||
|
// prepare script container
|
||||||
|
const scriptElm = document.createElement('script');
|
||||||
|
scriptElm.setAttribute('type', 'application/javascript');
|
||||||
|
|
||||||
|
// inject the script
|
||||||
|
scriptElm.textContent = `
|
||||||
|
(
|
||||||
|
async function () {
|
||||||
|
const response = {
|
||||||
|
id: '${resultMessageId}'
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
response.result = JSON.stringify(await (${toExecute})() ); // run script
|
||||||
|
} catch(err) {
|
||||||
|
response.error = JSON.stringify(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.postMessage(response, '*');
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
`;
|
||||||
|
|
||||||
|
// run the script
|
||||||
|
document.documentElement.appendChild(scriptElm);
|
||||||
|
|
||||||
|
// clean up script element
|
||||||
|
scriptElm.remove();
|
||||||
|
|
||||||
|
let resolve: (value: T) => void;
|
||||||
|
let reject: (value: any) => void;
|
||||||
|
const promise = new Promise<T>((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
function onResult(event: MessageEvent) {
|
||||||
|
if (event.data.id === resultMessageId) {
|
||||||
|
window.removeEventListener('message', onResult);
|
||||||
|
if (event.data.error !== undefined) {
|
||||||
|
return reject(JSON.parse(event.data.error));
|
||||||
|
}
|
||||||
|
return resolve(event.data.result !== undefined ? JSON.parse(event.data.result) : undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', onResult);
|
||||||
|
|
||||||
|
return await promise;
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"manifest_version": 2,
|
|
||||||
"name": "Stream Bypass",
|
|
||||||
"author": "ByteDream",
|
|
||||||
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
|
||||||
"version": "1.5.1",
|
|
||||||
"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/*",
|
|
||||||
"*://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"
|
|
||||||
],
|
|
||||||
"browser_action": {
|
|
||||||
"default_icon": "icons/stream-bypass.png",
|
|
||||||
"default_title": "Stream Bypass",
|
|
||||||
"default_popup": "popup/popup.html"
|
|
||||||
},
|
|
||||||
"icons": {
|
|
||||||
"128": "icons/stream-bypass.png"
|
|
||||||
}
|
|
||||||
}
|
|
87
src/manifest.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import pkg from '../package.json';
|
||||||
|
import { matches } from './lib/match';
|
||||||
|
|
||||||
|
const sharedManifest: Partial<chrome.runtime.ManifestBase> = {
|
||||||
|
browser_specific_settings: {
|
||||||
|
gecko: {
|
||||||
|
id: '{55dd42e8-3dd9-455a-b4fe-86664881b10c}'
|
||||||
|
},
|
||||||
|
gecko_android: {}
|
||||||
|
},
|
||||||
|
content_scripts: [
|
||||||
|
{
|
||||||
|
all_frames: true,
|
||||||
|
matches: Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`)),
|
||||||
|
js: ['src/entries/contentScript/main.ts'],
|
||||||
|
run_at: 'document_end'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
icons: {
|
||||||
|
16: 'icons/stream-bypass@16px.png',
|
||||||
|
32: 'icons/stream-bypass@32px.png',
|
||||||
|
48: 'icons/stream-bypass@48px.png',
|
||||||
|
96: 'icons/stream-bypass@96px.png',
|
||||||
|
128: 'icons/stream-bypass@128px.png'
|
||||||
|
},
|
||||||
|
permissions: ['storage'],
|
||||||
|
optional_permissions: ['nativeMessaging']
|
||||||
|
};
|
||||||
|
|
||||||
|
const browserAction = {
|
||||||
|
default_icon: {
|
||||||
|
16: 'icons/stream-bypass@16px.png',
|
||||||
|
32: 'icons/stream-bypass@32px.png'
|
||||||
|
},
|
||||||
|
default_popup: 'src/entries/popup/index.html'
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManifestV2 = {
|
||||||
|
...sharedManifest,
|
||||||
|
background: {
|
||||||
|
scripts: ['src/entries/background/mv2.ts'],
|
||||||
|
persistent: true
|
||||||
|
},
|
||||||
|
content_scripts: [{ ...sharedManifest.content_scripts![0], matches: ['<all_urls>'] }],
|
||||||
|
browser_action: browserAction,
|
||||||
|
permissions: [...sharedManifest.permissions, 'webRequest', 'webRequestBlocking', '<all_urls>']
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManifestV3 = {
|
||||||
|
...sharedManifest,
|
||||||
|
action: browserAction,
|
||||||
|
background: {
|
||||||
|
service_worker: 'src/entries/background/mv3.ts'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getManifest(
|
||||||
|
manifestVersion: number
|
||||||
|
): chrome.runtime.ManifestV2 | chrome.runtime.ManifestV3 {
|
||||||
|
const manifest = {
|
||||||
|
author: pkg.author,
|
||||||
|
description: pkg.description,
|
||||||
|
name: pkg.displayName ?? pkg.name,
|
||||||
|
version: pkg.version
|
||||||
|
};
|
||||||
|
|
||||||
|
if (manifestVersion === 2) {
|
||||||
|
return {
|
||||||
|
...manifest,
|
||||||
|
...ManifestV2,
|
||||||
|
manifest_version: manifestVersion
|
||||||
|
} as chrome.runtime.ManifestV2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifestVersion === 3) {
|
||||||
|
return {
|
||||||
|
...manifest,
|
||||||
|
// just like all the adblockers which are unable to fully work under MV3, we need access to every website
|
||||||
|
// the user enters in order to work correctly, which is forbidden when using MV3
|
||||||
|
name: `${manifest.name} Lite`,
|
||||||
|
...ManifestV3,
|
||||||
|
manifest_version: manifestVersion
|
||||||
|
} as chrome.runtime.ManifestV3;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Missing manifest definition for manifestVersion ${manifestVersion}`);
|
||||||
|
}
|
125
src/match.ts
@ -1,125 +0,0 @@
|
|||||||
enum Reliability {
|
|
||||||
LOW = 1,
|
|
||||||
NORMAL,
|
|
||||||
HIGH
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Match {
|
|
||||||
match(match: RegExpMatchArray): Promise<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
class Evoload implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
|
||||||
const response = await fetch('https://evoload.io/SecurePlayer', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({code: code})
|
|
||||||
})
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
return json['stream']['src']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MCloud implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
|
||||||
const response = await fetch(`https://mcloud.to/info/${code}?skey=${match[0]}`, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
referrer: `https://mcloud.to/embed/${code}`
|
|
||||||
})
|
|
||||||
const json = await response.json()
|
|
||||||
return json['media']['sources'][0]['file']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Mixdrop implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Streamtape implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://streamtape.com/get_video?${match[0]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TheVideoMe implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://thevideome.com/${match[5]}.mp4`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Upstream implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://${match[47]}.upstreamcdn.co/hls/${match.sort((a, b) => {return b.length - a.length})[0]}/master.m3u8`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vidstream implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
|
||||||
const response = await fetch(`https://vidstream.pro/info/${code}?skey=${match[0]}`, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
referrer: `https://vidstream.pro/embed/${code}`
|
|
||||||
})
|
|
||||||
const json = await response.json()
|
|
||||||
return json['media']['sources'][0]['file']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vivo implements Match {
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return this.rot47(decodeURIComponent(match[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
|
|
||||||
rot47(encoded: string): string {
|
|
||||||
const s = []
|
|
||||||
for(let i = 0; i < encoded.length; i++) {
|
|
||||||
const j = encoded.charCodeAt(i)
|
|
||||||
if((j >= 33) && (j <= 126)) {
|
|
||||||
s[i] = String.fromCharCode(33+((j+ 14)%94))
|
|
||||||
} else {
|
|
||||||
s[i] = String.fromCharCode(j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.join('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all domains to match. the matches must be structured like this:
|
|
||||||
// [domain, regex match (can be null), class to call after match (can be null), reliability]
|
|
||||||
// => the domain which should be redirected
|
|
||||||
// => the regex gets called if the user visits a site with the given domain and matches the websites document body.
|
|
||||||
// if the regex is null, the complete document body gets handled as one big regex match
|
|
||||||
// => the class to call when the regex was parsed successfully. the class has to implement the `Match` interface.
|
|
||||||
// if the class is null, the user gets redirected to the first regex match element
|
|
||||||
// => the reliability shows how reliable a stream redirect is. for example, vivo.sx works nearly every time whereas
|
|
||||||
// upstream.to works only sometimes because of a security mechanism they're using (CORS) which currently can't be bypassed
|
|
||||||
//
|
|
||||||
// every match HAS to be on an separate line (for automatically manifest generation)
|
|
||||||
const matches = [
|
|
||||||
['evoload.io', null, new Evoload(), Reliability.NORMAL],
|
|
||||||
['mcloud.to', new RegExp(/(?<=')\w+(?=';)/gm), new MCloud(), Reliability.NORMAL],
|
|
||||||
['mixdrop.co', new RegExp(/(?<=\|)\w{2,}/gm), new Mixdrop(), Reliability.HIGH],
|
|
||||||
['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape(), Reliability.NORMAL],
|
|
||||||
['streamzz.to', new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm), null, Reliability.NORMAL],
|
|
||||||
['thevideome.com', new RegExp(/(?<=\|)\w{2,}/gm), new TheVideoMe(), Reliability.NORMAL],
|
|
||||||
//['upstream.to', new RegExp(/(?<=\|)\w{2,}/gm), new Upstream(), Reliability.LOW],
|
|
||||||
['vidlox.me', new RegExp(/(?<=\[")\S+?(?=")/gm), null, Reliability.NORMAL],
|
|
||||||
['vidstream.pro', new RegExp(/(?<=')\w+(?=';)/gm), new Vidstream(), Reliability.LOW],
|
|
||||||
['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null, Reliability.NORMAL],
|
|
||||||
['vivo.st', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
|
|
||||||
['vivo.sx', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
|
|
||||||
['voe.sx', new RegExp(/https?:\/\/\S*m3u8(?=")/gm), null, Reliability.HIGH],
|
|
||||||
['vupload.com', new RegExp(/(?<=src:\s?").+?(?=")/gm), null, Reliability.NORMAL]
|
|
||||||
]
|
|
@ -1,26 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Title</title>
|
|
||||||
<link rel="stylesheet" href="popup.css">
|
|
||||||
<script src="../ext/popper.min.js"></script>
|
|
||||||
<script src="../ext/tippy-bundle.umd.min.js"></script>
|
|
||||||
<script src="../match.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container">
|
|
||||||
<div id="all">
|
|
||||||
<div class="buttons">
|
|
||||||
<a>On</a>
|
|
||||||
<a>Off</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<table id="sub-container">
|
|
||||||
</table>
|
|
||||||
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues/new">Something does not work as expected</a>
|
|
||||||
</div>
|
|
||||||
<script src="./popup.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,55 +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
|
|
||||||
|
|
||||||
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
|
|
@ -1,163 +0,0 @@
|
|||||||
function enableAll(enable: boolean) {
|
|
||||||
// @ts-ignore
|
|
||||||
chrome.storage.local.set({'all': enable})
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
for (let button of document.getElementById('sub-container').getElementsByTagName('a')) {
|
|
||||||
enable ? button.classList.remove('disabled') : button.classList.add('disabled')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableOne(website: string, enable: boolean) {
|
|
||||||
// @ts-ignore
|
|
||||||
chrome.storage.local.get(['disabled'], function (result) {
|
|
||||||
let disabled: string[] = Object.keys(result).length === 0 ? [] : result['disabled']
|
|
||||||
if (enable && disabled.indexOf(website) !== -1) {
|
|
||||||
disabled.splice(disabled.indexOf(website), 1)
|
|
||||||
} else if (!enable && disabled.indexOf(website) === -1) {
|
|
||||||
disabled.push(website)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
chrome.storage.local.set({'disabled': disabled})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
chrome.storage.local.get(['all', 'disabled'], function (result) {
|
|
||||||
let allDisabled = result['all'] !== undefined && !result['all']
|
|
||||||
let disabled = new Map()
|
|
||||||
|
|
||||||
if (allDisabled) {
|
|
||||||
// @ts-ignore
|
|
||||||
for (let match of matches) {
|
|
||||||
disabled.set(match[0], false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Object.keys(result).indexOf('disabled') !== -1) {
|
|
||||||
for (let disable of result['disabled']) {
|
|
||||||
disabled.set(disable, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let subContainer = document.getElementById('sub-container')
|
|
||||||
// @ts-ignore
|
|
||||||
for (let match of matches) {
|
|
||||||
let row = document.createElement('tr')
|
|
||||||
|
|
||||||
let name = document.createElement('td')
|
|
||||||
let nameValue = document.createElement('p')
|
|
||||||
nameValue.innerText = match[0]
|
|
||||||
switch (match[3]) {
|
|
||||||
case 1: // low
|
|
||||||
nameValue.classList.add('low-reliability')
|
|
||||||
// @ts-ignore
|
|
||||||
tippy(nameValue, {
|
|
||||||
content: 'Low reliability: Errors may occur often'
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 2: // normal
|
|
||||||
nameValue.classList.add('normal-reliability')
|
|
||||||
// @ts-ignore
|
|
||||||
tippy(nameValue, {
|
|
||||||
content: 'Normal reliability: Save to use but errors may occur'
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 3: //high
|
|
||||||
nameValue.classList.add('high-reliability')
|
|
||||||
// @ts-ignore
|
|
||||||
tippy(nameValue, {
|
|
||||||
content: 'High reliability: Errors are very unlikely to happen'
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let buttons = document.createElement('td')
|
|
||||||
buttons.classList.add('buttons')
|
|
||||||
let on = document.createElement('a')
|
|
||||||
on.innerText = 'On'
|
|
||||||
// @ts-ignore
|
|
||||||
let onTippy = tippy(on, {
|
|
||||||
content: `Enable ${match[0]}`,
|
|
||||||
onMount: () => {
|
|
||||||
if (on.classList.contains('active') || off.classList.contains('disabled')) {
|
|
||||||
onTippy.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let off = document.createElement('a')
|
|
||||||
off.innerText = 'Off'
|
|
||||||
// @ts-ignore
|
|
||||||
let offTippy = tippy(off, {
|
|
||||||
content: `Disable ${match[0]}`,
|
|
||||||
onMount: () => {
|
|
||||||
if (off.classList.contains('active') || off.classList.contains('disabled')) {
|
|
||||||
offTippy.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
disabled.has(match[0]) ? off.classList.add('active') : on.classList.add('active')
|
|
||||||
if (allDisabled) {
|
|
||||||
on.classList.add('disabled')
|
|
||||||
off.classList.add('disabled')
|
|
||||||
}
|
|
||||||
|
|
||||||
on.onclick = function () {
|
|
||||||
if (!on.classList.contains('disabled')) {
|
|
||||||
enableOne(match[0], true)
|
|
||||||
on.classList.add('active')
|
|
||||||
off.classList.remove('active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
off.onclick = function () {
|
|
||||||
if (!off.classList.contains('disabled')) {
|
|
||||||
enableOne(match[0], false)
|
|
||||||
on.classList.remove('active')
|
|
||||||
off.classList.add('active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name.append(nameValue)
|
|
||||||
buttons.append(on, off)
|
|
||||||
row.append(name, buttons)
|
|
||||||
subContainer.append(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
let allButtons = document.getElementById('all').getElementsByTagName('a')
|
|
||||||
let allOn = allButtons[0]
|
|
||||||
allButtons[0].onclick = function () {
|
|
||||||
if (!allButtons[0].classList.contains('disabled')) {
|
|
||||||
enableAll(true)
|
|
||||||
allButtons[0].classList.add('active')
|
|
||||||
allButtons[1].classList.remove('active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
let allOnTippy = tippy(allOn, {
|
|
||||||
content: 'Enable all websites',
|
|
||||||
onMount: () => {
|
|
||||||
if (allButtons[0].classList.contains('active')) {
|
|
||||||
allOnTippy.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
allButtons[1].onclick = function () {
|
|
||||||
if (!allButtons[1].classList.contains('disabled')) {
|
|
||||||
enableAll(false)
|
|
||||||
allButtons[0].classList.remove('active')
|
|
||||||
allButtons[1].classList.add('active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
let allOffTippy = tippy(allButtons[1], {
|
|
||||||
content: 'Disable all websites',
|
|
||||||
onMount: () => {
|
|
||||||
if (allButtons[1].classList.contains('active')) {
|
|
||||||
allOffTippy.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
allDisabled ? allButtons[1].classList.add('active') : allButtons[0].classList.add('active')
|
|
||||||
})
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>m3u8</title>
|
|
||||||
<link rel="stylesheet" href="/res/hls.css">
|
|
||||||
<script src="/ext/hls.light.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<video id="video"></video>
|
|
||||||
<p id="message" hidden></p>
|
|
||||||
<script src="/res/hls.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,16 +0,0 @@
|
|||||||
body
|
|
||||||
background-color: #131313
|
|
||||||
|
|
||||||
html, body, video
|
|
||||||
width: 100%
|
|
||||||
height: 100%
|
|
||||||
margin: 0
|
|
||||||
|
|
||||||
video
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
left: 0
|
|
||||||
|
|
||||||
#message
|
|
||||||
color: white
|
|
||||||
text-align: center
|
|
@ -1,62 +0,0 @@
|
|||||||
function showMessage(message: string) {
|
|
||||||
let messageElement = document.getElementById('message') as HTMLParagraphElement
|
|
||||||
messageElement.innerText = message
|
|
||||||
messageElement.hidden = false
|
|
||||||
document.getElementById('video').hidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadHls() {
|
|
||||||
let url = window.location.hash.substring(1)
|
|
||||||
let video = document.getElementById('video') as HTMLVideoElement;
|
|
||||||
|
|
||||||
video.controls = true
|
|
||||||
if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
||||||
video.src = url
|
|
||||||
// @ts-ignore
|
|
||||||
} else if (Hls.isSupported()) {
|
|
||||||
// @ts-ignore
|
|
||||||
let hls = new Hls()
|
|
||||||
hls.loadSource(url)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
|
|
||||||
let searchParams = new URLSearchParams(window.location.search)
|
|
||||||
let rawReliability = parseInt(searchParams.get('reliability'))
|
|
||||||
|
|
||||||
let thirdPartyFallback = setTimeout(() => {
|
|
||||||
let message: string
|
|
||||||
|
|
||||||
switch (rawReliability) {
|
|
||||||
case 1: // low
|
|
||||||
message = `The reliability for this domain is low, errors like this are common.
|
|
||||||
Try to choose another streaming provider (if existent) or deactivate the addon for this domain (${searchParams.get('domain')}) and try again`
|
|
||||||
break
|
|
||||||
case 2: // normal
|
|
||||||
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
|
|
||||||
break
|
|
||||||
case 3: // high
|
|
||||||
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
|
|
||||||
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>.
|
|
||||||
When you're using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
|
|
||||||
so if this is the case just try to reload the page and see if you it's working then`
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// shows a message if hls could not be loaded
|
|
||||||
showMessage(`Could not load hls video. ${message}`)
|
|
||||||
}, rawReliability * 3000)
|
|
||||||
|
|
||||||
document.title = searchParams.get('domain')
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
clearTimeout(thirdPartyFallback)
|
|
||||||
document.getElementById('video').hidden = false
|
|
||||||
document.getElementById('message').hidden = true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// shows a message if hls is not supported
|
|
||||||
showMessage(`Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadHls()
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es2015",
|
|
||||||
"removeComments": true,
|
|
||||||
"lib": [
|
|
||||||
"dom",
|
|
||||||
"es5",
|
|
||||||
"scripthost",
|
|
||||||
"es2015.collection",
|
|
||||||
"es2015.promise"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
}
|
|
3
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
/// <reference types="@samrum/vite-plugin-web-extension/client" />
|
7
svelte.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import sveltePreprocess from 'svelte-preprocess';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: sveltePreprocess()
|
||||||
|
};
|
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "es2021",
|
||||||
|
"module": "esnext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||||
|
}
|
36
vite.config.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import webExtension from '@samrum/vite-plugin-web-extension';
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
import { defineConfig, loadEnv, type PluginOption } from 'vite';
|
||||||
|
import { matches } from './src/lib/match';
|
||||||
|
import { getManifest } from './src/manifest';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd(), '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
svelte() as PluginOption,
|
||||||
|
webExtension({
|
||||||
|
manifest: getManifest(Number(env.MANIFEST_VERSION)),
|
||||||
|
additionalInputs: {
|
||||||
|
html: [
|
||||||
|
{
|
||||||
|
fileName: 'src/entries/player/player.html',
|
||||||
|
webAccessible: {
|
||||||
|
matches:
|
||||||
|
Number(env.MANIFEST_VERSION) === 3
|
||||||
|
? Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`))
|
||||||
|
: ['<all_urls>']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}) as unknown as PluginOption
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: [{ find: '~', replacement: fileURLToPath(new URL('./src', import.meta.url)) }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|