Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
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@v3
|
||||||
|
|
||||||
|
- name: Install nodejs
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
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@v3
|
||||||
|
|
||||||
|
- name: Install nodejs
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
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@v3
|
||||||
|
with:
|
||||||
|
name: stream-bypass-mv${{ matrix.manifest_version }}
|
||||||
|
path: ./dist
|
||||||
|
if-no-files-found: error
|
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
.idea/
|
.idea/
|
||||||
build/
|
|
||||||
dist/
|
dist/
|
||||||
|
release/
|
||||||
node_modules/
|
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
|
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 ByteDream
|
Copyright (c) 2022-NOW ByteDream
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
149
README.md
@ -3,21 +3,18 @@
|
|||||||
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.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/ByteDream/stream-bypass/releases/latest">
|
<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">
|
<img src="https://img.shields.io/github/v/release/ByteDream/stream-bypass?label=Version&style=flat-square" alt="Version">
|
||||||
</a>
|
</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%20Store%20Downloads&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://addons.mozilla.org/de/firefox/addon/stream-bypass/">
|
<a href="https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb">
|
||||||
<img src="https://img.shields.io/amo/stars/stream-bypass?label=Firefox%20Store%20Stars&style=flat-square" alt="Firefox Addon Stars">
|
<img src="https://img.shields.io/chrome-web-store/users/ddfpfjomnakfckhmilacnbokdaknamdb?style=flat-square&label=Chrome%20Users" alt="Chrome Store">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/ByteDream/stream-bypass/releases/latest">
|
<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>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -25,9 +22,13 @@ A multi-browser addon / extension for multiple streaming providers which redirec
|
|||||||
•
|
•
|
||||||
<a href="#-installation">Installation 📥</a>
|
<a href="#-installation">Installation 📥</a>
|
||||||
•
|
•
|
||||||
<a href="#-supported-sites">Supported Sites 📜</a>
|
<a href="#-features">Features ✨</a>
|
||||||
•
|
•
|
||||||
<a href="#%EF%B8%8F-building">Building ⚙️</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>
|
<a href="#-license">License ⚖</a>
|
||||||
</p>
|
</p>
|
||||||
@ -45,83 +46,129 @@ Additionally, this enables you to download the video by right-clicking it and ju
|
|||||||
|
|
||||||
## 📥 Installation
|
## 📥 Installation
|
||||||
|
|
||||||
### Firefox
|
### Official browser stores
|
||||||
|
|
||||||
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/).
|
The best way to install the extension are the official browser extension stores:
|
||||||
|
|
||||||
### Chromium / Google Chrome
|
- [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)
|
||||||
|
|
||||||
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).
|
### Manual installation
|
||||||
2. Go into your browser and type `chrome://extensions` in the address bar.
|
|
||||||
3. Turn the developer mode in the top right corner on.
|
|
||||||
4. Click Load unpacked.
|
|
||||||
5. Choose the cloned / unzipped directory.
|
|
||||||
|
|
||||||
### Opera
|
- Firefox
|
||||||
|
- 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)
|
||||||
|
- Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar
|
||||||
|
- Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the 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
|
||||||
|
|
||||||
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).
|
## ✨ Features
|
||||||
2. Go into your browser and type `opera://extensions` in the address bar.
|
|
||||||
3. Turn the developer mode in the top right corner on.
|
|
||||||
4. Click Load unpacked.
|
|
||||||
5. Choose the cloned / unzipped directory.
|
|
||||||
|
|
||||||
## 📜 Supported sites
|
| Feature | Firefox | Chrome | Firefox for Android |
|
||||||
|
| --------------------------------------------------------------------------------------------------------------------------------- | ------- | ------ | ------------------- |
|
||||||
|
| Replace site-speicifc video player with browser native video player | ✔ | ✔ | ✔ |
|
||||||
|
| Support websites that are accessed via a redirect | ✔ | ❌ | ✔ |
|
||||||
|
| Open video in mpv (with [ff2mpv](https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams)) | ✔ | ✔ | ❌ |
|
||||||
|
|
||||||
| Site | Supported | Note |
|
- ✔️: Supported.
|
||||||
|-----------------------------------------------------------------------|-----------|-------------------------------------------------------|
|
- ❌: Not supported.
|
||||||
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | |
|
|
||||||
| [evoload.io](https://evoload.io) | ⚠️️ | Website down / Timeout |
|
## 📜 Supported websites
|
||||||
| [filemoon.sx](https://filemoon.sx) | ✔ | |
|
|
||||||
| [mcloud.to](https://mcloud.to/) | ❌ | Reverse engineering the site costs too much time (#5) |
|
| Site | Firefox & Firefox for Android | Chrome & Chromium based |
|
||||||
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | |
|
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||||
| [mp4upload.com](https://mp4upload.com) | ✔ | |
|
| [dropload.io](https://dropload.io) | ✔ | ✔ |
|
||||||
| [newgrounds.com](https://newgrounds.com) | ✔ | |
|
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | ⚠ (redirect probably required) |
|
||||||
| [streamtape.com](https://streamtape.com) | ✔ | |
|
| [filemoon.to](https://filemoon.to) | ✔ | ✔ |
|
||||||
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
|
| [goodstream.uno](https://goodstream.uno) | ✔ | ✔ |
|
||||||
| [upstream.to](https://upstream.to) | ✔ | |
|
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | ✔ |
|
||||||
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
|
| [mp4upload.com](https://mp4upload.com) | ✔ | ✔ |
|
||||||
| [vidlox.me](https://vidlox.me) | ⚠ | Website down / Timeout |
|
| [newgrounds.com](https://newgrounds.com) | ✔ | ✔ |
|
||||||
| [vidoza.net](https://vidoza.net) | ✔ | |
|
| [streama2z.com](https://streama2z.com) | ✔ | ❌ (redirect always required) |
|
||||||
| [vistream.pro](https://vidstream.pro) | ❌ | Reverse engineering the site costs too much time (#5) |
|
| [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) |
|
||||||
| [vivo.sx](https://vivo.sx) | ⚠️ | Website down / Timeout |
|
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | ✔ |
|
||||||
| [voe.sx](https://voe.sx) | ✔ | |
|
| [supervideo.tv](https://supervideo.tv) | ✔ | ✔ |
|
||||||
| [vupload.com](https://vupload.com) | ✔ | |
|
| [upstream.to](https://upstream.to) | ✔ | ✔ |
|
||||||
|
| [vidmoly.to](https://vidmoly.me) | ✔ | ✔ |
|
||||||
|
| [vidoza.net](https://vidoza.net) | ⚠ (doesn't always work the first time, retrying/reloading the page one or two times fixes it) | ⚠ (doesn't always work the first time, retrying/reloading the page one or two times fixes it) |
|
||||||
|
| [voe.sx](https://voe.sx) | ✔ | ❌ (redirect always required) |
|
||||||
|
| [vupload.com](https://vupload.com) | ✔ | ✔ |
|
||||||
|
| [kwik.cx](https://kwik.cx) | ✔ | ✔ |
|
||||||
|
|
||||||
- ✔️: Everything ok.
|
- ✔️: Everything ok.
|
||||||
- ⚠: Included in the addon but will probably not work. See `Note` in this case, an explanation why will stand there in the most cases.
|
- ⚠: Works with limitations.
|
||||||
- ❌: Not included / supported by the addon. This can have various reasons. See `Note` for an explanation.
|
- ❌: 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.
|
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
|
## 🛠️ Building
|
||||||
|
|
||||||
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
|
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- `npm` installed.
|
- `npm` installed.
|
||||||
- A copy of this repository and a shell / console open in the copied directory.
|
- 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:
|
If the requirements are satisfied, you can continue with the following commands:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# install all dependencies
|
# install all dependencies
|
||||||
$ npm install
|
$ npm install
|
||||||
|
|
||||||
# build the extension source to a build/ directory
|
# build the extension source to the dist/ directory
|
||||||
$ npm run build
|
$ npm run build
|
||||||
|
|
||||||
# same as build + create a bundle zipfile at dist/
|
# same as build + more optimizations and browser specific settings at release/
|
||||||
$ npm run bundle
|
$ npm run release:firefox # or "release:chrome" to create a release for chromium based browsers
|
||||||
```
|
```
|
||||||
|
|
||||||
##### 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 [installation](#-installation).
|
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.
|
||||||
|
|
||||||
|
## ⚙️ 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
|
## ⚖ 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.
|
||||||
|
40
eslint.config.mjs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
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'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
8249
package-lock.json
generated
Normal file
67
package.json
@ -1,41 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "stream-bypass",
|
"name": "stream-bypass",
|
||||||
"version": "2.1.6",
|
"version": "3.1.1",
|
||||||
|
"displayName": "Stream Bypass",
|
||||||
|
"author": "bytedream",
|
||||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||||
"main": "src/index.ts",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node tasks/build.ts",
|
"build": "vite build",
|
||||||
"bundle": "node tasks/build.ts && web-ext build -s build -a dist -o",
|
"watch": "vite build --watch --mode development --minify false",
|
||||||
"bundle:all": "node tasks/build.ts && web-ext build -s build -a dist -o && web-ext sign -s build -a dist",
|
"dev": "vite",
|
||||||
"check": "web-ext lint -s build"
|
"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 --plugin prettier-plugin-svelte . && eslint .",
|
||||||
|
"format": "prettier --write --plugin prettier-plugin-svelte .",
|
||||||
|
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
|
||||||
|
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
|
||||||
},
|
},
|
||||||
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/ByteDream/stream-bypass.git"
|
"url": "git+https://github.com/bytedream/stream-bypass.git"
|
||||||
},
|
},
|
||||||
"author": "ByteDream",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/ByteDream/stream-bypass/issues"
|
"url": "https://github.com/bytedream/stream-bypass/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^22.0.0",
|
"@samrum/vite-plugin-web-extension": "^5.1.1",
|
||||||
"@rollup/plugin-node-resolve": "^14.0.1",
|
"@sveltejs/vite-plugin-svelte": "^5.0.2",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@rollup/plugin-typescript": "^8.3.3",
|
"@types/chrome": "^0.0.287",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
"@types/firefox-webext-browser": "^120.0.4",
|
||||||
"@typescript-eslint/parser": "^5.27.1",
|
"eslint": "^9.17.0",
|
||||||
"@types/chrome": "^0.0.196",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"@types/node-sass": "^4.11.2",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"@types/yazl": "^2.4.2",
|
"hls.js": "^1.5.17",
|
||||||
"eslint": "^8.17.0",
|
"prettier": "^3.4.2",
|
||||||
"hls.js": "^1.1.5",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"node-sass": "^7.0.1",
|
"sass": "^1.83.0",
|
||||||
"node-sass-package-importer": "^5.3.2",
|
"svelte": "^5.14.0",
|
||||||
"rollup": "^2.75.6",
|
"svelte-check": "^4.1.1",
|
||||||
"tippy.js": "^6.3.7",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"typescript": "^4.7.3",
|
"tslib": "^2.8.1",
|
||||||
"web-ext": "^7.0.0"
|
"typescript": "^5.7.2",
|
||||||
}
|
"typescript-eslint": "^8.18.0",
|
||||||
|
"vite": "^6.0.3",
|
||||||
|
"web-ext": "^8.3.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 |
@ -1,18 +0,0 @@
|
|||||||
import {getMatch} from "./match/match";
|
|
||||||
import {storageDelete, storageGet, storageSet} from "./store/store";
|
|
||||||
import {Match} from "./match/matches";
|
|
||||||
|
|
||||||
chrome.webRequest.onBeforeRedirect.addListener(async details => {
|
|
||||||
// check if redirects origins from a previous redirect
|
|
||||||
if (await storageGet('redirect') === undefined) {
|
|
||||||
let match: Match
|
|
||||||
if ((match = await getMatch(new URL(details.url).host)) !== undefined) {
|
|
||||||
await storageSet('redirect', match.id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await storageDelete('redirect')
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
urls: ['<all_urls>'],
|
|
||||||
types: ['main_frame', 'sub_frame']
|
|
||||||
})
|
|
40
src/entries/background/mv2.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import './shared';
|
||||||
|
|
||||||
|
import type { Match } from '~/lib/match';
|
||||||
|
import { Redirect, UrlReferer } from '~/lib/settings';
|
||||||
|
import { getMatch } from '~/lib/match';
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
});
|
90
src/entries/contentScript/main.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import type { Match } from '~/lib/match';
|
||||||
|
import { getMatch } 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;
|
||||||
|
try {
|
||||||
|
url = await match.match(re);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
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 && !url.includes('.m3u8')) {
|
||||||
|
// 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
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
65
src/entries/player/Player.svelte
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { play } from '~/entries/player/player';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
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 Player from '~/entries/player/Player.svelte';
|
||||||
|
import { mount } from 'svelte';
|
||||||
|
|
||||||
|
mount(Player, {
|
||||||
|
target: document.body
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
46
src/entries/player/player.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { matches } from '~/lib/match';
|
||||||
|
import Hls from 'hls.js';
|
||||||
|
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 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 (new URL(url).pathname.endsWith('.m3u8')) {
|
||||||
|
await playHls(url, domain, videoElem);
|
||||||
|
} else {
|
||||||
|
await playNative(url, domain, videoElem);
|
||||||
|
}
|
||||||
|
}
|
165
src/entries/popup/Popup.svelte
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { type Match, matches } 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}
|
||||||
|
<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 Popup from '~/entries/popup/Popup.svelte';
|
||||||
|
import { mount } from '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>
|
@ -1,77 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="195.32812mm"
|
|
||||||
height="195.32812mm"
|
|
||||||
viewBox="0 0 195.32812 195.32812"
|
|
||||||
version="1.1"
|
|
||||||
id="svg5"
|
|
||||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
|
||||||
sodipodi:docname="logo-disabled.svg"
|
|
||||||
inkscape:export-filename="disabled_128.png"
|
|
||||||
inkscape:export-xdpi="16.644812"
|
|
||||||
inkscape:export-ydpi="16.644812"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview7"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.90509668"
|
|
||||||
inkscape:cx="428.68349"
|
|
||||||
inkscape:cy="362.94465"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1015"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="36"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="layer1" />
|
|
||||||
<defs
|
|
||||||
id="defs2">
|
|
||||||
<inkscape:path-effect
|
|
||||||
effect="powerstroke"
|
|
||||||
id="path-effect3140"
|
|
||||||
is_visible="true"
|
|
||||||
lpeversion="1"
|
|
||||||
offset_points="3,0"
|
|
||||||
not_jump="false"
|
|
||||||
sort_points="true"
|
|
||||||
interpolator_type="CubicBezierJohan"
|
|
||||||
interpolator_beta="0.2"
|
|
||||||
start_linecap_type="zerowidth"
|
|
||||||
linejoin_type="extrp_arc"
|
|
||||||
miter_limit="4"
|
|
||||||
scale_width="1"
|
|
||||||
end_linecap_type="zerowidth" />
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-11.238281,-30.525391)">
|
|
||||||
<path
|
|
||||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
|
||||||
d="m 108.90234,30.525391 c -53.858772,0 -97.664059,43.805286 -97.664059,97.664059 0,53.85878 43.805287,97.66407 97.664059,97.66407 53.85878,0 97.66407,-43.80529 97.66407,-97.66407 0,-53.858773 -43.80529,-97.664059 -97.66407,-97.664059 z m 0,13.433593 c 46.59895,0 84.23047,37.631526 84.23047,84.230466 0,46.59895 -37.63152,84.23047 -84.23047,84.23047 -46.59894,0 -84.232418,-37.63152 -84.232418,-84.23047 0,-46.59894 37.633478,-84.230466 84.232418,-84.230466 z"
|
|
||||||
id="path2592" />
|
|
||||||
<path
|
|
||||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
|
||||||
d="m 8.9863281,105.29688 v 96.92578 l 83.9394529,-48.46289 -7.699219,-4.44532 z m 10.2656249,17.78125 53.142578,30.68164 -53.142578,30.68164 z"
|
|
||||||
id="path2646"
|
|
||||||
transform="matrix(1.3085265,0,0,1.3085265,56.033246,-73.00885)" />
|
|
||||||
<path
|
|
||||||
style="fill:#808080;stroke:#000000;stroke-width:5.46616"
|
|
||||||
d="M 345.13114,686.08528 C 222.64086,675.79533 119.51392,599.94853 74.016249,486.68834 64.419992,462.79978 58.296197,439.49115 53.966274,410.37335 51.203358,391.79333 51.205528,346.25661 53.970219,327.58931 61.4703,276.94849 77.618278,233.84988 103.92601,194.25835 143.90493,134.09253 200.40964,90.698198 268.39759,67.947934 322.37862,49.884696 378.75198,46.533168 433.87378,58.109989 c 82.82951,17.396066 153.43381,65.362801 200.69115,136.344381 10.701,16.07313 25.83502,46.0053 32.67962,64.63397 6.70243,18.2417 13.10531,43.27443 16.2797,63.64715 2.0974,13.46078 2.48648,20.4825 2.53407,45.73343 0.0589,31.24975 -0.58565,38.59247 -5.57665,63.52913 -26.77909,133.797 -136.64661,235.61462 -272.39375,252.4357 -17.24628,2.13706 -47.67501,2.9353 -62.95678,1.65153 z M 422.60679,489.0315 C 535.93722,423.59631 628.78214,369.70078 628.92885,369.26367 629.20612,368.43746 217.06471,130.09256 214.27041,129.46316 c -1.37426,-0.30954 -1.58595,31.67289 -1.58595,239.60604 0,227.99566 0.0965,239.93763 1.9335,239.44933 1.06342,-0.28267 94.65839,-54.05183 207.98883,-119.48703 z"
|
|
||||||
id="path2606"
|
|
||||||
transform="matrix(0.26458333,0,0,0.26458333,11.238281,30.525391)" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 2.7 KiB |
@ -1,77 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="195.32812mm"
|
|
||||||
height="195.32812mm"
|
|
||||||
viewBox="0 0 195.32812 195.32812"
|
|
||||||
version="1.1"
|
|
||||||
id="svg5"
|
|
||||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
|
||||||
sodipodi:docname="logo.svg"
|
|
||||||
inkscape:export-filename="logo_128.png"
|
|
||||||
inkscape:export-xdpi="16.644812"
|
|
||||||
inkscape:export-ydpi="16.644812"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview7"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.90509668"
|
|
||||||
inkscape:cx="198.32136"
|
|
||||||
inkscape:cy="397.19514"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1015"
|
|
||||||
inkscape:window-x="1920"
|
|
||||||
inkscape:window-y="36"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="layer1" />
|
|
||||||
<defs
|
|
||||||
id="defs2">
|
|
||||||
<inkscape:path-effect
|
|
||||||
effect="powerstroke"
|
|
||||||
id="path-effect3140"
|
|
||||||
is_visible="true"
|
|
||||||
lpeversion="1"
|
|
||||||
offset_points="3,0"
|
|
||||||
not_jump="false"
|
|
||||||
sort_points="true"
|
|
||||||
interpolator_type="CubicBezierJohan"
|
|
||||||
interpolator_beta="0.2"
|
|
||||||
start_linecap_type="zerowidth"
|
|
||||||
linejoin_type="extrp_arc"
|
|
||||||
miter_limit="4"
|
|
||||||
scale_width="1"
|
|
||||||
end_linecap_type="zerowidth" />
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-11.238281,-30.525391)">
|
|
||||||
<path
|
|
||||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
|
||||||
d="m 108.90234,30.525391 c -53.858772,0 -97.664059,43.805286 -97.664059,97.664059 0,53.85878 43.805287,97.66407 97.664059,97.66407 53.85878,0 97.66407,-43.80529 97.66407,-97.66407 0,-53.858773 -43.80529,-97.664059 -97.66407,-97.664059 z m 0,13.433593 c 46.59895,0 84.23047,37.631526 84.23047,84.230466 0,46.59895 -37.63152,84.23047 -84.23047,84.23047 -46.59894,0 -84.232418,-37.63152 -84.232418,-84.23047 0,-46.59894 37.633478,-84.230466 84.232418,-84.230466 z"
|
|
||||||
id="path2592" />
|
|
||||||
<path
|
|
||||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
|
||||||
d="m 8.9863281,105.29688 v 96.92578 l 83.9394529,-48.46289 -7.699219,-4.44532 z m 10.2656249,17.78125 53.142578,30.68164 -53.142578,30.68164 z"
|
|
||||||
id="path2646"
|
|
||||||
transform="matrix(1.3085265,0,0,1.3085265,56.033246,-73.00885)" />
|
|
||||||
<path
|
|
||||||
style="fill:#ffffff;stroke:#000000;stroke-width:5.46616"
|
|
||||||
d="M 345.13114,686.08528 C 222.64086,675.79533 119.51392,599.94853 74.016249,486.68834 64.419992,462.79978 58.296197,439.49115 53.966274,410.37335 51.203358,391.79333 51.205528,346.25661 53.970219,327.58931 61.4703,276.94849 77.618278,233.84988 103.92601,194.25835 143.90493,134.09253 200.40964,90.698198 268.39759,67.947933 322.37862,49.884696 378.75198,46.533168 433.87378,58.109989 c 82.82951,17.396066 153.43381,65.362801 200.69115,136.344371 10.701,16.07314 25.83502,46.00531 32.67962,64.63398 6.70243,18.2417 13.10531,43.27443 16.2797,63.64715 2.0974,13.46078 2.48648,20.4825 2.53407,45.73343 0.0589,31.24975 -0.58565,38.59247 -5.57665,63.52913 -26.7791,133.797 -136.64661,235.61462 -272.39375,252.43569 -17.24628,2.13707 -47.67501,2.9353 -62.95678,1.65154 z M 422.60679,489.0315 C 535.93722,423.59631 628.78214,369.70078 628.92885,369.26367 629.20612,368.43746 217.06471,130.09256 214.27041,129.46316 c -1.37426,-0.30954 -1.58595,31.67289 -1.58595,239.60604 0,227.99566 0.0965,239.93763 1.9335,239.44933 1.06342,-0.28267 94.65839,-54.05183 207.98883,-119.48703 z"
|
|
||||||
id="path374"
|
|
||||||
transform="matrix(0.26458333,0,0,0.26458333,11.238281,30.525391)" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
38
src/index.ts
@ -1,38 +0,0 @@
|
|||||||
import {getMatch} from "./match/match";
|
|
||||||
import {storageDelete, storageGet} from "./store/store";
|
|
||||||
import {Match, matches} from "./match/matches";
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let match: Match;
|
|
||||||
let redirect = false;
|
|
||||||
if ((match = await getMatch(window.location.host)) === undefined) {
|
|
||||||
let id: string
|
|
||||||
if ((id = await storageGet('redirect')) !== undefined) {
|
|
||||||
redirect = true
|
|
||||||
match = matches.find(m => m.id === id)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const re = document.body.innerHTML.match(match.regex)
|
|
||||||
if (re === null) return
|
|
||||||
if (redirect) await storageDelete('redirect')
|
|
||||||
|
|
||||||
const url = await match.match(re)
|
|
||||||
|
|
||||||
if (match.replace && !url.endsWith('.m3u8')) {
|
|
||||||
const player = document.createElement('video')
|
|
||||||
player.style.width = '100%'
|
|
||||||
player.style.height = '100%'
|
|
||||||
player.controls = true
|
|
||||||
player.src = url
|
|
||||||
|
|
||||||
document.body.innerHTML = ''
|
|
||||||
document.body.append(player)
|
|
||||||
} else {
|
|
||||||
window.location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${window.location.host}`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
309
src/lib/match.ts
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
import { unpack } from './utils';
|
||||||
|
import { Hosters, Redirect, TmpHost } from './settings';
|
||||||
|
|
||||||
|
export interface Match {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
domains: string[];
|
||||||
|
replace?: boolean;
|
||||||
|
regex: RegExp[];
|
||||||
|
notice?: string;
|
||||||
|
|
||||||
|
match(match: RegExpMatchArray): Promise<string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Doodstream: Match = {
|
||||||
|
name: 'Doodstream',
|
||||||
|
id: 'doodstream',
|
||||||
|
domains: [
|
||||||
|
'doodstream.com',
|
||||||
|
'dood.pm',
|
||||||
|
'dood.ws',
|
||||||
|
'dood.wf',
|
||||||
|
'dood.cx',
|
||||||
|
'dood.sh',
|
||||||
|
'dood.watch',
|
||||||
|
'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 (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.ui'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async (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 (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 (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 (match: RegExpMatchArray) => {
|
||||||
|
const unpacked = await unpack(match[0]);
|
||||||
|
return unpacked.match(/(?<=source=').*(?=')/)![0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mixdrop: Match = {
|
||||||
|
name: 'Mixdrop',
|
||||||
|
id: 'mixdrop',
|
||||||
|
domains: ['mixdrop.co', 'mixdrop.to', 'mixdrop.ch', 'mixdrop.bz', 'mixdrop.gl'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async (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 (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 (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 (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 (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.tv'],
|
||||||
|
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||||
|
|
||||||
|
match: async (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 (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 (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 (match: RegExpMatchArray) => {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Voe: Match = {
|
||||||
|
name: 'Voe',
|
||||||
|
id: 'voe',
|
||||||
|
domains: ['voe.sx'],
|
||||||
|
regex: [/(?<='hls':\s*')\S*(?=')/gm],
|
||||||
|
|
||||||
|
match: async (match: RegExpMatchArray) => {
|
||||||
|
return atob(match[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Vupload: Match = {
|
||||||
|
name: 'Vupload',
|
||||||
|
id: 'vupload',
|
||||||
|
domains: ['vupload.com'],
|
||||||
|
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||||
|
|
||||||
|
match: async (match: RegExpMatchArray) => {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const matches = {
|
||||||
|
[Doodstream.id]: Doodstream,
|
||||||
|
[DropLoad.id]: DropLoad,
|
||||||
|
[Filemoon.id]: Filemoon,
|
||||||
|
[GoodStream.id]: GoodStream,
|
||||||
|
[Kwik.id]: Kwik,
|
||||||
|
[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;
|
||||||
|
}
|
113
src/lib/settings.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import type { Match } from './match';
|
||||||
|
import { matches } 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;
|
||||||
|
}
|
92
src/lib/utils.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,50 +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": "2.1.6",
|
|
||||||
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
|
||||||
"browser_specific_settings": {
|
|
||||||
"gecko": {
|
|
||||||
"id": "{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"content_scripts": [
|
|
||||||
{
|
|
||||||
"all_frames": true,
|
|
||||||
"matches": [
|
|
||||||
"<all_urls>"
|
|
||||||
],
|
|
||||||
"js": [
|
|
||||||
"index.js"
|
|
||||||
],
|
|
||||||
"run_at": "document_end"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"background": {
|
|
||||||
"scripts": [
|
|
||||||
"background.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"permissions": [
|
|
||||||
"storage",
|
|
||||||
"webRequest",
|
|
||||||
"<all_urls>"
|
|
||||||
],
|
|
||||||
"web_accessible_resources": [
|
|
||||||
"ui/player/*"
|
|
||||||
],
|
|
||||||
"browser_action": {
|
|
||||||
"default_icon": {
|
|
||||||
"48": "icons/logo_48.png",
|
|
||||||
"128": "icons/logo_128.png"
|
|
||||||
},
|
|
||||||
"default_title": "Stream Bypass",
|
|
||||||
"default_popup": "ui/popup/popup.html"
|
|
||||||
},
|
|
||||||
"icons": {
|
|
||||||
"48": "icons/logo_48.png",
|
|
||||||
"128": "icons/logo_128.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}`);
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
import {Match, matches} from "./matches";
|
|
||||||
import {getAllDisabled, getDisabled} from "../store/store";
|
|
||||||
|
|
||||||
export async function getMatch(host: string): Promise<Match | undefined> {
|
|
||||||
if (await getAllDisabled()) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
if (match.domains.some(v => host.indexOf(v) !== -1) && !((await getDisabled()).some(v => v === match))) {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
export enum Reliability {
|
|
||||||
HIGH,
|
|
||||||
NORMAL,
|
|
||||||
LOW,
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class Match {
|
|
||||||
name: string
|
|
||||||
id: string
|
|
||||||
reliability: Reliability
|
|
||||||
domains: string[]
|
|
||||||
replace?: boolean
|
|
||||||
regex: RegExp
|
|
||||||
abstract match(match: RegExpMatchArray): Promise<string>
|
|
||||||
|
|
||||||
notice?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
class Doodstream implements Match {
|
|
||||||
name = 'Doodstream'
|
|
||||||
id = 'doodstream'
|
|
||||||
reliability = Reliability.NORMAL
|
|
||||||
domains = [
|
|
||||||
'doodstream.com',
|
|
||||||
'dood.pm',
|
|
||||||
'dood.ws',
|
|
||||||
'dood.wf'
|
|
||||||
]
|
|
||||||
replace = true
|
|
||||||
regex = new RegExp(/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const response = await fetch(`https://${window.location.host}${match[1]}`, {
|
|
||||||
headers: {
|
|
||||||
'Range': 'bytes=0-'
|
|
||||||
},
|
|
||||||
referrer: `https://${window.location.host}/e/${window.location.pathname.split('/').slice(-1)[0]}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
return `${await response.text()}1234567890${match[2]}${Date.now()}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Evoload implements Match {
|
|
||||||
name = 'Evoload'
|
|
||||||
id = 'evoload'
|
|
||||||
reliability = Reliability.NORMAL
|
|
||||||
domains = [
|
|
||||||
'evoload.io'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/.*/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const code = window.location.pathname.split('/').slice(-1)[0]
|
|
||||||
const response = await fetch('https://evoload.io/SecurePlayer', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({code: code})
|
|
||||||
})
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
return json['stream']['src']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Filemoon implements Match {
|
|
||||||
name = 'Filemoon'
|
|
||||||
id = 'filemoon'
|
|
||||||
reliability = Reliability.HIGH
|
|
||||||
domains = [
|
|
||||||
'filemoon.sx'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const start_idx = match.indexOf('moon')
|
|
||||||
|
|
||||||
const prefix = `${match[start_idx]}-${match[start_idx-1]}-${match[start_idx-2]}-${match[start_idx-3]}`
|
|
||||||
const time = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
|
|
||||||
const offset = !isNaN(parseInt(match[start_idx-12])) && parseInt(match[start_idx-12]).toString().length == match[start_idx-12].length ? 0 : -1
|
|
||||||
|
|
||||||
return `https://${prefix}.filemoon.${match[start_idx-4]}/${match[start_idx-5]}/${match[start_idx-6]}/${match[start_idx-7]}/${match[start_idx-8]}/master.m3u8?t=${match[start_idx-11]}${offset != 0 ? `-${match[start_idx-12]}` : ''}&s=${time}&e=${match[start_idx + offset - 12]}&sp=${match[start_idx + offset - 18]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Mixdrop implements Match {
|
|
||||||
name = 'Mixdrop'
|
|
||||||
id = 'mixdrop'
|
|
||||||
reliability = Reliability.NORMAL
|
|
||||||
domains = [
|
|
||||||
'mixdrop.co'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
const prefix = /(?<=\/\/)[a|s](?=-)/.exec(document.body.innerHTML)[0]
|
|
||||||
const subdomain = match[1].length < match[2].length ? match[1] : match[2]
|
|
||||||
const domain = match.slice().sort((a, b) => b.length - a.length).find(m => /^[a-z]+$/.test(m))
|
|
||||||
const id = match[1].length > match[2].length ? match[1] : match[2]
|
|
||||||
const tld = match.find(m => ['net', 'io', 'to', 'sx', 'com'].indexOf(m) !== -1)
|
|
||||||
const s = match.slice().sort((a, b) => b.length - a.length).slice(1)[0]
|
|
||||||
const e_t = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
|
|
||||||
|
|
||||||
return `https://${prefix}-${subdomain}.${domain}.${tld}/v/${id}.mp4?s=${s}&e=${e_t}&_t=${e_t}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Mp4Upload implements Match {
|
|
||||||
name = 'Mp4Upload'
|
|
||||||
id = 'mp4upload'
|
|
||||||
reliability = Reliability.NORMAL
|
|
||||||
domains = [
|
|
||||||
'mp4upload.com'
|
|
||||||
]
|
|
||||||
replace = true
|
|
||||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
let id = match.slice().reduce((a, b) => a.length >= b.length ? a : b)
|
|
||||||
return `https://www4.mp4upload.com:282/d/${id}/video.mp4`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Newgrounds implements Match {
|
|
||||||
name = 'Newgrounds'
|
|
||||||
id = 'newgrounds'
|
|
||||||
reliability = Reliability.HIGH
|
|
||||||
domains = [
|
|
||||||
'newgrounds.com'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/.*/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
let id = window.location.pathname.split('/').slice(-1)[0]
|
|
||||||
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let json = await response.json()
|
|
||||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Streamtape implements Match {
|
|
||||||
name = 'Streamtape'
|
|
||||||
id = 'streamtape'
|
|
||||||
reliability = Reliability.NORMAL
|
|
||||||
domains = [
|
|
||||||
'streamtape.com'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/id=.*(?=')/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://streamtape.com/get_video?${match.reverse()[0]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Streamzz implements Match {
|
|
||||||
name = 'Streamzz'
|
|
||||||
id = 'streamzz'
|
|
||||||
reliability = Reliability.LOW
|
|
||||||
domains = [
|
|
||||||
'streamzz.to',
|
|
||||||
'streamz.ws'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://get.${document.domain.split('.')[0]}.tw/getlink-${match.sort((a, b) => b.length - a.length)[0]}.dll`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Upstream implements Match {
|
|
||||||
name = 'Upstream'
|
|
||||||
id = 'upstream'
|
|
||||||
reliability = Reliability.NORMAL
|
|
||||||
domains = [
|
|
||||||
'upstream.to'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return `https://${match[49]}.upstreamcdn.co/hls/${match[148]}/master.m3u8`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vidlox implements Match {
|
|
||||||
name = 'Vidlox'
|
|
||||||
id = 'vidlox'
|
|
||||||
reliability = Reliability.LOW
|
|
||||||
domains = [
|
|
||||||
'vidlox.me'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=\[")\S+?(?=")/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return match[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vidoza implements Match {
|
|
||||||
name = 'Vidoza'
|
|
||||||
id = 'vidoza'
|
|
||||||
reliability = Reliability.HIGH
|
|
||||||
domains = [
|
|
||||||
'vidoza.net'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return match[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vivo implements Match {
|
|
||||||
name = 'Vivo'
|
|
||||||
id = 'vivo'
|
|
||||||
reliability = Reliability.LOW
|
|
||||||
domains = [
|
|
||||||
'vivo.sx'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=source:\s')(\S+)(?=')/gms)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return this.rot47(decodeURIComponent(match[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
|
|
||||||
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('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Voe implements Match {
|
|
||||||
name = 'Voe'
|
|
||||||
id = 'voe'
|
|
||||||
reliability = Reliability.HIGH
|
|
||||||
domains = [
|
|
||||||
'voe.sx'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/https?:\/\/\S*m3u8(?=")/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return match[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Vupload implements Match {
|
|
||||||
name = 'Vupload'
|
|
||||||
id = 'vupload'
|
|
||||||
reliability = Reliability.HIGH
|
|
||||||
domains = [
|
|
||||||
'vupload.com'
|
|
||||||
]
|
|
||||||
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
|
|
||||||
|
|
||||||
async match(match: RegExpMatchArray): Promise<string> {
|
|
||||||
return match[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const matches = [
|
|
||||||
new Doodstream(),
|
|
||||||
new Evoload(),
|
|
||||||
new Filemoon(),
|
|
||||||
new Mixdrop(),
|
|
||||||
new Mp4Upload(),
|
|
||||||
new Newgrounds(),
|
|
||||||
new Streamtape(),
|
|
||||||
new Streamzz(),
|
|
||||||
new Upstream(),
|
|
||||||
new Vidlox(),
|
|
||||||
new Vidoza(),
|
|
||||||
new Vivo(),
|
|
||||||
new Voe(),
|
|
||||||
new Vupload()
|
|
||||||
]
|
|
@ -1,68 +0,0 @@
|
|||||||
import {Match, matches} from "../match/matches";
|
|
||||||
|
|
||||||
export async function storageGet(key: string): Promise<any> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
chrome.storage.local.get(key, (value) => {
|
|
||||||
resolve(value[key])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function storageSet(key: string, value: any) {
|
|
||||||
const obj = {}
|
|
||||||
obj[key] = value
|
|
||||||
await chrome.storage.local.set(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function storageDelete(key: string) {
|
|
||||||
await chrome.storage.local.remove(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDisabled(): Promise<Match[]> {
|
|
||||||
const localMatches = []
|
|
||||||
|
|
||||||
for (const disabled of (await storageGet('disabled') as string[]) || []) {
|
|
||||||
let m: Match
|
|
||||||
if ((m = matches.find((v) => v.id === disabled)) !== undefined) {
|
|
||||||
localMatches.push(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return localMatches
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllDisabled(): Promise<boolean> {
|
|
||||||
const value = await storageGet('all')
|
|
||||||
return value !== undefined ? String(value).toLowerCase() === 'true' : false
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function enableAll() {
|
|
||||||
await storageSet('all', false)
|
|
||||||
await chrome.browserAction.setIcon({
|
|
||||||
path: null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function disableAll() {
|
|
||||||
await storageSet('all', true)
|
|
||||||
await chrome.browserAction.setIcon({
|
|
||||||
path: {
|
|
||||||
48: chrome.runtime.getURL('icons/disabled_48.png'),
|
|
||||||
128: chrome.runtime.getURL('icons/disabled_128.png')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function enable(match: Match) {
|
|
||||||
const disabled = await storageGet('disabled') || []
|
|
||||||
disabled.splice(disabled.indexOf(match.id))
|
|
||||||
await storageSet('disabled', disabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function disable(match: Match) {
|
|
||||||
const disabled = await storageGet('disabled') as string[] || []
|
|
||||||
if (disabled.indexOf(match.id) !== undefined) {
|
|
||||||
disabled.push(match.id)
|
|
||||||
await storageSet('disabled', disabled)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "esnext",
|
|
||||||
"target": "es2019",
|
|
||||||
"removeComments": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"lib": [
|
|
||||||
"dom",
|
|
||||||
"es5",
|
|
||||||
"scripthost",
|
|
||||||
"es2015.collection",
|
|
||||||
"es2015.promise"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"../node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Stream Bypass</title>
|
|
||||||
<link rel="stylesheet" href="player.css">
|
|
||||||
<script src="player.js" defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<video id="video"></video>
|
|
||||||
<div id="message-container">
|
|
||||||
<p id="message"></p>
|
|
||||||
<br>
|
|
||||||
<p>Open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a></p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,34 +0,0 @@
|
|||||||
body {
|
|
||||||
background-color: #131313;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body, video {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#message-container {
|
|
||||||
visibility: hidden;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
& * {
|
|
||||||
visibility: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
& a, & a:visited {
|
|
||||||
color: red
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import {matches} from "../../match/matches";
|
|
||||||
// @ts-ignore
|
|
||||||
import Hls from "hls.js";
|
|
||||||
|
|
||||||
function show_message(message: string) {
|
|
||||||
document.getElementById('message').innerText = message
|
|
||||||
document.getElementById('message-container').style.visibility = 'visible'
|
|
||||||
document.getElementById('video').hidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function play_native(url: string) {
|
|
||||||
const video = document.getElementById('video') as HTMLVideoElement
|
|
||||||
video.controls = true
|
|
||||||
video.src = url
|
|
||||||
}
|
|
||||||
|
|
||||||
async function play_hls(url: string) {
|
|
||||||
const video = document.getElementById('video') as HTMLVideoElement
|
|
||||||
video.controls = true
|
|
||||||
|
|
||||||
if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
||||||
video.src = url
|
|
||||||
} else if (Hls.isSupported()) {
|
|
||||||
const hls = new Hls({
|
|
||||||
enableWorker: false
|
|
||||||
})
|
|
||||||
hls.loadSource(url)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
} else {
|
|
||||||
show_message('Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const urlQuery = new URLSearchParams(window.location.search)
|
|
||||||
const id = urlQuery.get('id')
|
|
||||||
const url = decodeURIComponent(urlQuery.get('url'))
|
|
||||||
const domain = urlQuery.get('domain')
|
|
||||||
|
|
||||||
const match = matches.find((m) => m.id === id)
|
|
||||||
if (match === undefined) {
|
|
||||||
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
document.title = `Stream Bypass (${domain})`
|
|
||||||
|
|
||||||
new URL(url).pathname.endsWith('.m3u8') ? await play_hls(url) : await play_native(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
@ -1,23 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Stream-Bypass</title>
|
|
||||||
<link rel="stylesheet" href="popup.css">
|
|
||||||
<script src="popup.js" defer></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">Something does not work</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,66 +0,0 @@
|
|||||||
body {
|
|
||||||
background-color: #2b2a33;
|
|
||||||
font-weight: bold;
|
|
||||||
max-height: 500px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
a, p {
|
|
||||||
color: white;
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 5px 0;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
border: 1px solid #281515;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 5px 8px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: rgba(255, 65, 65, 0.74);
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
background-color: grey;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&#error {
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
font-weight: lighter;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px 0 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#all {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 10px 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.low-reliability {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-color: rgb(255, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.normal-reliability {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.high-reliability {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-color: #00ff00;
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
|
|
||||||
import {matches, Reliability} from "../../match/matches";
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const disabled = await getDisabled()
|
|
||||||
|
|
||||||
const subContainer = document.getElementById('sub-container')
|
|
||||||
for (const m of matches) {
|
|
||||||
const row = document.createElement('tr')
|
|
||||||
|
|
||||||
const name = document.createElement('td')
|
|
||||||
const nameValue = document.createElement('p')
|
|
||||||
nameValue.innerText = m.name
|
|
||||||
switch (m.reliability) {
|
|
||||||
case Reliability.LOW:
|
|
||||||
nameValue.classList.add('low-reliability')
|
|
||||||
break
|
|
||||||
case Reliability.NORMAL:
|
|
||||||
nameValue.classList.add('normal-reliability')
|
|
||||||
break
|
|
||||||
case Reliability.HIGH:
|
|
||||||
nameValue.classList.add('high-reliability')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttons = document.createElement('td')
|
|
||||||
buttons.classList.add('buttons')
|
|
||||||
const on = document.createElement('a')
|
|
||||||
on.innerText = 'On'
|
|
||||||
const off = document.createElement('a')
|
|
||||||
off.innerText = 'Off'
|
|
||||||
disabled.find((v) => v.id === m.id) === undefined ? on.classList.add('active') : off.classList.add('active')
|
|
||||||
|
|
||||||
on.onclick = async function () {
|
|
||||||
if (!on.classList.contains('disabled')) {
|
|
||||||
await enable(m)
|
|
||||||
on.classList.add('active')
|
|
||||||
off.classList.remove('active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
off.onclick = async function () {
|
|
||||||
if (!off.classList.contains('disabled')) {
|
|
||||||
await disable(m)
|
|
||||||
on.classList.remove('active')
|
|
||||||
off.classList.add('active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name.append(nameValue)
|
|
||||||
buttons.append(on, off)
|
|
||||||
row.append(name, buttons)
|
|
||||||
subContainer.append(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
const allOnButton = document.getElementById('all').getElementsByTagName('a')[0]
|
|
||||||
const allOffButton = document.getElementById('all').getElementsByTagName('a')[1]
|
|
||||||
|
|
||||||
if (await getAllDisabled()) {
|
|
||||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
|
||||||
for (let i = 0; i < allBtns.length; i++) {
|
|
||||||
allBtns[i].classList.add('disabled')
|
|
||||||
}
|
|
||||||
allOffButton.classList.add('active')
|
|
||||||
} else {
|
|
||||||
allOnButton.classList.add('active')
|
|
||||||
}
|
|
||||||
|
|
||||||
allOnButton.onclick = async () => {
|
|
||||||
if (!allOnButton.classList.contains('active')) {
|
|
||||||
allOnButton.classList.add('active')
|
|
||||||
allOffButton.classList.remove('active')
|
|
||||||
|
|
||||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
|
||||||
for (let i = 0; i < allBtns.length; i++) {
|
|
||||||
allBtns[i].classList.remove('disabled')
|
|
||||||
}
|
|
||||||
|
|
||||||
await enableAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allOffButton.onclick = async () => {
|
|
||||||
if (!allOffButton.classList.contains('active')) {
|
|
||||||
allOffButton.classList.add('active')
|
|
||||||
allOnButton.classList.remove('active')
|
|
||||||
|
|
||||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
|
||||||
for (let i = 0; i < allBtns.length; i++) {
|
|
||||||
allBtns[i].classList.add('disabled')
|
|
||||||
}
|
|
||||||
|
|
||||||
await disableAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
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()
|
||||||
|
};
|
107
tasks/build.ts
@ -1,107 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const rollup = require('rollup')
|
|
||||||
const rollupPluginCommonJs = require('@rollup/plugin-commonjs')
|
|
||||||
const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default
|
|
||||||
const rollupPluginReplace = require('@rollup/plugin-replace')
|
|
||||||
const rollupPluginTypescript = require('@rollup/plugin-typescript')
|
|
||||||
const sass = require('node-sass')
|
|
||||||
const sassPluginNodeImport = require('node-sass-package-importer')
|
|
||||||
const typescript = require('typescript')
|
|
||||||
|
|
||||||
async function buildManifest() {
|
|
||||||
const manifest = JSON.parse(fs.readFileSync('src/manifest.json'))
|
|
||||||
|
|
||||||
manifest['version'] = process.env.npm_package_version
|
|
||||||
|
|
||||||
fs.writeFileSync('src/manifest.json', JSON.stringify(manifest, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildMisc() {
|
|
||||||
const files = {
|
|
||||||
'src/manifest.json': 'build/manifest.json',
|
|
||||||
|
|
||||||
'src/icons/logo_48.png': 'build/icons/logo_48.png',
|
|
||||||
'src/icons/logo_128.png': 'build/icons/logo_128.png',
|
|
||||||
'src/icons/disabled_48.png': 'build/icons/disabled_48.png',
|
|
||||||
'src/icons/disabled_128.png': 'build/icons/disabled_128.png',
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [src, dst] of Object.entries(files)) {
|
|
||||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
|
||||||
fs.copyFileSync(src, dst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildHtml() {
|
|
||||||
const files = {
|
|
||||||
'src/ui/popup/popup.html': 'build/ui/popup/popup.html',
|
|
||||||
'src/ui/player/player.html': 'build/ui/player/player.html'
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [src, dst] of Object.entries(files)) {
|
|
||||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
|
||||||
fs.copyFileSync(src, dst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildCss() {
|
|
||||||
const files = {
|
|
||||||
'src/ui/popup/popup.scss': 'build/ui/popup/popup.css',
|
|
||||||
'src/ui/player/player.scss': 'build/ui/player/player.css'
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [src, dst] of Object.entries(files)) {
|
|
||||||
const compiled = sass.renderSync({
|
|
||||||
file: src,
|
|
||||||
importer: sassPluginNodeImport()
|
|
||||||
})
|
|
||||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
|
||||||
fs.writeFileSync(dst, compiled.css)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildJs() {
|
|
||||||
const files = {
|
|
||||||
'src/ui/popup/popup.ts': 'build/ui/popup/popup.js',
|
|
||||||
'src/ui/player/player.ts': 'build/ui/player/player.js',
|
|
||||||
|
|
||||||
'src/index.ts': 'build/index.js',
|
|
||||||
'src/background.ts': 'build/background.js'
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [src, dst] of Object.entries(files)) {
|
|
||||||
const bundle = await rollup.rollup({
|
|
||||||
input: src,
|
|
||||||
plugins: [
|
|
||||||
rollupPluginNodeResolve({
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
rollupPluginReplace({
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production')
|
|
||||||
}),
|
|
||||||
rollupPluginTypescript({
|
|
||||||
typescript,
|
|
||||||
tsconfig: 'src/tsconfig.json'
|
|
||||||
}),
|
|
||||||
rollupPluginCommonJs({
|
|
||||||
extensions: ['.js', '.ts']
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
await bundle.write({
|
|
||||||
file: dst,
|
|
||||||
strict: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function build() {
|
|
||||||
await buildManifest()
|
|
||||||
await buildMisc()
|
|
||||||
await buildHtml()
|
|
||||||
await buildCss()
|
|
||||||
await buildJs()
|
|
||||||
}
|
|
||||||
|
|
||||||
build()
|
|
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "es2019",
|
||||||
|
"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"]
|
||||||
|
}
|
38
vite.config.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
import webExtension from '@samrum/vite-plugin-web-extension';
|
||||||
|
import path from 'path';
|
||||||
|
import { getManifest } from './src/manifest';
|
||||||
|
import { matches } from './src/lib/match';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd(), '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
svelte(),
|
||||||
|
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>']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, './src')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|