Compare commits

..

190 Commits
v1.2.0 ... main

Author SHA1 Message Date
607326e6d6 Update dependencies and version 2025-05-08 02:02:57 +02:00
47f6de2de8 Fix voe 2025-05-08 01:59:29 +02:00
d159fb0aa4 Add mixdrop domain 2025-05-06 22:21:09 +02:00
290733a85a Add supervideo domain 2025-05-06 22:20:04 +02:00
84eb32ab61 Fix dropload domain 2025-05-06 22:18:19 +02:00
927a4e3ae3 Update dependencies and version 2025-04-17 02:43:13 +02:00
5926177920 Update voe.sx match regex 2025-04-17 02:37:32 +02:00
9a29316641 Fix voe.sx 2025-04-17 02:26:59 +02:00
c300125f64 Set javascript target to es2021 2025-04-17 02:26:41 +02:00
59f2ffec57 Allow arbitrary properties in Match interface 2025-04-17 02:10:18 +02:00
6dff691c25 Use other notation for match function 2025-04-17 02:04:45 +02:00
c4085f9ac8 Update version 2025-04-04 02:13:20 +02:00
422702b16f Add import order prettier plugin 2025-04-04 02:10:51 +02:00
c3dad3681c Fix player media type url query 2025-04-04 02:05:04 +02:00
6eb3259ec5 Remove duplicate supported websites 2025-04-04 01:11:41 +02:00
68e445a738 Update supported websites 2025-04-04 00:58:14 +02:00
e864bc6100 Update dependencies and version 2025-04-04 00:45:03 +02:00
3ac73ac3b4 Update vite config 2025-04-04 00:44:51 +02:00
6aaf960c28 Holy fuck why does vite have a problem with this 2025-04-04 00:43:57 +02:00
8a13dea681 Format 2025-04-04 00:01:21 +02:00
8d575241fe Fix lastPathSegment util 2025-04-04 00:00:38 +02:00
50f400b8b9 Use enum to specify media type 2025-04-03 23:56:18 +02:00
f6fcfd354a Add more utils 2025-04-03 23:48:53 +02:00
c081127d35 Add loadx.ws 2025-04-03 23:43:49 +02:00
416fceba88 Update match function to manually specify media type 2025-04-03 23:43:39 +02:00
a9cf03c176 Fix luluvdo.com 2025-04-03 23:14:31 +02:00
8852318483 Update voe.sx to catch every redirect url 2025-04-03 22:55:24 +02:00
7067aaf4a0 Add luluvdo.com to supported websites table 2025-03-14 18:52:08 +01:00
bf723e2ed6 Update dependencies and version 2025-03-14 18:41:05 +01:00
9c80362a1c Update ci actions 2025-03-14 18:31:29 +01:00
ae738ff32f Add luluvdo.com site 2025-03-14 18:20:08 +01:00
16d4f2956a Add dood.work domain (#23) 2025-03-14 17:43:50 +01:00
bytedream
f6c6102436
Merge pull request #24 from muleyo/main
Added Voe.sx Domain
2025-03-13 16:08:39 +01:00
muleyo
f1525817b1
Added Voe.sx Domain
A domain for Voe.sx was missing
2025-03-10 01:42:23 +01:00
1e1d8cdfa6 Fix tick character 2024-12-19 22:50:59 +01:00
47eed2d12b Update supported websites 2024-12-15 19:08:50 +01:00
67e03eafaa Update dependencies and version 2024-12-15 18:43:06 +01:00
db0ccd9b56 Fix vidoza.net 2024-12-15 18:05:25 +01:00
fd3eda9c61 Fix vidmoly.to 2024-12-15 17:31:13 +01:00
e522916585 Fix filemoon.to 2024-12-15 17:14:46 +01:00
b9514284da Fix voe.sx 2024-12-15 16:16:38 +01:00
06beaa6ebe Update README 2024-07-29 02:35:43 +02:00
6a56c37dd2 Update package-lock.json 2024-07-29 02:31:30 +02:00
018b381732 Update README 2024-07-29 00:00:57 +02:00
696ec22471 Update dependencies and version 2024-07-28 23:43:56 +02:00
e379113aa4 Add firefox android support 2024-07-28 23:33:54 +02:00
84605ceb30 Make nativeMessaging an optional permission 2024-07-28 23:31:25 +02:00
67c492db47 Update popup UI 2024-07-28 23:04:04 +02:00
ae2f196b06 Lint 2024-07-28 18:08:05 +02:00
298e9308ce Add streama2z.com (#19) 2024-07-28 17:54:41 +02:00
3cd4c6b6b7 Fix doodstream.com 2024-07-28 17:54:02 +02:00
6286acf85c Make video replace strategy more aggressive 2024-07-28 17:33:54 +02:00
94eee79218 Fix voe.sx 2024-07-28 16:35:58 +02:00
2f186bda07 Add d000d.com (#18) 2024-07-28 14:46:36 +02:00
1c9f95cebc Fix linting errors 2024-07-14 21:08:47 +02:00
c57cd03407 Fix vidmoly & add it to README 2024-05-20 15:40:38 +02:00
64cf565da3 Update dependencies and version 2024-05-20 15:37:46 +02:00
bf8a7eb602 Remove reliability 2024-05-20 14:11:09 +02:00
c643a39e2d Add new doodstream domain 2024-04-24 01:18:01 +02:00
fcfbc41fdb Fix voe.sx 2024-04-24 00:46:32 +02:00
1251f079f5 Remove discord link 2023-12-10 19:51:35 +01:00
ccfc77167b Add vidmoly.me 2023-12-08 15:50:19 +01:00
a10066458f Add chrome web store link 2023-11-20 12:20:32 +01:00
6a8c705b06 Fix mv2 chrome namespace apis 2023-11-17 16:25:40 +01:00
76a5bac7fd Fix chromium popup height 2023-11-17 16:25:29 +01:00
3aca863a4b Fix invalid release name 2023-11-17 15:06:17 +01:00
fe14edc0ef Make mv3 compatible 2023-11-17 15:06:09 +01:00
fffe23638c Update build instructions 2023-11-11 22:05:39 +01:00
5214228a72 Add linting and build pipeline 2023-11-11 21:48:53 +01:00
7000d5a08b Lint 2023-11-11 21:48:44 +01:00
a21f799e7d Update install instructions 2023-11-11 21:35:24 +01:00
1fb85313dd Use more reliable streamtape match function 2023-11-11 21:31:23 +01:00
6e7410b088 Rewrite 2023-11-11 21:31:20 +01:00
Bharathi
0054f5da0e
Update few domains (#15)
* Update matches.ts

* Update matches.ts

* Update matches.ts
2023-08-08 18:58:50 +02:00
48dc192964 Update version 2023-08-08 14:25:48 +02:00
sdaqo
7c45cacd36
add ff2mpv setting + new hosters (#14)
* add Kwik, use unpacker to improve reliabilty

* use packer for filemoon

* use packer for upstream

* add ff2mpv setting + functionality

* Update README.md

* get rid of @types/webextension-polyfill

* Revert "Update README.md"

This reverts commit affb6000968beb798423a5403508215b7f6e4670.

* Update README.md

* Update info link for ff2mpv

* add kwik to hosters in readme

* removes console.logs

* Delete package-lock.json

* add package-lock.json to .gitignore

* unpack without using eval

* Merge main branch into here

* Add Dropload Hoster

* Add Supervideo Hoster

* Add GoodStream Hoster

* Add hosters to readme and delete console.logs

* Delete package-lock.json

* Fix ff2mpv info url

* Update readme

---------

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

* use packer for filemoon

* use packer for upstream

* Update README.md

* Revert "Update README.md"

This reverts commit affb6000968beb798423a5403508215b7f6e4670.

* add kwik to hosters in readme

* unpack without using eval
2023-04-28 18:19:01 +02:00
ByteDream
7d8d8b6614
Merge pull request #9 from bachig26/master
Update matches.ts
2023-01-19 13:27:04 +01:00
Bharathi
b4052834f2
Update matches.ts
- add dood.re
2023-01-19 05:02:40 +01:00
ByteDream
9900c33243
Merge pull request #7 from bachig26/patch-1
added more domains
2023-01-11 08:10:40 +01:00
Bharathi
98709bc934
added more domains
- additional domains to mixdrop and doodstream
2023-01-11 01:07:48 +01:00
f4cbdd3258 Version 2.1.7 2022-11-10 16:33:48 +01:00
fa41a8de8e Fix voe regex 2022-10-24 23:10:27 +02:00
ce8bc855b9 Update dead sites 2022-10-23 16:56:05 +02:00
03202b2a12 Fix issues link 2022-10-01 12:10:19 +02:00
9f0e1b59ce Update version to 2.1.6 2022-09-30 20:30:42 +02:00
d928d25e09 Update supported websites 2022-09-30 20:29:15 +02:00
266771aa13 Update issues url 2022-09-30 20:26:43 +02:00
d56672d90f Fix mp4upload 2022-09-30 16:02:00 +02:00
a9ea5fe4b2 Add filemoon.sx 2022-09-30 14:59:42 +02:00
bb3f5384d6 Fix url media matching 2022-09-30 14:57:23 +02:00
6989587161 Remove error message when video is not loaded properly 2022-09-12 22:01:50 +02:00
dd9bf71a5c Update dependency versions and bump version to 2.1.5 2022-09-10 18:15:53 +02:00
6da0050df4 Fix false-positive error message 2022-09-10 18:10:48 +02:00
1a7c22ec0e Move from sass to scss for stylesheets 2022-09-10 17:37:12 +02:00
175862b098 Enable error message on non hls streams 2022-08-23 22:07:58 +02:00
fd5a532d0f Change streamzz reliability to low 2022-08-23 21:53:38 +02:00
8c43eedb23 Change doodstream reliability to normal 2022-08-23 21:43:24 +02:00
e027c2e09e Version 2.1.4 2022-08-04 17:06:10 +02:00
f9a0656d4d Optimize redirect functionality 2022-08-04 17:05:50 +02:00
382d8b1268 Add dood.wf as domain for doodstream 2022-08-04 17:03:03 +02:00
5b8639ce6a web development :))))))))))))))))))))))))))) 2022-07-23 17:40:16 +02:00
841c824590 Fix native player error message 2022-07-23 17:28:47 +02:00
2055a3ea81 Update version to 2.1.2 2022-07-23 17:19:31 +02:00
0262d1853c Change error message layout and link color when video cannot be loaded 2022-07-22 13:55:28 +02:00
81da6600e6 Fix error message if video cannot be loaded 2022-07-22 13:55:03 +02:00
817f5b82f9 Remove unused import 2022-07-22 01:49:54 +02:00
9a17fb0d9b Fix domain sometimes not shown in window title when using native player 2022-07-22 01:41:50 +02:00
17f8aab216 Fix mixdrop not working 2022-07-22 01:34:34 +02:00
44d4c9cbcf Update supported websites 2022-07-20 11:56:08 +02:00
b34531b982 Bump version to 2.1.1 2022-07-20 11:55:57 +02:00
e699d3885c Change mp4upload reliability 2022-07-19 11:53:14 +02:00
396038a803 Fix replace video player height 2022-07-19 11:28:11 +02:00
bd64d4ed0b Add doodstream domain 2022-07-19 11:27:03 +02:00
a207c336b0 Add mp4upload support 2022-07-19 11:09:09 +02:00
2460657f2a Remove doodstream comment 2022-07-19 11:06:56 +02:00
698ed5ac3c Add support for doodstream 2022-07-19 11:03:16 +02:00
dc42220f09 Fix streamtape 2022-07-19 10:07:21 +02:00
e146649bbf Use window.location instead of location 2022-07-16 02:34:22 +02:00
424e34190c Rework title of built-in player 2022-07-16 02:32:23 +02:00
c5f4f8b246 Version to 2.1.0 2022-07-13 18:39:26 +02:00
e2b8d884af Fix streamtape regex 2022-07-13 18:21:03 +02:00
b07d0b4be6 Update supported sites table 2022-07-13 18:04:55 +02:00
2d0441997c Add doodstream stubs 2022-07-13 18:04:44 +02:00
53e040db46 Fix addon cannot be disabled via ui 2022-07-13 01:00:33 +02:00
6d1ff3fbea Fix player not working in non firefox browsers 2022-07-13 00:46:40 +02:00
4cf76eb62a Add functions to bypass websites that are redirected to (looking at you voe) 2022-07-13 00:45:52 +02:00
672b920f31 Rename match.ts to matches.ts 2022-07-12 22:51:37 +02:00
1e166b5ecc Add new voe domain (un-block-voe.net) 2022-07-04 12:27:14 +02:00
213b996755 Add new voe domain (voeun-block.net) 2022-06-26 14:08:56 +02:00
ByteDream
de0d8d5a41
Fix typo 2022-06-20 16:22:34 +02:00
ByteDream
1f34f74e11
Fix typo 2022-06-20 10:49:34 +02:00
ByteDream
60a2247b02
Merge pull request #2 from ByteDream/v2
Update to v2
2022-06-20 10:45:23 +02:00
88814fcc83 Fck I've deleted the wrong file 2022-06-20 10:39:55 +02:00
f908e8249b Remove background script from build file 2022-06-18 02:37:05 +02:00
8d57dfbc57 Update copyright year 2022-06-18 02:33:04 +02:00
3aa84ba709 Change Firefox Addon ... names to Firefox Store ... 2022-06-18 02:31:12 +02:00
9ecfbd44e9 Remove background script OwO forgot that 2022-06-18 02:22:38 +02:00
b1bea7687c Change reliability for vidlox 2022-06-18 02:21:34 +02:00
c3148a76a3 Fix typo 2022-06-18 01:50:48 +02:00
210aa5c263 Update supported websites 2022-06-18 01:40:39 +02:00
a55c455fcc Refactor, complete past changes & change manifest back to version 2 2022-06-18 01:29:47 +02:00
923787bedb Update icons 2022-06-18 01:29:19 +02:00
8990e25a72 Redirect to custom video player on every url 2022-06-18 01:13:54 +02:00
9b4bcc6c64 Update to manifest version 3 2022-06-17 00:23:29 +02:00
2e8cce10b5 Add streamz.ws 2022-06-16 23:43:48 +02:00
9d4b52c107 Add vidoza 2022-06-16 23:42:43 +02:00
806320a0de Fix streamzz and remove vidoza 2022-06-16 22:28:28 +02:00
774d3b52d7 Update readme 2022-06-16 19:47:11 +02:00
31787bb3e3 Use web-ext instead of custom bundle & add code check 2022-06-16 19:44:24 +02:00
e64feb5130 Remove readme build 2022-06-16 19:23:30 +02:00
aa51b5d95f Remove old and not working websites 2022-06-16 19:23:13 +02:00
27c03c0b5b Center hls error message 2022-06-16 19:17:44 +02:00
f48adc3db3 Exclude comment matches when matching domain matches 2022-06-16 16:09:35 +02:00
feaf843584 Fix package 2022-06-15 10:53:06 +02:00
0b6021cf52 FIx popup 2022-06-15 10:52:50 +02:00
8b375cae2c Fix build script 2022-06-15 10:52:20 +02:00
c9acb929cc Add readme build 2022-06-15 09:13:45 +02:00
7a69ac83e0 I hate nodejs. 2022-06-14 23:39:26 +02:00
a9a8609cb8 Lots of updates :o 2022-06-14 20:50:51 +02:00
e0d4f8747e Update .gitignore 2022-06-12 17:36:11 +02:00
f8213772c8 Fixed error report message 2022-03-04 20:59:34 +01:00
ByteDream
bce27f2beb
Updated firefox download notice :)))) 2022-02-04 16:10:06 +01:00
7bfae612f9 Updated version 2022-02-04 15:46:44 +01:00
106f4ffda3 Re-added content_security_policy to fix hls playing 2022-02-04 15:40:16 +01:00
b83d287a42 Added newgrounds.com 2022-02-04 15:40:00 +01:00
a39418e506 Added prefix wildcards to url matching 2022-02-04 15:39:22 +01:00
0fc06a78a7 Updated version 2022-02-02 17:42:34 +01:00
90bed8b774 Removed content_security_policy 2022-02-02 09:50:07 +01:00
ac31945372 Removed upstream.com 2022-02-01 22:07:51 +01:00
1683bf2035 Fixed readme build 2022-02-01 22:06:44 +01:00
af54a28737 Hls error style changes & typo fixes 2022-02-01 21:57:12 +01:00
0f8d323c3c Added comment passing on extracting matching sites 2022-02-01 21:55:55 +01:00
4c1bfbde62 Updated vupload.com regex (mp4 => hls) 2022-02-01 21:00:20 +01:00
9889cf79a2 Added badges 2022-02-01 20:49:13 +01:00
230eb074e9 Added gitignore 2022-02-01 20:36:31 +01:00
ByteDream
2a9c00c3a8
Typo fix 2022-01-10 16:10:48 +01:00
ByteDream
16cf6688ec
Updated direct download links using smartrelease :3 2022-01-10 15:16:49 +01:00
4edc3d5f84 Update v1.4.2 2022-01-06 17:32:44 +01:00
e5552910b2 Hls player works now; fixed vupload.com regex 2022-01-06 17:32:30 +01:00
2db8381df7 Added link to issues 2022-01-06 17:30:47 +01:00
ByteDream
55eac3c010
Firefox addon store link reactivation 2021-11-23 12:30:24 +01:00
d93a5ea784 Static 3rd party library version 2021-11-18 20:34:22 +01:00
ByteDream
e41bc2b8ef
Update README.md 2021-11-18 17:18:21 +01:00
052c0d1e78 firefox addon fix 2021-11-06 13:40:06 +01:00
1e7b9b3d8d Fixed empty supported website 2021-10-23 23:07:27 +02:00
fbb9a1d67b Added website support completion for README.md 2021-10-23 23:03:30 +02:00
126aeee1f7 Added automatically browser recognizing for README.md manipulation 2021-10-23 22:20:56 +02:00
56 changed files with 11210 additions and 753 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
MANIFEST_VERSION=2

56
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: ci
on:
push:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install nodejs
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- manifest_version: 2
- manifest_version: 3
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install nodejs
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
env:
MANIFEST_VERSION: ${{ matrix.manifest_version }}
run: npm run build
- name: Upload
uses: actions/upload-artifact@v4
with:
name: stream-bypass-mv${{ matrix.manifest_version }}
path: ./dist
if-no-files-found: error

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea/
dist/
release/
node_modules/

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
auto-install-peers=false
strict-peer-dependencies=false

8
.prettierignore Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
node_modules
/dist
.env
.env.*
!.env.example
package-lock.json

17
.prettierrc.cjs Normal file
View File

@ -0,0 +1,17 @@
// @ts-check
/** @type {import("prettier").Config} */
module.exports = {
useTabs: true,
singleQuote: true,
trailingComma: 'none',
printWidth: 100,
plugins: ['prettier-plugin-svelte', '@ianvs/prettier-plugin-sort-imports'],
/* prettier-plugin-svelte */
overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
/* @ianvs/prettier-plugin-sort-imports */
importOrder: ['^~/(.*)$', '^./(.*)$', ''],
importOrderParserPlugins: ['typescript'],
importOrderTypeScriptVersion: '5.0.0',
importOrderCaseSensitive: false
};

View File

@ -1,9 +0,0 @@
FROM alpine:latest
RUN apk --no-cache add python3 nodejs npm
RUN npm install -g typescript sass
COPY [".", "."]
CMD ["python3", "build.py", "-b", "-c"]

View File

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

205
README.md
View File

@ -2,94 +2,175 @@
A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video. A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.
<p align="center">
<a href="https://github.com/bytedream/stream-bypass/releases/latest">
<img src="https://img.shields.io/github/v/release/ByteDream/stream-bypass?label=Version&style=flat-square" alt="Version">
</a>
<a href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
<img src="https://img.shields.io/amo/users/stream-bypass?label=Firefox%20Users&style=flat-square" alt="Firefox Addon Store">
</a>
<a href="https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb">
<img src="https://img.shields.io/chrome-web-store/users/ddfpfjomnakfckhmilacnbokdaknamdb?style=flat-square&label=Chrome%20Users" alt="Chrome Store">
</a>
<a href="https://github.com/bytedream/stream-bypass/releases/latest">
<img src="https://img.shields.io/github/downloads/ByteDream/stream-bypass/total?label=GitHub%20Downloads&style=flat-square" alt="GitHub Downloads">
</a>
</p>
<p align="center">
<a href="#-introduction">Introduction 📝</a>
<a href="#-installation">Installation 📥</a>
<a href="#-features">Features ✨</a>
<a href="#-supported-websites">Supported Websites 📜</a>
<a href="#%EF%B8%8F-building">Building 🛠️</a>
<a href="#%EF%B8%8F-settings">Settings ⚙️</a>
<a href="#-license">License ⚖</a>
</p>
## 📝 Introduction
This addon replaces the video player from this sides with the native player build-in into the browser or redirects directly to the source video. This addon replaces the video player from this sides with the native player build-in into the browser or redirects directly to the source video.
This has the advantage, that no advertising or popups are shown when trying to interact with the video (playing, skipping, ...) or some sites are showing them even if you do nothing. This has the advantage, that no advertising or popups are shown when trying to interact with the video (playing, skipping, ...) or some sites are showing them even if you do nothing.
Additionally this enables you to download the video by right-clicking it and just choose the download option. Additionally, this enables you to download the video by right-clicking it and just choose the download option.
Supported streaming providers (for a complete list of all supported websites, see [here](SUPPORTED)):
- [streamtape.com](https://streamtape.com/)
- [vidoza.net](https://vidoza.net/)
<details id="example"> <details id="example">
<summary><b>How it's working</b></summary> <summary><b>How it's working:</b></summary>
<img src="example.gif" alt=""> <img src="example.gif" alt="">
</details> </details>
The addon was tested on ## 📥 Installation
- Firefox (92.0)
- Chromium (92.0)
- Opera (78.0)
## Installing ### Official browser stores
### Firefox The best way to install the extension are the official browser extension stores:
Install the addon directly from the [firefox addon store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/). - [Firefox Addon Store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/) (Firefox for Android is supported too!)
- [Chrome Web Store](https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb)
### Chromium / Google Chrome ### Manual installation
1. Download the `stream-bypass-<version>.zip` file 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). - Firefox
2. Go into your browser and type `chrome://extensions` in the address bar. - Download `stream-bypass-<version>-mv2.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that)
3. Turn the developer mode in the top right corner on. - Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar
4. Click Load unpacked. - Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the unzipped directory
5. Choose the cloned / unzipped directory. - Chromium / Google Chrome
> As nearly every browser other than Firefox is based on Chromium, this should be the same for most of them
- Download `stream-bypass-<version>-mv3.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that)
- Go into your browser and type `chrome://extensions` in the address bar
- Turn on the developer mode by checking the switch in the top right corner
- Click `Load unpacked` and choose the unzipped directory
### Opera ## ✨ Features
1. Download the `stream-bypass-<version>.zip` file 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). | Feature | Firefox | Chrome | Firefox for Android |
2. Go into your browser and type `opera://extensions` in the address bar. | --------------------------------------------------------------------------------------------------------------------------------- | ------- | ------ | ------------------- |
3. Turn the developer mode in the top right corner on. | Replace site-speicifc video player with browser native video player | ✔ | ✔ | ✔ |
4. Click Load unpacked. | Support websites that are accessed via a redirect | ✔ | ❌ | ✔ |
5. Choose the cloned / unzipped directory. | Open video in mpv (with [ff2mpv](https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams)) | ✔ | ✔ | ❌ |
## Compiling - ✔️: Supported.
- ❌: Not supported.
If you want to use / install the addon from source, you have to compile the `typescript` and `sass` files yourself. ## 📜 Supported websites
- Compile it [manual](#manual).
- Compile it using [docker](#docker).
### Manual | Site | Firefox & Firefox for Android | Chrome & Chromium based |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| [dropload.io](https://dropload.io) | ✔ | ✔ |
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔ | ⚠ (redirect probably required) |
| [filemoon.to](https://filemoon.to) | ✔ | ✔ |
| [goodstream.uno](https://goodstream.uno) | ✔ | ✔ |
| [kwik.cx](https://kwik.cx) | ✔ | ✔ |
| [loadx.ws](https://loadx.ws) | ✔ | ❌ (background request always required) |
| [luluvdo.com](https://luluvdo.com) | ✔ | ❌ (background request always required) |
| [mixdrop.co](https://mixdrop.co) | ✔ | ✔ |
| [mp4upload.com](https://mp4upload.com) | ✔ | ✔ |
| [newgrounds.com](https://newgrounds.com) | ✔ | ✔ |
| [streama2z.com](https://streama2z.com) | ✔ | ❌ (redirect always required) |
| [streamtape.com](https://streamtape.com) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) |
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | ✔ |
| [supervideo.tv](https://supervideo.tv) | ✔ | ✔ |
| [upstream.to](https://upstream.to) | ✔ | ✔ |
| [vidmoly.to](https://vidmoly.me) | ✔ | ✔ |
| [vidoza.net](https://vidoza.net) | ✔ | ✔ |
| [voe.sx](https://voe.sx) | ✔ | ❌ (redirect always required) |
| [vupload.com](https://vupload.com) | ✔ | ✔ |
For compiling everything bare bones, you need [typescript](https://www.typescriptlang.org/) and [sass](https://sass-lang.com/) installed. - ✔️: Everything ok.
- Compile typescript - ⚠: Works with limitations.
- ❌: Not supported.
_This table might not be 100% accurate, it isn't actively monitored if the addon works for every website!_
Some sites put much effort in obfuscating their code / how they receive the video stream so that it simply cost too much time for me to reverse engineer it and find out how to bypass the native video player of the site.
## 🛠️ Building
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
Requirements:
- `npm` installed.
- A copy of this repository and a shell / console open in the copied directory.
If the requirements are satisfied, you can continue with the following commands:
```shell
# install all dependencies
$ npm install
# build the extension source to the dist/ directory
$ npm run build
# same as build + more optimizations and browser specific settings at release/
$ npm run release:firefox # or "release:chrome" to create a release for chromium based browsers
``` ```
$ tsc -p src
```
- Compile sass (replace `<path to sass file>` with every `.sass` file in the `src` directory)
```
$ sass --no-source-map <path to sass file>
```
The compiled output will be in the `src` directory.
If you want to keep it a little cleaner, you additionally need [python3](https://www.python.org).
- Compile everything with one line
```
$ python3 build.py -b -c
```
The compiled output will remain in a (new created if not existing) `build` directory.
### Docker
For this, you need [docker](https://www.docker.com/) to be installed.
- Build the docker image
```
$ docker build -t stream-bypass .
```
- Compile
```
$ docker run --rm -v build:/build stream-bypass
```
The compiled output will remain in a (new created if not existing) `build` directory.
##### Install ##### Install
If you want to use the addon in Chromium or any browser which is based on it (almost every other, Google Chrome, Opera, ...), follow the steps in [installing](#installing). If you want to use the addon in Chromium or any browser which is based on it, follow the steps in [installation](#-installation).
When using firefox, use the following When using firefox, use the following:
1. Type `about:debugging` in the browser's address bar. 1. Type `about:debugging` in the browser's address bar.
2. Select 'This Firefox' tab (maybe named different, depending on your language). 2. Select 'This Firefox' tab (maybe named different, depending on your language).
3. Under `Temporary Extensions`, click `Load Temporary Add-on`. 3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
4. Choose any file in the directory where the compiled sources are. 4. Choose any file in the directory where the compiled sources are.
## License ## ⚙️ Settings
### <ins>ff2mpv: use mpv to directly play streams</ins>
ff2mpv is located at this repository: https://github.com/woodruffw/ff2mpv
Steps to get it set up:
- In the [Usage](https://github.com/woodruffw/ff2mpv#usage) section of the ff2mpv repository pick the installation instruction for your operating system (Linux/Windows/macOS; you do not need the browser addon).
- Scroll down to `Install manually`
- Follow instructions for Firefox/Chrome
- Edit the `ff2mpv.json` you created:
- Firefox: Add `{55dd42e8-3dd9-455a-b4fe-86664881b10c}` to `allowed_extensions` ->
```
"allowed_extensions": [
"ff2mpv@yossarian.net",
"{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
]
```
- Chrome/Chromium:
- Go To: Settings -> Extensions
- Click on `Details` of the Stream Bypass extension and copy the ID
- Add `chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/` to `allowed_origins` ->
```
"allowed_origins": [
"chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/",
"chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/"
]
```
## ⚖ License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details. This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.

View File

@ -1,14 +0,0 @@
evoload.io
mcloud.to
mixdrop.co
streamtape.com
streamzz.to
thevideome.com
upstream.to
vidlox.me
vidstream.pro
vidoza.net
vivo.st
vivo.sx
voe.sx
vupload.com

107
build.py
View File

@ -1,107 +0,0 @@
#!/usr/bin/python3
import argparse
import json
import sys
from pathlib import Path
import re
import shutil
import subprocess
def load_matches():
matched = []
indexed = False
pattern = re.compile(r"(?<=\[')\S*(?=',)")
for line in open('src/match.ts', 'r'):
if not indexed:
if 'constmatches=[' in line.replace(' ', ''):
indexed = True
else:
match = pattern.findall(line)
if match:
matched.append(match[0])
else:
break
return matched
def write_manifest():
matches = load_matches()
manifest = json.load(open('src/manifest.json', 'r'))
for content_script in manifest['content_scripts']:
content_script['matches'] = [f'*://{match}/*' for match in matches]
domains = []
for match in matches:
toplevel = match.split('.')[-1]
if toplevel not in domains:
domains.append(toplevel)
manifest['content_security_policy'] = f"script-src 'self' blob: https://cdn.jsdelivr.net https://unpkg.com {' '.join(f'*.{toplevel}' for toplevel in domains)}; object-src 'self'"
json.dump(manifest, open('src/manifest.json', 'w'), indent=2)
def write_supported():
open('SUPPORTED', 'w').writelines([f'{match}\n' for match in load_matches()])
def copy_built():
if not shutil.which('tsc'):
sys.stderr.write('The typescript compiler `tsc` could not be found')
sys.exit(1)
elif not shutil.which('sass'):
sys.stderr.write('The sass compiler `sass` could not be found')
sys.exit(1)
write_manifest()
subprocess.call(['tsc', '-p', 'src'])
build_path = Path('build')
if not build_path.is_dir():
build_path.mkdir()
for file in Path('src').rglob('*'):
build_file = build_path.joinpath(str(file)[4:])
if file.is_dir():
if not build_file.exists():
build_file.mkdir(parents=True)
elif file.suffix == '.sass':
css_file = str(file)[:-4] + 'css'
subprocess.call(['sass', '--no-source-map', file, css_file])
shutil.copy(css_file, str(build_path.joinpath(css_file[4:])))
elif file.name == 'tsconfig.json':
continue
elif file.suffix != '.ts':
shutil.copy(str(file), str(build_file))
def clean_build():
for file in Path('src').rglob('*'):
if file.suffix in ['.js', '.css', '.map']:
file.unlink()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--manifest', action='store_true', help='Builds the manifest.json file for addon information in ./src')
parser.add_argument('-s', '--supported', action='store_true', help='Builds the SUPPORTED file with all supported domains in the current directory')
parser.add_argument('-b', '--build', action='store_true', help='Creates a ./build folder and builds all typescript / sass files')
parser.add_argument('-c', '--clean', action='store_true', help='Cleans the ./src folder from .js, .css and .map files')
parsed = parser.parse_args()
if parsed.manifest:
write_manifest()
if parsed.supported:
write_supported()
if parsed.build:
copy_built()
if parsed.clean:
clean_build()
if not parsed.manifest and not parsed.supported and not parsed.build and not parsed.clean:
print('\n'.join(load_matches()))

40
eslint.config.mjs Normal file
View File

@ -0,0 +1,40 @@
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import svelte from 'eslint-plugin-svelte';
import ts from 'typescript-eslint';
export default ts.config(
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
parser: ts.parser
}
}
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'no-undef': 'off'
}
},
{
ignores: [
'.DS_Store',
'node_modules',
'dist',
'release',
'.idea',
'.env',
'.env.*',
'!.env.example',
'package-lock.json'
]
}
);

9556
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

51
package.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "stream-bypass",
"version": "3.1.6",
"displayName": "Stream Bypass",
"author": "bytedream",
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
"scripts": {
"build": "vite build",
"watch": "vite build --watch --mode development --minify false",
"dev": "vite",
"serve:firefox": "web-ext run --start-url \"about:debugging#/runtime/this-firefox\" --source-dir ./dist/",
"serve:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/",
"check": "svelte-check --tsconfig ./tsconfig.json",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/bytedream/stream-bypass.git"
},
"bugs": {
"url": "https://github.com/bytedream/stream-bypass/issues"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@samrum/vite-plugin-web-extension": "^5.1.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tsconfig/svelte": "^5.0.4",
"@types/chrome": "^0.0.320",
"@types/firefox-webext-browser": "^120.0.4",
"eslint": "^9.26.0",
"eslint-config-prettier": "^10.1.3",
"eslint-plugin-svelte": "^3.5.1",
"hls.js": "^1.6.2",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3",
"sass": "^1.87.0",
"svelte": "^5.28.2",
"svelte-check": "^4.1.7",
"svelte-preprocess": "^6.0.3",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.0",
"vite": "^6.3.5",
"web-ext": "^8.6.0"
},
"type": "module"
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,39 @@
import './shared';
import { getMatch, type Match } from '~/lib/match';
import { Redirect, UrlReferer } from '~/lib/settings';
chrome.webRequest.onBeforeSendHeaders.addListener(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
async (details) => {
const referer = await UrlReferer.get(new URL(details.url).hostname);
if (!referer) return;
await UrlReferer.delete(new URL(details.url).hostname);
details.requestHeaders!.push({
name: 'Referer',
value: `https://${referer}/`
});
return { requestHeaders: details.requestHeaders };
},
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] },
['blocking', 'requestHeaders']
);
chrome.webRequest.onBeforeRedirect.addListener(
async (details) => {
// check if redirects origins from a previous redirect
if ((await Redirect.get()) == null) {
let match: Match | null;
if ((match = await getMatch(new URL(details.url).hostname)) !== null) {
await Redirect.set(match);
}
} else {
await Redirect.delete();
}
},
{ urls: ['<all_urls>'], types: ['main_frame', 'sub_frame'] }
);

View File

@ -0,0 +1 @@
import './shared';

View File

@ -0,0 +1,5 @@
chrome.runtime.onMessage.addListener(async (message) => {
if (message.action == 'ff2mpv') {
await chrome.runtime.sendNativeMessage('ff2mpv', { url: message.url });
}
});

View File

@ -0,0 +1,104 @@
import { getMatch, MatchMediaType, type Match } from '~/lib/match';
import { Other, Redirect } from '~/lib/settings';
async function main() {
let match: Match | null;
let redirect = false;
if ((match = await getMatch(window.location.host)) === null) {
if ((match = await Redirect.get()) === null) {
return;
}
redirect = true;
}
// some sites have a javascript based redirect, e.g. example.com redirects to example.org by changing
// window.location.href instead of a 3XX http redirect. an empty body is a sign that such a javascript redirect
// occurred
if (document.body == null) {
await Redirect.set(match);
return;
}
let re = null;
for (const regex of match.regex) {
if ((re = document.body.innerHTML.match(regex)) !== null) {
break;
}
}
if (re === null) {
return;
}
if (redirect) {
await Redirect.delete();
}
let url: string | null;
let urlType: MatchMediaType | null;
try {
const matchResult = await match.match(re);
if (matchResult && typeof matchResult === 'string') {
url = matchResult;
urlType = url.includes('.m3u8') ? MatchMediaType.Hls : MatchMediaType.Native;
} else if (matchResult && typeof matchResult === 'object') {
if (MatchMediaType.Hls in matchResult) {
url = matchResult[MatchMediaType.Hls];
urlType = MatchMediaType.Hls;
} else if (MatchMediaType.Native in matchResult) {
url = matchResult[MatchMediaType.Native];
urlType = MatchMediaType.Native;
}
}
} catch {
return;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!url || !urlType) {
return;
}
// send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application
if (await Other.getFf2mpv()) {
await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url });
}
if (match.replace && urlType != MatchMediaType.Hls) {
// this destroys all intervals that may spawn popups or events
let intervalId = window.setInterval(() => {}, 0);
while (intervalId--) {
clearInterval(intervalId);
}
let timeoutId = window.setTimeout(() => {}, 0);
while (timeoutId--) {
clearTimeout(timeoutId);
}
// clear completed document
document.documentElement.innerHTML = '';
document.body.style.backgroundColor = '#131313';
// video player
const player = document.createElement('video');
player.style.width = '100%';
player.style.height = '100%';
player.controls = true;
player.src = url;
// add video player to document body
document.body.style.margin = '0';
document.body.append(player);
} else {
window.location.assign(
chrome.runtime.getURL(
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
window.location.hostname
}&type=${urlType}`
)
);
}
}
main();

View File

@ -0,0 +1,65 @@
<script lang="ts">
import { onMount } from 'svelte';
import { play } from '~/entries/player/player';
let errorMessage: string | null = $state(null);
let videoElem: HTMLVideoElement;
onMount(async () => {
try {
await play(videoElem);
videoElem.controls = true;
} catch (e) {
errorMessage = e as string;
}
});
</script>
<!-- svelte-ignore a11y_media_has_caption -->
<video id="video" bind:this={videoElem}></video>
{#if errorMessage}
<div id="message-container">
<p>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html errorMessage}
</p>
</div>
{/if}
<!-- eslint-disable -->
<style lang="scss" global>
body {
background-color: #131313;
}
html,
body,
video {
width: 100%;
height: 100%;
margin: 0;
}
video {
position: absolute;
top: 0;
left: 0;
}
#message-container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: white;
text-align: center;
height: 100%;
& a,
& a:visited {
color: inherit;
text-decoration: underline;
}
}
</style>

View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Stream Bypass</title>
</head>
<body>
<script type="module">
import { mount } from 'svelte';
import Player from '~/entries/player/Player.svelte';
mount(Player, {
target: document.body
});
</script>
</body>
</html>

View File

@ -0,0 +1,47 @@
import Hls from 'hls.js';
import { matches, MatchMediaType } from '~/lib/match';
import { UrlReferer } from '~/lib/settings';
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
await UrlReferer.set(new URL(url).hostname, domain);
videoElem.src = url;
}
async function playHls(url: string, domain: string, videoElem: HTMLVideoElement) {
if (videoElem.canPlayType('application/vnd.apple.mpegurl')) {
videoElem.src = url;
} else if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: false,
xhrSetup: async (xhr: XMLHttpRequest, url: string) => {
await UrlReferer.set(new URL(url).hostname, domain);
xhr.open('GET', url);
}
});
hls.loadSource(url);
hls.attachMedia(videoElem);
} else {
throw 'Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>';
}
}
export async function play(videoElem: HTMLVideoElement) {
const urlQuery = new URLSearchParams(window.location.search);
const id = urlQuery.get('id') as string;
const url = decodeURIComponent(urlQuery.get('url') as string);
const domain = urlQuery.get('domain') as string;
const type = urlQuery.get('type') as MatchMediaType;
const match = matches[id];
if (match === undefined) {
throw `Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`;
}
document.title = `Stream Bypass (${domain})`;
if (type === MatchMediaType.Hls) {
await playHls(url, domain, videoElem);
} else if (type === MatchMediaType.Native) {
await playNative(url, domain, videoElem);
}
}

View File

@ -0,0 +1,165 @@
<script lang="ts">
import { matches, type Match } from '~/lib/match';
import { Hosters, Other } from '~/lib/settings';
import Toggle from './toggle.svelte';
let hostersEnabled: boolean;
let hosters: (Match & { active: boolean; disabled: boolean })[] = [];
(async () => {
hostersEnabled = !(await Hosters.getAllDisabled());
const disabled = await Hosters.getDisabled();
hosters = Object.values(matches).map((m: any) => {
m['active'] = disabled.findIndex((p) => p.id == m.id) == -1;
return m;
}) as typeof hosters;
})();
let isMobile: boolean;
(async () => {
isMobile = (await browser.runtime.getPlatformInfo()).os === 'android';
})();
let ff2mpvEnabled: boolean;
(async () => {
ff2mpvEnabled = (await Other.getFf2mpv()) as boolean;
})();
</script>
<main
style={isMobile
? 'height: 100vh; display: flex; flex-direction: column; align-items: center'
: 'height: 500px'}
>
<fieldset>
<legend>Hoster</legend>
<div class="setting-container" style={isMobile ? 'grid-column-gap: 5rem' : ''}>
<label for="hosters-enabled">Enabled</label>
<div>
<Toggle
bind:checked={hostersEnabled}
id="hosters-enabled"
onChange={() => Hosters.setAll(hostersEnabled)}
/>
</div>
<hr />
{#each hosters as hoster, i (hoster.id)}
<label for="hoster-{i}" style="cursor: {hostersEnabled ? 'pointer' : 'default'}"
>{hoster.name}</label
>
<div>
<Toggle
bind:checked={hoster.active}
disabled={!hostersEnabled}
id="hoster-{i}"
onChange={async () => {
if (hoster.active) {
await Hosters.enable(hoster);
} else {
await Hosters.disable(hoster);
}
}}
></Toggle>
</div>
{/each}
</div>
</fieldset>
{#if !isMobile}
<fieldset>
<legend>Other</legend>
<div class="setting-container">
<label for="ff2mpv">ff2mpv</label>
<div>
<Toggle
bind:checked={ff2mpvEnabled}
id="ff2mpv"
onChange={async () => {
ff2mpvEnabled = !ff2mpvEnabled;
if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) {
await Other.setFf2mpv(ff2mpvEnabled);
ff2mpvEnabled = !ff2mpvEnabled;
}
}}
></Toggle>
<a
class="info-questionmark"
href="https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams"
>?</a
>
</div>
</div>
</fieldset>
{/if}
<a id="report-notice" href="https://github.com/ByteDream/stream-bypass/issues"
>Report issues or requests</a
>
</main>
<!-- eslint-disable -->
<style lang="scss" global>
body {
background-color: #2b2a33;
color: white;
font-weight: bold;
margin: 0;
padding: 0 8px;
}
fieldset {
border-radius: 5px;
border-color: gray;
}
#report-notice {
border: none;
color: white;
display: block;
font-weight: lighter;
font-size: 0.8rem;
text-align: center;
padding: 5px 0;
}
.settings {
display: flex;
flex-direction: column;
align-items: center;
margin: 10px 0;
}
.header {
font-size: 16px;
margin: 5px 0;
text-align: center;
}
.setting-container {
display: grid;
grid-template-columns: auto auto;
grid-column-gap: 5px;
grid-row-gap: 4px;
align-items: end;
width: 100%;
& > label {
height: 34px;
margin: 0;
user-select: none;
cursor: pointer;
display: flex;
align-items: center;
}
& > hr {
grid-column: 1 / span 2;
width: 100%;
}
}
.info-questionmark {
display: inline-block;
transform: translateX(-40%) translateY(-100%);
color: black;
text-decoration: none;
}
</style>

View File

@ -0,0 +1,18 @@
<!doctype html>
<html style="overflow-y: hidden" lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Stream Bypass</title>
</head>
<body style="overflow-y: scroll">
<script type="module">
import { mount } from 'svelte';
import Popup from '~/entries/popup/Popup.svelte';
mount(Popup, {
target: document.body
});
</script>
</body>
</html>

View 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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,46 +0,0 @@
function hasSuffix(content: string, suffix: string) {
return content.indexOf(suffix, content.length - suffix.length) !== -1
}
// @ts-ignore
chrome.storage.local.get(['all', 'disabled'], function (result) {
let keys = Object.keys(result)
if (keys.indexOf('all') !== -1 && !result['all']) {
return
}
// @ts-ignore
for (let match of matches) {
let domain = match[0] as string
if (window.location.href.indexOf(domain) !== -1) {
if (keys.indexOf('disabled') === -1 || result['disabled'].indexOf(domain) === -1) {
let regex = match[1] as RegExp
let matchClass = match[2] as Match
let reliability = match[3] as Reliability
let re
if (regex !== null) {
if ((re = document.body.innerHTML.match(regex)) === null) {
continue
}
} else {
re = document.body.innerHTML.match(regex)
}
if (matchClass === null) {
if (regex === null) {
location.assign(document.body.innerHTML)
} else {
// @ts-ignore
location.assign(hasSuffix(re[0], 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${re[0]}`) : re[0])
}
} else {
matchClass.match(re).then(function (path) {
// @ts-ignore
location.assign(hasSuffix(path, 'm3u8') ? chrome.runtime.getURL(`res/hls.html?domain=${domain}&reliability=${reliability}#${path}`) : path)
})
}
}
return
}
}
})

441
src/lib/match.ts Normal file
View File

@ -0,0 +1,441 @@
import { Hosters, Redirect, TmpHost } from './settings';
import { lastPathSegment } from './util/extract';
import { unpack } from './util/userspace';
export interface Match {
name: string;
id: string;
domains: string[];
replace?: boolean;
regex: RegExp[];
notice?: string;
match(
match: RegExpMatchArray
): Promise<
string | { [MatchMediaType.Hls]: string } | { [MatchMediaType.Native]: string } | null
>;
// allow other properties that may be implemented by the objects that use this interface declaration
[other: string]: any;
}
export enum MatchMediaType {
Hls = 'hls',
Native = 'native'
}
export const Doodstream: Match = {
name: 'Doodstream',
id: 'doodstream',
domains: [
'doodstream.com',
'dood.pm',
'dood.ws',
'dood.wf',
'dood.cx',
'dood.sh',
'dood.watch',
'dood.work',
'dood.to',
'dood.so',
'dood.la',
'dood.li',
'dood.re',
'dood.yt',
'doods.pro',
'ds2play.com',
'dooood.com',
'd000d.com'
],
replace: true,
regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
match: async function (match: RegExpMatchArray) {
const response = await fetch(`https://${window.location.host}${match[1]}`, {
headers: {
Range: 'bytes=0-'
},
referrer: `https://${window.location.host}/e/${
window.location.pathname.split('/').slice(-1)[0]
}`
});
return `${await response.text()}1234567890${match[2]}${Date.now()}`;
}
};
export const DropLoad: Match = {
name: 'Dropload',
id: 'dropload',
domains: ['dropload.io'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Filemoon: Match = {
name: 'Filemoon',
id: 'filemoon',
domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'],
regex: [/(?<=<iframe\s*src=")\S*(?=")/s, /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
replace: true,
match: async function (match: RegExpMatchArray) {
if (window.location.host.startsWith('filemoon')) {
await TmpHost.set(new URL(match[0]).host, Filemoon);
return null;
}
await TmpHost.delete();
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:")\S*(?=")/)![0];
}
};
export const GoodStream: Match = {
name: 'Goodstream',
id: 'goodstream',
domains: ['goodstream.uno'],
regex: [/(?<=file:\s+").*(?=")/g],
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const Kwik: Match = {
name: 'Kwik',
id: 'kwik',
domains: ['kwik.cx'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=source=').*(?=')/)![0];
}
};
export const LoadX: Match = {
name: 'LoadX',
id: 'loadx',
domains: ['loadx.ws'],
regex: [/./gm],
match: async () => {
const hash = encodeURIComponent(lastPathSegment(window.location.href));
const response = await fetch(
`https://${window.location.host}/player/index.php?data=${hash}&do=getVideo`,
{
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}
);
const responseJson = await response.json();
const videoSource: string = responseJson['videoSource'];
// extension of extracted url is '.txt', so we have to manually specify that it's a hls
return { [MatchMediaType.Hls]: videoSource.replace('\\/', '/') };
}
};
export const Luluvdo: Match = {
name: 'Luluvdo',
id: 'luluvdo',
domains: ['luluvdo.com'],
regex: [/./gm],
match: async () => {
const requestBody = new FormData();
requestBody.set('op', 'embed');
requestBody.set('file_code', lastPathSegment(window.location.href));
const response = await fetch(`https://${window.location.host}/dl`, {
method: 'POST',
body: requestBody,
referrer: window.location.href
});
let unpacked;
const responseText = await response.text();
const evalMatch = responseText.match(/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms)!;
// sometimes is packed, sometimes it's not. looks like someone forgets to obfuscate the code when pushing to
// production
if (evalMatch) {
unpacked = await unpack(evalMatch[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
} else {
unpacked = responseText;
}
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Mixdrop: Match = {
name: 'Mixdrop',
id: 'mixdrop',
domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
return `https:${url}`;
}
};
export const Mp4Upload: Match = {
name: 'Mp4Upload',
id: 'mp4upload',
domains: ['mp4upload.com'],
replace: true,
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
}
};
export const Newgrounds: Match = {
name: 'Newgrounds',
id: 'newgrounds',
domains: ['newgrounds.com'],
regex: [/.*/gm],
match: async () => {
const id = window.location.pathname.split('/').slice(-1)[0];
const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const json = await response.json();
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']);
}
};
export const StreamA2z: Match = {
name: 'Stream2Az',
id: 'stream2az',
domains: ['streama2z.com', 'streama2z.xyz'],
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
match: async function (match: RegExpMatchArray) {
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
await Redirect.set(StreamA2z);
return null;
}
return match[0];
}
};
export const Streamtape: Match = {
name: 'Streamtape',
id: 'streamtape',
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
regex: [/id=.*(?=')/gm],
match: async function (match: RegExpMatchArray) {
let i = 0;
while (i < match.length) {
if (match[++i - 1] == match[i]) {
return `https://streamtape.com/get_video?${match[i]}`;
}
}
// use the old method as fallback
return `https://streamtape.com/get_video?${match.reverse()[0]}`;
}
};
export const Streamzz: Match = {
name: 'Streamzz',
id: 'streamzz',
domains: ['streamzz.to', 'streamz.ws'],
regex: [/(?<=\|)\w{2,}/gm],
match: async function (match: RegExpMatchArray) {
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
match.sort((a, b) => b.length - a.length)[0]
}.dll`;
}
};
export const SuperVideo: Match = {
name: 'Supervideo',
id: 'supervideo',
domains: ['supervideo.cc', 'supervideo.tv'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Upstream: Match = {
name: 'Upstream',
id: 'upstream',
domains: ['upstream.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Vidmoly: Match = {
name: 'Vidmoly',
id: 'vidmoly',
domains: ['vidmoly.me', 'vidmoly.to'],
regex: [/(?<=file:").+\.m3u8/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const Vidoza: Match = {
name: 'Vidoza',
id: 'vidoza',
domains: ['vidoza.net', 'videzz.net'],
regex: [/(?<=src:\s?").+?(?=")/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const Voe: Match = {
name: 'Voe',
id: 'voe',
domains: ['voe.sx'],
regex: [
// voe.sx
/(?<=window\.location\.href\s=\s')\S*(?=')/gm,
// whatever site voe.sx redirects to
/(?<=<script type="application\/json">).*(?=<\/script>)/m
],
match: async function (match: RegExpMatchArray) {
if (window.location.host === 'voe.sx') {
const redirectUrl = new URL(match[0]);
await TmpHost.set(redirectUrl.host, Voe);
return null;
} else {
let json = match[0];
json = JSON.parse(json);
let deobfuscated = json[0];
deobfuscated = this.rot13(deobfuscated);
deobfuscated = this.removeSpecialSequences(deobfuscated);
deobfuscated = atob(deobfuscated);
deobfuscated = this.shiftString(deobfuscated);
deobfuscated = deobfuscated.split('').reverse().join('');
deobfuscated = atob(deobfuscated);
const payload = JSON.parse(deobfuscated);
return payload['source'];
}
},
rot13: function (encrypted: string) {
let decrypted = '';
for (let i = 0; i < encrypted.length; i++) {
let char = encrypted.charCodeAt(i);
if (char >= 65 && char <= 90) {
char = ((char - 65 + 13) % 26) + 65;
} else if (char >= 97 && char <= 122) {
char = ((char - 97 + 13) % 26) + 97;
}
decrypted += String.fromCharCode(char);
}
return decrypted;
},
removeSpecialSequences: function (input: string) {
return input
.replaceAll(/@\$/g, '')
.replaceAll(/\^\^/g, '')
.replaceAll(/~@/g, '')
.replaceAll(/%\?/g, '')
.replaceAll(/\*~/g, '')
.replaceAll(/!!/g, '')
.replaceAll(/#&/g, '');
},
shiftString: function (input: string) {
let shifted = '';
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
const shiftedChar = char - 3;
shifted += String.fromCharCode(shiftedChar);
}
return shifted;
}
};
export const Vupload: Match = {
name: 'Vupload',
id: 'vupload',
domains: ['vupload.com'],
regex: [/(?<=src:\s?").+?(?=")/gm],
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const matches = {
[Doodstream.id]: Doodstream,
[DropLoad.id]: DropLoad,
[Filemoon.id]: Filemoon,
[GoodStream.id]: GoodStream,
[Kwik.id]: Kwik,
[LoadX.id]: LoadX,
[Luluvdo.id]: Luluvdo,
[Mixdrop.id]: Mixdrop,
[Mp4Upload.id]: Mp4Upload,
[Newgrounds.id]: Newgrounds,
[StreamA2z.id]: StreamA2z,
[Streamtape.id]: Streamtape,
[Streamzz.id]: Streamzz,
[SuperVideo.id]: SuperVideo,
[Upstream.id]: Upstream,
[Vidmoly.id]: Vidmoly,
[Vidoza.id]: Vidoza,
[Voe.id]: Voe,
[Vupload.id]: Vupload
};
export async function getMatch(domain: string): Promise<Match | null> {
if (await Hosters.getAllDisabled()) {
return null;
}
for (const match of Object.values(matches)) {
if (
match.domains.indexOf(domain) !== -1 &&
!(await Hosters.getDisabled().then((d) => d.find((p) => p.id == match.id)))
) {
return match;
}
}
const tmpHost = await TmpHost.get();
if (tmpHost && tmpHost[0] === domain) {
return tmpHost[1];
}
return null;
}

112
src/lib/settings.ts Normal file
View File

@ -0,0 +1,112 @@
import { matches, type Match } from './match';
export const Hosters = {
getDisabled: async () => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
return disabled.map((id) => matches[id]).filter((m) => m !== undefined);
},
disable: async (match: Match) => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
const index = disabled.indexOf(match.id);
if (index === -1) {
disabled.push(match.id);
await storageSet('hosters.disabled', disabled);
}
},
enable: async (match: Match) => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
const index = disabled.indexOf(match.id);
if (index !== -1) {
disabled.splice(index, 1);
await storageSet('hosters.disabled', disabled);
}
},
getAllDisabled: async () => {
return await storageGet<boolean>('hosters.allDisabled', false);
},
setAll: async (enable: boolean) => {
await storageSet('hosters.allDisabled', !enable);
}
};
export const Redirect = {
get: async (): Promise<Match | null> => {
return matches[(await storageGet('redirect')) as string] || null;
},
set: async (match: Match) => {
await storageSet('redirect', match.id);
},
delete: async () => {
await storageDelete('redirect');
}
};
export const TmpHost = {
get: async (): Promise<[string, Match] | null> => {
const tmphost = await storageGet<[string, number]>('tmphost');
if (tmphost === undefined) {
return null;
}
return [tmphost[0], matches[tmphost[1]]];
},
set: async (domain: string, match: Match) => {
await storageSet('tmphost', [domain, match.id]);
},
delete: async () => {
await storageDelete('tmphost');
}
};
export const UrlReferer = {
get: async (url: string): Promise<string | null> => {
return (await storageGet(`urlReferer.${url}`)) || null;
},
set: async (url: string, referer: string) => {
await storageSet(`urlReferer.${url}`, referer);
},
delete: async (url: string) => {
await storageDelete(`urlReferer.${url}`);
}
};
export const Other = {
getFf2mpv: async () => {
return await storageGet('other.ff2mpv', false);
},
setFf2mpv: async (enable: boolean) => {
await storageSet('other.ff2mpv', enable);
}
};
async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
let resolve: (value: T | undefined) => void;
const promise = new Promise<T | undefined>((r) => (resolve = r));
chrome.storage.local.get(key, (entry) => {
const value = entry[key];
resolve(value === undefined ? defaultValue : value);
});
return promise;
}
async function storageSet<T>(key: string, value: T) {
let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r));
const obj = {
[key]: value
};
chrome.storage.local.set(obj, () => resolve());
return promise;
}
async function storageDelete(key: string) {
let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r));
chrome.storage.local.remove(key, () => resolve());
return promise;
}

6
src/lib/util/extract.ts Normal file
View File

@ -0,0 +1,6 @@
export function lastPathSegment(path: string): string {
while (path.endsWith('/')) {
path = path.slice(0, -1);
}
return path.substring(path.lastIndexOf('/') + 1);
}

92
src/lib/util/userspace.ts Normal file
View 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;
}

View File

@ -1,51 +0,0 @@
{
"manifest_version": 2,
"name": "Stream Bypass",
"author": "ByteDream",
"description": "",
"version": "1.2.0",
"homepage_url": "https://github.com/ByteDream/stream-bypass",
"browser_specific_settings": {
"gecko": {
"id": "{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
}
},
"content_scripts": [
{
"all_frames": true,
"matches": [
"*://evoload.io/*",
"*://mcloud.to/*",
"*://mixdrop.co/*",
"*://streamtape.com/*",
"*://streamzz.to/*",
"*://thevideome.com/*",
"*://upstream.to/*",
"*://vidlox.me/*",
"*://vidstream.pro/*",
"*://vidoza.net/*",
"*://vivo.st/*",
"*://vivo.sx/*",
"*://voe.sx/*",
"*://vupload.com/*"
],
"js": [
"match.js",
"index.js"
],
"run_at": "document_end"
}
],
"permissions": [
"storage"
],
"content_security_policy": "script-src 'self' blob: https://cdn.jsdelivr.net https://unpkg.com *.io *.to *.co *.com *.me *.pro *.net *.st *.sx; object-src 'self'",
"browser_action": {
"default_icon": "icons/stream-bypass.png",
"default_title": "Stream Bypass",
"default_popup": "popup/popup.html"
},
"icons": {
"128": "icons/stream-bypass.png"
}
}

87
src/manifest.ts Normal file
View 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}`);
}

View File

@ -1,131 +0,0 @@
enum Reliability {
LOW = 1,
NORMAL,
HIGH
}
interface Match {
match(match: RegExpMatchArray): Promise<string>
}
class Evoload implements Match {
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch('https://evoload.io/SecurePlayer', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({code: code})
})
const json = await response.json()
return json['stream']['src']
}
}
class MCloud implements Match {
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch(`https://mcloud.to/info/${code}?skey=${match[0]}`, {
headers: {
'Content-Type': 'application/json'
},
referrer: `https://mcloud.to/embed/${code}`
})
const json = await response.json()
return json['media']['sources'][0]['file']
}
}
class Mixdrop implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
}
}
class Streamtape implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://streamtape.com/get_video?${match[0]}`
}
}
class TheVideoMe implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://thevideome.com/${match[5]}.mp4`
}
}
class Upstream implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://${match[48]}.upstreamcdn.co/hls/,${match.sort((a, b) => {return b.length - a.length})[0]},.urlset/master.m3u8`
}
}
class Vidstream implements Match {
async match(match: RegExpMatchArray): Promise<string> {
const code = window.location.pathname.split('/').slice(-1)[0]
const response = await fetch(`https://vidstream.pro/info/${code}?skey=${match[0]}`, {
headers: {
'Content-Type': 'application/json'
},
referrer: `https://vidstream.pro/embed/${code}`
})
const json = await response.json()
return json['media']['sources'][0]['file']
}
}
class Vivo implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return this.rot47(decodeURIComponent(match[0]))
}
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
rot47(encoded: string): string {
const s = []
for(let i = 0; i < encoded.length; i++) {
const j = encoded.charCodeAt(i)
if((j >= 33) && (j <= 126)) {
s[i] = String.fromCharCode(33+((j+ 14)%94))
} else {
s[i] = String.fromCharCode(j)
}
}
return s.join('')
}
}
class Vupload implements Match {
async match(match: RegExpMatchArray): Promise<string> {
return `https://www3.megaupload.to/${match[0]}/v.mp4`
}
}
// all domains to match. the matches must be structured like this:
// [domain, regex match (can be null), class to call after match (can be null), reliability]
// => the domain which should be redirected
// => the regex gets called if the user visits a site with the given domain and matches the websites document body.
// if the regex is null, the complete document body gets handled as one big regex match
// => the class to call when the regex was parsed successfully. the class has to implement the `Match` interface.
// if the class is null, the user gets redirected to the first regex match element
// => the reliability shows how reliable a stream redirect is. for example, vivo.sx works nearly every time whereas
// upstream.to works only sometimes because of a security mechanism they're using (CORS) which currently can't be bypassed
//
// every match HAS to be on an separate line (for automatically manifest generation)
const matches = [
['evoload.io', null, new Evoload(), Reliability.NORMAL],
['mcloud.to', new RegExp(/(?<=')\w+(?=';)/gm), new MCloud(), Reliability.NORMAL],
['mixdrop.co', new RegExp(/(?<=\|)\w{2,}/gm), new Mixdrop(), Reliability.HIGH],
['streamtape.com', new RegExp(/id=\S*(?=')/gm), new Streamtape(), Reliability.NORMAL],
['streamzz.to', new RegExp(/https?:\/\/get.streamz.tw\/getlink-\w+\.dll/gm), null, Reliability.NORMAL],
['thevideome.com', new RegExp(/(?<=\|)\w{2,}/gm), new TheVideoMe(), Reliability.NORMAL],
['upstream.to', new RegExp(/(?<=\|)\w{2,}/gm), new Upstream(), Reliability.LOW],
['vidlox.me', new RegExp(/(?<=\[")\S+?(?=")/gm), null, Reliability.NORMAL],
['vidstream.pro', new RegExp(/(?<=')\w+(?=';)/gm), new Vidstream(), Reliability.LOW],
['vidoza.net', new RegExp(/(?<=src:(\s*)?")\S*(?=")/gm), null, Reliability.NORMAL],
['vivo.st', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
['vivo.sx', new RegExp(/(?<=source:\s')(\S+)(?=')/gm), new Vivo(), Reliability.HIGH],
['voe.sx', new RegExp(/https?:\/\/\S*m3u8(?=")/gm), null, Reliability.HIGH],
['vupload.com', new RegExp(/(?<=class\|)\w*/gm), new Vupload(), Reliability.NORMAL]
]

View File

@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="popup.css">
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
<script src="../match.js"></script>
</head>
<body>
<div id="container">
<div id="all">
<div class="buttons">
<a>On</a>
<a>Off</a>
</div>
</div>
<hr>
<table id="sub-container">
</table>
</div>
<script src="./popup.js"></script>
</body>
</html>

View File

@ -1,47 +0,0 @@
body
background-color: #2b2a33
font-weight: bold
max-height: 500px
overflow-x: hidden
overflow-y: auto
a, p
color: white
font-size: 16px
margin: 5px 0
a
border: 1px solid #281515
cursor: pointer
font-weight: normal
padding: 5px 8px
&.active
background-color: rgba(255, 65, 65, 0.74)
cursor: default
&.disabled
background-color: grey
cursor: not-allowed
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

View File

@ -1,163 +0,0 @@
function enableAll(enable: boolean) {
// @ts-ignore
chrome.storage.local.set({'all': enable})
// @ts-ignore
for (let button of document.getElementById('sub-container').getElementsByTagName('a')) {
enable ? button.classList.remove('disabled') : button.classList.add('disabled')
}
}
function enableOne(website: string, enable: boolean) {
// @ts-ignore
chrome.storage.local.get(['disabled'], function (result) {
let disabled: string[] = Object.keys(result).length === 0 ? [] : result['disabled']
if (enable && disabled.indexOf(website) !== -1) {
disabled.splice(disabled.indexOf(website), 1)
} else if (!enable && disabled.indexOf(website) === -1) {
disabled.push(website)
} else {
return
}
// @ts-ignore
chrome.storage.local.set({'disabled': disabled})
})
}
// @ts-ignore
chrome.storage.local.get(['all', 'disabled'], function (result) {
let allDisabled = result['all'] !== undefined && !result['all']
let disabled = new Map()
if (allDisabled) {
// @ts-ignore
for (let match of matches) {
disabled.set(match[0], false)
}
} else {
if (Object.keys(result).indexOf('disabled') !== -1) {
for (let disable of result['disabled']) {
disabled.set(disable, false)
}
}
}
let subContainer = document.getElementById('sub-container')
// @ts-ignore
for (let match of matches) {
let row = document.createElement('tr')
let name = document.createElement('td')
let nameValue = document.createElement('p')
nameValue.innerText = match[0]
switch (match[3]) {
case 1: // low
nameValue.classList.add('low-reliability')
// @ts-ignore
tippy(nameValue, {
content: 'Low reliability: Errors may occur often'
})
break
case 2: // normal
nameValue.classList.add('normal-reliability')
// @ts-ignore
tippy(nameValue, {
content: 'Normal reliability: Save to use but errors may occur'
})
break
case 3: //high
nameValue.classList.add('high-reliability')
// @ts-ignore
tippy(nameValue, {
content: 'High reliability: Errors are very unlikely to happen'
})
break
}
let buttons = document.createElement('td')
buttons.classList.add('buttons')
let on = document.createElement('a')
on.innerText = 'On'
// @ts-ignore
let onTippy = tippy(on, {
content: `Enable ${match[0]}`,
onMount: () => {
if (on.classList.contains('active') || off.classList.contains('disabled')) {
onTippy.hide()
}
}
})
let off = document.createElement('a')
off.innerText = 'Off'
// @ts-ignore
let offTippy = tippy(off, {
content: `Disable ${match[0]}`,
onMount: () => {
if (off.classList.contains('active') || off.classList.contains('disabled')) {
offTippy.hide()
}
}
})
disabled.has(match[0]) ? off.classList.add('active') : on.classList.add('active')
if (allDisabled) {
on.classList.add('disabled')
off.classList.add('disabled')
}
on.onclick = function () {
if (!on.classList.contains('disabled')) {
enableOne(match[0], true)
on.classList.add('active')
off.classList.remove('active')
}
}
off.onclick = function () {
if (!off.classList.contains('disabled')) {
enableOne(match[0], false)
on.classList.remove('active')
off.classList.add('active')
}
}
name.append(nameValue)
buttons.append(on, off)
row.append(name, buttons)
subContainer.append(row)
}
let allButtons = document.getElementById('all').getElementsByTagName('a')
let allOn = allButtons[0]
allButtons[0].onclick = function () {
if (!allButtons[0].classList.contains('disabled')) {
enableAll(true)
allButtons[0].classList.add('active')
allButtons[1].classList.remove('active')
}
}
// @ts-ignore
let allOnTippy = tippy(allOn, {
content: 'Enable all websites',
onMount: () => {
if (allButtons[0].classList.contains('active')) {
allOnTippy.hide()
}
}
})
allButtons[1].onclick = function () {
if (!allButtons[1].classList.contains('disabled')) {
enableAll(false)
allButtons[0].classList.remove('active')
allButtons[1].classList.add('active')
}
}
// @ts-ignore
let allOffTippy = tippy(allButtons[1], {
content: 'Disable all websites',
onMount: () => {
if (allButtons[1].classList.contains('active')) {
allOffTippy.hide()
}
}
})
allDisabled ? allButtons[1].classList.add('active') : allButtons[0].classList.add('active')
})

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>m3u8</title>
<link rel="stylesheet" href="/res/hls.css">
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<video id="video"></video>
<p id="message" hidden></p>
<script src="/res/hls.js"></script>
</body>
</html>

View File

@ -1,9 +0,0 @@
html, body, video
width: 100%
height: 100%
margin: 0
video
position: absolute
top: 0
left: 0

View File

@ -1,60 +0,0 @@
function showMessage(message: string) {
let messageElement = document.getElementById('message') as HTMLParagraphElement
messageElement.innerHTML = message
messageElement.hidden = false
document.getElementById('video').hidden = true
}
function loadHls() {
let url = window.location.hash.substring(1)
let video = document.getElementById('video') as HTMLVideoElement;
video.controls = true
if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url
// @ts-ignore
} else if (Hls.isSupported()) {
// @ts-ignore
let hls = new Hls()
hls.loadSource(url)
hls.attachMedia(video)
let searchParams = new URLSearchParams(window.location.search)
let rawReliability = parseInt(searchParams.get('reliability'))
let thirdPartyFallback = setTimeout(() => {
let message: string
switch (rawReliability) {
case 1: // low
message = `The reliability for this domain is low, so errors like this are common.
Try to choose another streaming provider (if existent) or deactivate the addon for this domain (${searchParams.get('domain')}) and try again`
break
case 2: // normal
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
break
case 3: // high
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>.
When your using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
so if this is the case just try to reload the page and see if you it's working then`
break
}
// shows a message if hls could not be loaded
showMessage(`Could not load hls video. ${message}`)
}, rawReliability * 3000)
// @ts-ignore
hls.on(Hls.Events.MANIFEST_PARSED, () => {
clearTimeout(thirdPartyFallback)
document.getElementById('video').hidden = false
document.getElementById('message').hidden = true
})
} else {
// shows a message if hls is not supported
showMessage(`Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`)
}
}
loadHls()

View File

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"removeComments": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015.collection",
"es2015.promise"
]
},
"exclude": [
"node_modules"
],
}

3
src/vite-env.d.ts vendored Normal file
View 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
View File

@ -0,0 +1,7 @@
import sveltePreprocess from 'svelte-preprocess';
export default {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: sveltePreprocess()
};

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"target": "es2021",
"module": "esnext",
"useDefineForClassFields": true,
"resolveJsonModule": true,
"removeComments": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
},
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}

36
vite.config.ts Normal file
View File

@ -0,0 +1,36 @@
import { fileURLToPath } from 'url';
import webExtension from '@samrum/vite-plugin-web-extension';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { defineConfig, loadEnv, type PluginOption } from 'vite';
import { matches } from './src/lib/match';
import { getManifest } from './src/manifest';
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
svelte() as PluginOption,
webExtension({
manifest: getManifest(Number(env.MANIFEST_VERSION)),
additionalInputs: {
html: [
{
fileName: 'src/entries/player/player.html',
webAccessible: {
matches:
Number(env.MANIFEST_VERSION) === 3
? Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`))
: ['<all_urls>']
}
}
]
}
}) as unknown as PluginOption
],
resolve: {
alias: [{ find: '~', replacement: fileURLToPath(new URL('./src', import.meta.url)) }]
}
};
});