Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9a0197f7f | |||
| d8bb70890a | |||
| 45900b2e86 | |||
| 3058847f31 | |||
| 082f30722c | |||
| 39a6fffd50 | |||
| 6109cd5543 | |||
| de38196a77 | |||
| d2b6086155 | |||
| 66c638425d | |||
| b2da03f9a1 | |||
| 76a4217aab | |||
| cac464cc65 | |||
| 7bd8f01a1b | |||
| b7c5e020b6 | |||
| fdd1ca350e | |||
| 607326e6d6 | |||
| 47f6de2de8 | |||
| d159fb0aa4 | |||
| 290733a85a | |||
| 84eb32ab61 | |||
| 927a4e3ae3 | |||
| 5926177920 | |||
| 9a29316641 | |||
| c300125f64 | |||
| 59f2ffec57 | |||
| 6dff691c25 | |||
| c4085f9ac8 | |||
| 422702b16f | |||
| c3dad3681c | |||
| 6eb3259ec5 | |||
| 68e445a738 | |||
| e864bc6100 | |||
| 3ac73ac3b4 | |||
| 6aaf960c28 | |||
| 8a13dea681 | |||
| 8d575241fe | |||
| 50f400b8b9 | |||
| f6fcfd354a | |||
| c081127d35 | |||
| 416fceba88 | |||
| a9cf03c176 | |||
| 8852318483 | |||
| 7067aaf4a0 | |||
| bf723e2ed6 | |||
| 9c80362a1c | |||
| ae738ff32f | |||
| 16d4f2956a | |||
|
|
f6c6102436 | ||
|
|
f1525817b1 | ||
| 1e1d8cdfa6 | |||
| 47eed2d12b | |||
| 67e03eafaa | |||
| db0ccd9b56 | |||
| fd3eda9c61 | |||
| e522916585 | |||
| b9514284da | |||
| 06beaa6ebe | |||
| 6a56c37dd2 | |||
| 018b381732 | |||
| 696ec22471 | |||
| e379113aa4 | |||
| 84605ceb30 | |||
| 67c492db47 | |||
| ae2f196b06 | |||
| 298e9308ce | |||
| 3cd4c6b6b7 | |||
| 6286acf85c | |||
| 94eee79218 | |||
| 2f186bda07 | |||
| 1c9f95cebc | |||
| c57cd03407 | |||
| 64cf565da3 | |||
| bf8a7eb602 | |||
| c643a39e2d | |||
| fcfbc41fdb | |||
| 1251f079f5 | |||
| ccfc77167b | |||
| a10066458f | |||
| 6a8c705b06 | |||
| 76a5bac7fd | |||
| 3aca863a4b | |||
| fe14edc0ef | |||
| fffe23638c | |||
| 5214228a72 | |||
| 7000d5a08b | |||
| a21f799e7d | |||
| 1fb85313dd | |||
| 6e7410b088 | |||
|
|
0054f5da0e | ||
| 48dc192964 | |||
|
|
7c45cacd36 | ||
| 00514e4e81 | |||
| 4c1d0d0b63 | |||
|
|
a920832945 | ||
|
|
7d8d8b6614 | ||
|
|
b4052834f2 | ||
|
|
9900c33243 | ||
|
|
98709bc934 | ||
| f4cbdd3258 | |||
| fa41a8de8e | |||
| ce8bc855b9 | |||
| 03202b2a12 | |||
| 9f0e1b59ce | |||
| d928d25e09 | |||
| 266771aa13 | |||
| d56672d90f | |||
| a9ea5fe4b2 | |||
| bb3f5384d6 | |||
| 6989587161 | |||
| dd9bf71a5c | |||
| 6da0050df4 | |||
| 1a7c22ec0e | |||
| 175862b098 | |||
| fd5a532d0f | |||
| 8c43eedb23 | |||
| e027c2e09e | |||
| f9a0656d4d | |||
| 382d8b1268 |
40
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Firefox (mv2)
|
||||
run: npm run build:firefox
|
||||
|
||||
- name: Build Chrome (mv3)
|
||||
run: npm run build
|
||||
|
||||
- name: Upload Firefox (mv2)
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: stream-bypass-mv2
|
||||
path: .output/firefox-mv2
|
||||
|
||||
- name: Upload Chrome (mv3)
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: stream-bypass-mv3
|
||||
path: .output/chrome-mv3
|
||||
28
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags-ignore:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
85
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
type: boolean
|
||||
description: Dry run
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Zip extensions
|
||||
run: |
|
||||
npm run zip:firefox
|
||||
npm run zip
|
||||
|
||||
- name: Resolve zip extension paths
|
||||
id: paths
|
||||
run: |
|
||||
MV2_PATH=$(find .output -type f -name "*-mv2.zip")
|
||||
MV2_NAME=$(basename $MV2_PATH)
|
||||
MV3_PATH=$(find .output -type f -name "*-mv3.zip")
|
||||
MV3_NAME=$(basename $MV3_PATH)
|
||||
echo "mv2_path=$MV2_PATH" >> "$GITHUB_OUTPUT"
|
||||
echo "mv2_name=$MV2_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "mv3_path=$MV3_PATH" >> "$GITHUB_OUTPUT"
|
||||
echo "mv3_name=$MV3_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload mv2 as artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.paths.outputs.mv2_name }}
|
||||
path: ${{ steps.paths.outputs.mv2_path }}
|
||||
|
||||
- name: Upload mv3 as artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.paths.outputs.mv3_name }}
|
||||
path: ${{ steps.paths.outputs.mv3_path }}
|
||||
|
||||
- name: Upload to latest release
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: .output/*mv*.zip
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
|
||||
- name: Submit to stores
|
||||
env:
|
||||
DRY_RUN: ${{ github.event.inputs.dry_run }}
|
||||
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
|
||||
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
|
||||
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
|
||||
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
run: |
|
||||
npx wxt submit \
|
||||
--firefox-zip .output/*-mv2.zip --firefox-sources-zip .output/*-sources.zip \
|
||||
--chrome-zip .output/*-mv3.zip
|
||||
31
.gitignore
vendored
@@ -1,4 +1,27 @@
|
||||
.idea/
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
# Editor directories and files
|
||||
.zed
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
11
.prettierignore
Normal file
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
.vscode
|
||||
.zed
|
||||
|
||||
node_modules
|
||||
package-lock.json
|
||||
.output
|
||||
.wxt
|
||||
|
||||
.github
|
||||
18
.prettierrc.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import("prettier").Config} */
|
||||
module.exports = {
|
||||
useTabs: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'none',
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
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
|
||||
};
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 ByteDream
|
||||
Copyright (c) 2022-NOW ByteDream
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
176
README.md
@@ -3,20 +3,17 @@
|
||||
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 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%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 href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
|
||||
<img src="https://img.shields.io/amo/stars/stream-bypass?label=Firefox%20Store%20Stars&style=flat-square" alt="Firefox Addon Stars">
|
||||
<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>
|
||||
<a href="https://discord.gg/gUWwekeNNg">
|
||||
<img src="https://img.shields.io/discord/915659846836162561?label=Discord&style=flat-square" alt="Discord">
|
||||
<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>
|
||||
|
||||
@@ -25,9 +22,13 @@ A multi-browser addon / extension for multiple streaming providers which redirec
|
||||
•
|
||||
<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>
|
||||
</p>
|
||||
@@ -39,86 +40,151 @@ This has the advantage, that no advertising or popups are shown when trying to i
|
||||
Additionally, this enables you to download the video by right-clicking it and just choose the download option.
|
||||
|
||||
<details id="example">
|
||||
<summary><b>How it's working:</b></summary>
|
||||
<summary><b>How it works:</b></summary>
|
||||
<img src="example.gif" alt="">
|
||||
</details>
|
||||
|
||||
## 📥 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).
|
||||
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.
|
||||
<details>
|
||||
<summary><h3 id="manual-installation1">Manual installation</h3></summary>
|
||||
|
||||
### Opera
|
||||
- Firefox (mv2)
|
||||
- Download `stream-bypass-<version>-mv2.zip` from the [latest release](https://github.com/bytedream/stream-bypass/releases/latest) and unzip it (e.g. with [7zip](https://www.7-zip.org/))
|
||||
- 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 (mv3)
|
||||
> 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 (e.g. [7zip](https://www.7-zip.org/))
|
||||
- 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).
|
||||
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.
|
||||
</details>
|
||||
|
||||
## 📜 Supported sites
|
||||
## ✨ Features
|
||||
|
||||
| Site | Supported | Note |
|
||||
|-----------------------------------------------------------------------|-----------|--------------------------------------------------|
|
||||
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | |
|
||||
| [evoload.io](https://evoload.io) | ✔️ | |
|
||||
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | |
|
||||
| [mp4upload.com](https://mp4upload.com) | ✔ | |
|
||||
| [newgrounds.com](https://newgrounds.com) | ✔ | |
|
||||
| [streamtape.com](https://streamtape.com) | ✔ | |
|
||||
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
|
||||
| [upstream.to](https://upstream.to) | ✔ | |
|
||||
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
|
||||
| [vidlox.me](https://vidlox.me) | ⚠ | Website down / Timeout |
|
||||
| [vidoza.net](https://vidoza.net) | ✔ | |
|
||||
| [vivo.sx](https://vivo.sx) | ⚠️ | Website down / Timeout |
|
||||
| [voe.sx](https://voe.sx) / [voeunblk.com](https://voeunblk.com) | ✔ | |
|
||||
| [vupload.com](https://vupload.com) | ✔ | |
|
||||
- ✔: Supported.
|
||||
- ✖: Not supported.
|
||||
|
||||
- ✔️: 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.
|
||||
- ❌: Not included / supported by the addon. This can have various reasons. See `Note` for an explanation.
|
||||
| Feature | Firefox (mv2) | Chrome (mv3) | Firefox for Android (mv2) |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------ | ------------------------- |
|
||||
| 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)) | ✔ | ✔ | ✖ |
|
||||
|
||||
## 📜 Supported websites
|
||||
|
||||
- ✔: Everything ok.
|
||||
- ⚠: Works with limitations.
|
||||
- ✖: Not supported.
|
||||
|
||||
| Site | Firefox & Firefox for Android (mv2) | Chrome & Chromium based (mv2) |
|
||||
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| [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) | ✔ | ✔ |
|
||||
|
||||
_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
|
||||
## 🛠️ 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 way described in [installation](#-installation), 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
|
||||
$ npm i
|
||||
|
||||
# build the extension source to a build/ directory
|
||||
$ npm run build
|
||||
# build the extension and start it in a new firefox instance
|
||||
$ npm run dev:firefox
|
||||
|
||||
# same as build + create a bundle zipfile at dist/
|
||||
$ npm run bundle
|
||||
# build the extension with optimizations to the .output/firefox-mv2 directory
|
||||
$ npm run build:firefox
|
||||
```
|
||||
|
||||
You can omit the `:firefox` suffix, then it's built for Chrome.
|
||||
|
||||
##### 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 the [manual installation](#-installation).
|
||||
When using firefox, use the following:
|
||||
|
||||
1. Type `about:debugging` in the browser's address bar.
|
||||
2. Select 'This Firefox' tab (maybe named different, depending on your language).
|
||||
3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
|
||||
4. Choose any file in the directory where the compiled sources are.
|
||||
|
||||
## ⚙️ Settings
|
||||
|
||||
> You reach the settings by pressing the tree dots (⋮) in the top right corner of the extension popup.
|
||||
|
||||
### Hosts
|
||||
|
||||
You can enable or disabled for which hosts the extension should redirect.
|
||||
|
||||
### ff2mpv
|
||||
|
||||
[ff2mpv](https://github.com/woodruffw/ff2mpv) allows you to play streams directly in [mpv](https://mpv.io/) instead of the browser.
|
||||
You can enable or disable this behavior.
|
||||
|
||||
<details>
|
||||
<summary><i>Steps to get it set up</i></summary>
|
||||
|
||||
- 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/"
|
||||
]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## ⚖ License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.
|
||||
|
||||
31
eslint.config.mjs
Normal file
@@ -0,0 +1,31 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import ts from 'typescript-eslint';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('.gitignore', import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs[('flat/recommended', 'flat/prettier')],
|
||||
prettier,
|
||||
includeIgnoreFile(gitignorePath),
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'no-undef': 'off'
|
||||
}
|
||||
}
|
||||
);
|
||||
8355
package-lock.json
generated
Normal file
91
package.json
@@ -1,41 +1,54 @@
|
||||
{
|
||||
"name": "stream-bypass",
|
||||
"version": "2.1.3",
|
||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "node tasks/build.ts",
|
||||
"bundle": "node tasks/build.ts && web-ext build -s build -a dist -o",
|
||||
"bundle:all": "node tasks/build.ts && web-ext build -s build -a dist -o && web-ext sign -s build -a dist",
|
||||
"check": "web-ext lint -s build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ByteDream/stream-bypass.git"
|
||||
},
|
||||
"author": "ByteDream",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ByteDream/stream-bypass/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"@types/chrome": "^0.0.190",
|
||||
"@types/node-sass": "^4.11.2",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"eslint": "^8.17.0",
|
||||
"hls.js": "^1.1.5",
|
||||
"node-sass": "^7.0.1",
|
||||
"node-sass-package-importer": "^5.3.2",
|
||||
"rollup": "^2.75.6",
|
||||
"tippy.js": "^6.3.7",
|
||||
"typescript": "^4.7.3",
|
||||
"web-ext": "^7.0.0"
|
||||
}
|
||||
"name": "stream-bypass",
|
||||
"version": "4.0.0",
|
||||
"displayName": "Stream Bypass",
|
||||
"author": "bytedream",
|
||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/bytedream/stream-bypass.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/bytedream/stream-bypass/issues"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"dev:firefox": "wxt -b firefox",
|
||||
"build": "wxt build",
|
||||
"build:firefox": "wxt build -b firefox",
|
||||
"zip": "wxt zip",
|
||||
"zip:firefox": "wxt zip -b firefox",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"postinstall": "wxt prepare",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.4.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@steeze-ui/heroicons": "^2.4.2",
|
||||
"@steeze-ui/svelte-icon": "^1.6.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"@tsconfig/svelte": "^5.0.5",
|
||||
"@wxt-dev/module-svelte": "^2.0.4",
|
||||
"eslint": "^9.38.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.12.5",
|
||||
"hls.js": "^1.6.13",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"sass": "^1.93.2",
|
||||
"svelte": "^5.43.2",
|
||||
"svelte-check": "^4.3.3",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.2",
|
||||
"vite": "^7.1.12",
|
||||
"web-ext": "^9.1.0",
|
||||
"wxt": "^0.20.11"
|
||||
}
|
||||
}
|
||||
|
||||
1
public/icon/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/icon/stream-bypass@128px.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
public/icon/stream-bypass@16px.png
Normal file
|
After Width: | Height: | Size: 713 B |
BIN
public/icon/stream-bypass@32px.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icon/stream-bypass@48px.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
1
public/icon/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/icon/stream-bypass_disabled@128px.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
public/icon/stream-bypass_disabled@16px.png
Normal file
|
After Width: | Height: | Size: 792 B |
BIN
public/icon/stream-bypass_disabled@32px.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/icon/stream-bypass_disabled@48px.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
21
src/assets/base.css
Normal file
@@ -0,0 +1,21 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--color-linux-mint-green: #35a854;
|
||||
|
||||
--default-font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
background-color: #030712;
|
||||
color: var(--color-gray-300);
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import {getMatch} from "./match/match";
|
||||
import {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)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
urls: ['<all_urls>'],
|
||||
types: ['main_frame', 'sub_frame']
|
||||
})
|
||||
30
src/entrypoints/background/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { UrlReferer } from '@/lib/settings';
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addListener(async (message) => {
|
||||
if (message.action == 'ff2mpv') {
|
||||
await browser.runtime.sendNativeMessage('ff2mpv', { url: message.url });
|
||||
}
|
||||
|
||||
// the following listener is only available in mv2
|
||||
if (import.meta.env.MANIFEST_VERSION === 3) return;
|
||||
|
||||
browser.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;
|
||||
|
||||
details.requestHeaders!.push({
|
||||
name: 'Referer',
|
||||
value: `https://${referer}/`
|
||||
});
|
||||
|
||||
return { requestHeaders: details.requestHeaders };
|
||||
},
|
||||
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] },
|
||||
['blocking', 'requestHeaders']
|
||||
);
|
||||
});
|
||||
});
|
||||
78
src/entrypoints/content/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { getHost, hosts, type Host, type HostMatch } from '@/lib/host';
|
||||
import { FF2MPVSettings } from '@/lib/settings';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: [
|
||||
...Object.values(hosts).flatMap((h) => h.domains.map((d) => `*://${d}/*`)),
|
||||
// only mv2 allows to match all urls
|
||||
...(import.meta.env.MANIFEST_VERSION === 2 ? ['<all_urls>'] : [])
|
||||
],
|
||||
allFrames: true,
|
||||
runAt: 'document_end',
|
||||
main
|
||||
});
|
||||
|
||||
async function main() {
|
||||
let host: Host | null;
|
||||
if ((host = await getHost(window.location.host)) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let re = null;
|
||||
for (const regex of host.regex) {
|
||||
if ((re = document.body.innerHTML.match(regex)) !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (re === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hostMatch: HostMatch | null;
|
||||
try {
|
||||
hostMatch = await host.match(re);
|
||||
} catch {
|
||||
hostMatch = null;
|
||||
}
|
||||
|
||||
if (!hostMatch || !hostMatch.url) return;
|
||||
|
||||
// send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application
|
||||
if (await FF2MPVSettings.getEnabled()) {
|
||||
await browser.runtime.sendMessage({ action: 'ff2mpv', url: hostMatch.url });
|
||||
}
|
||||
|
||||
if (host.replace && hostMatch.type != '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 = hostMatch.url;
|
||||
|
||||
// add video player to document body
|
||||
document.body.style.margin = '0';
|
||||
document.body.append(player);
|
||||
} else {
|
||||
window.location.assign(
|
||||
browser.runtime.getURL(
|
||||
`/player.html?id=${host.id}&url=${encodeURIComponent(hostMatch.url)}&domain=${window.location.hostname}&type=${hostMatch.type}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/entrypoints/player/Player.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { play } from './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 class="absolute top-0 left-0 w-full h-full m-0" bind:this={videoElem}></video>
|
||||
{#if errorMessage}
|
||||
<div class="h-full flex items-center justify-center text-center">
|
||||
<p>
|
||||
{errorMessage} <a class="underline" href="https://github.com/bytedream/stream-bypass/issues">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
26
src/entrypoints/player/index.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Stream Bypass</title>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: #131313; overflow: hidden">
|
||||
<script type="module">
|
||||
import { mount } from 'svelte';
|
||||
import Player from './Player.svelte';
|
||||
|
||||
mount(Player, {
|
||||
target: document.body
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
75
src/entrypoints/player/player.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import Hls from 'hls.js';
|
||||
import { listenMessages, MessageType, sendMessage } from '@/lib/communication';
|
||||
import { HostMatchType, hosts } from '@/lib/host';
|
||||
import { UrlReferer } from '@/lib/settings';
|
||||
|
||||
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
|
||||
// multiple hosts need to have a correct referer set
|
||||
await UrlReferer.addTemporary(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) => {
|
||||
// multiple hosts need to have a correct referer set
|
||||
await UrlReferer.addTemporary(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';
|
||||
}
|
||||
}
|
||||
|
||||
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 HostMatchType;
|
||||
|
||||
const host = hosts.find((host) => host.id === id);
|
||||
if (!host) {
|
||||
throw `Invalid id: ${id}. Please report this`;
|
||||
}
|
||||
document.title = `Stream Bypass (${domain})`;
|
||||
|
||||
initCommunication(id, url, domain);
|
||||
|
||||
switch (type) {
|
||||
case HostMatchType.NATIVE:
|
||||
await playNative(url, domain, videoElem);
|
||||
break;
|
||||
case HostMatchType.HLS:
|
||||
await playHls(url, domain, videoElem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function initCommunication(id: string, url: string, domain: string) {
|
||||
const notifyActiveMatch = () =>
|
||||
sendMessage(MessageType.NotifyActiveMatch, {
|
||||
id: id,
|
||||
url: url,
|
||||
domain: domain
|
||||
});
|
||||
|
||||
// if an extension popup is open, it will be notified that a match/player is now active
|
||||
notifyActiveMatch();
|
||||
|
||||
// if an extension popup is opened, the listener will recognize it's active match request and send the match/player
|
||||
// data
|
||||
const cancel = listenMessages((type) => {
|
||||
if (type !== MessageType.RequestActiveMatch) return;
|
||||
notifyActiveMatch();
|
||||
});
|
||||
window.onbeforeunload = cancel;
|
||||
}
|
||||
33
src/entrypoints/popup/App.svelte
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import '@/assets/base.css';
|
||||
|
||||
import { fly } from 'svelte/transition';
|
||||
import Main from '@/entrypoints/popup/pages/main/Main.svelte';
|
||||
import Settings from '@/entrypoints/popup/pages/settings/Settings.svelte';
|
||||
import { isMobile } from '@/entrypoints/popup/state.js';
|
||||
|
||||
/* state init */
|
||||
browser.runtime.getPlatformInfo().then((info) => ($isMobile = info.os === 'android'));
|
||||
|
||||
/* types */
|
||||
type Page = 'main' | 'settings';
|
||||
|
||||
/* states */
|
||||
let activePage = $state<Page>('main');
|
||||
</script>
|
||||
|
||||
<div class="flex w-[350px] overflow-hidden" class:w-screen={$isMobile}>
|
||||
{#if activePage === 'main'}
|
||||
<div transition:fly={{ x: -300, duration: 150 }} class="min-w-full w-full h-[300px] flex-1 flex flex-col">
|
||||
<Main onSettingsOpenRequest={() => (activePage = 'settings')} />
|
||||
</div>
|
||||
{:else if activePage === 'settings'}
|
||||
<div
|
||||
transition:fly={{ x: 300, duration: 150 }}
|
||||
class="min-w-full w-full h-[300px] flex-1 flex flex-col"
|
||||
class:h-screen={$isMobile}
|
||||
>
|
||||
<Settings onSettingsCloseRequest={() => (activePage = 'main')} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
1
src/entrypoints/popup/components/Divider.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<div class="w-full border-b-[1px] border-gray-400"></div>
|
||||
57
src/entrypoints/popup/components/Toggle.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
/* types */
|
||||
interface Props {
|
||||
checked: boolean;
|
||||
onChecked?: (checked: boolean) => void | boolean | Promise<void> | Promise<boolean>;
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
/* states */
|
||||
let { checked = $bindable(), onChecked, size = 'md' }: Props = $props();
|
||||
let internalChecked = $state($state.snapshot(checked));
|
||||
|
||||
/* callbacks */
|
||||
async function onInputChange() {
|
||||
internalChecked = !internalChecked;
|
||||
let approved = false;
|
||||
|
||||
if (!onChecked) {
|
||||
approved = true;
|
||||
} else {
|
||||
const ret = onChecked(!internalChecked);
|
||||
|
||||
if (typeof ret === 'boolean') {
|
||||
approved = ret;
|
||||
} else if (typeof ret === 'object' && 'then' in ret && typeof ret.then === 'function') {
|
||||
const promiseRet = await ret;
|
||||
if (typeof promiseRet === 'undefined') approved = true;
|
||||
else approved = promiseRet;
|
||||
}
|
||||
}
|
||||
|
||||
if (approved) {
|
||||
internalChecked = !internalChecked;
|
||||
checked = internalChecked;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input type="checkbox" class="peer sr-only" bind:checked={internalChecked} onchange={onInputChange} />
|
||||
<div
|
||||
class="block rounded-full box bg-red-700 peer-checked:bg-linux-mint-green"
|
||||
class:w-8={size === 'sm'}
|
||||
class:h-4={size === 'sm'}
|
||||
class:w-10={size === 'md'}
|
||||
class:h-5={size === 'md'}
|
||||
></div>
|
||||
<div
|
||||
class="absolute flex items-center justify-center transition bg-white rounded-full dot left-0 top-0 peer-checked:translate-x-full"
|
||||
class:w-4={size === 'sm'}
|
||||
class:h-4={size === 'sm'}
|
||||
class:w-5={size === 'md'}
|
||||
class:h-5={size === 'md'}
|
||||
></div>
|
||||
</div>
|
||||
</label>
|
||||
13
src/entrypoints/popup/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Stream Bypass</title>
|
||||
<meta name="manifest.type" content="browser_action" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="contents h-full"></div>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
src/entrypoints/popup/main.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { mount } from 'svelte';
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!
|
||||
});
|
||||
|
||||
export default app;
|
||||
3
src/entrypoints/popup/pages/main/AllDisabled.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<p class="text-[1.05rem]">Extension disabled</p>
|
||||
</div>
|
||||
69
src/entrypoints/popup/pages/main/CopyMatch.svelte
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { Clipboard, InformationCircle } from '@steeze-ui/heroicons';
|
||||
import { Icon } from '@steeze-ui/svelte-icon';
|
||||
import { isMobile } from '@/entrypoints/popup/state';
|
||||
|
||||
/* types */
|
||||
interface Props {
|
||||
url: string;
|
||||
domain: string;
|
||||
}
|
||||
|
||||
type UrlType = 'url' | 'curl';
|
||||
|
||||
/* states */
|
||||
let { url, domain }: Props = $props();
|
||||
|
||||
let urlOutputType: UrlType = $state('url');
|
||||
let urlOutput = $derived(getUrl(urlOutputType));
|
||||
|
||||
/* functions */
|
||||
function getUrl(type: UrlType) {
|
||||
switch (type) {
|
||||
case 'url':
|
||||
return url;
|
||||
case 'curl':
|
||||
return `curl -H "Referer: https://${domain}/" "${encodeURI(url)}"`;
|
||||
}
|
||||
}
|
||||
|
||||
/* callbacks */
|
||||
function copyUrl() {
|
||||
navigator.clipboard.writeText(urlOutput);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="flex gap-2 items-center pb-1">
|
||||
<p class="mt-0.5 text-sm">Show video as</p>
|
||||
<select
|
||||
class="w-fit text-xs border text-slate-200 border-gray-500 rounded cursor-pointer pt-1 pb-0.5 pl-1"
|
||||
bind:value={urlOutputType}
|
||||
>
|
||||
<option value="url">URL</option>
|
||||
<option value="curl">cURL</option>
|
||||
</select>
|
||||
{#if urlOutputType === 'url'}
|
||||
<div class="relative group h-4 flex justify-center items-center">
|
||||
<button class="text-sm peer"><Icon src={InformationCircle} size="1rem" /></button>
|
||||
<span
|
||||
class="z-10 absolute w-58 bottom-5/4 p-1 bg-gray-800 text-xs invisible opacity-0 group-hover:visible group-hover:opacity-100 peer-focus:visible peer-focus:opacity-100 transition-[opacity]"
|
||||
>You may have to send the referer header <code class="select-text">Referer: {domain}</code> when accessing
|
||||
the url</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<div class="relative group w-full" tabindex={$isMobile ? 0 : undefined}>
|
||||
<pre
|
||||
class="w-full h-20 overflow-y-scroll text-[0.8rem] wrap-anywhere text-wrap select-text rounded bg-gray-900 py-[0.25rem] px-1.5">{urlOutput}</pre>
|
||||
<div
|
||||
class="absolute top-2 right-2 transition-opacity duration-100 opacity-0 group-hover:opacity-100 group-focus:opacity-100 h-full"
|
||||
>
|
||||
<button class="cursor-pointer" title="Copy to clipboard" onclick={copyUrl}>
|
||||
<Icon src={Clipboard} size="1rem" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
46
src/entrypoints/popup/pages/main/Header.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '@/entrypoints/popup/components/Toggle.svelte';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
|
||||
/* types */
|
||||
interface Props {
|
||||
allHostsDisabled: boolean;
|
||||
onSettingsClick: () => void;
|
||||
}
|
||||
|
||||
/* states */
|
||||
let { allHostsDisabled = $bindable(), onSettingsClick }: Props = $props();
|
||||
|
||||
/* effects */
|
||||
$effect(() => {
|
||||
HostSettings.setAllHostsDisabled(allHostsDisabled);
|
||||
browser.browserAction.setIcon({
|
||||
path: allHostsDisabled
|
||||
? {
|
||||
16: 'icon/stream-bypass_disabled@16px.png',
|
||||
32: 'icon/stream-bypass_disabled@32px.png',
|
||||
48: 'icon/stream-bypass_disabled@48px.png',
|
||||
128: 'icon/stream-bypass_disabled@128px.png'
|
||||
}
|
||||
: {
|
||||
16: 'icon/stream-bypass@16px.png',
|
||||
32: 'icon/stream-bypass@32px.png',
|
||||
48: 'icon/stream-bypass@48px.png',
|
||||
128: 'icon/stream-bypass@128px.png'
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between items-center p-2">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<h1>stream-bypass</h1>
|
||||
<span class="text-xs text-gray-400">v{import.meta.env.VERSION}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
{#key allHostsDisabled}
|
||||
<Toggle bind:checked={() => !allHostsDisabled, (v) => (allHostsDisabled = !v)} />
|
||||
{/key}
|
||||
<button class="font-bold cursor-pointer" onclick={() => onSettingsClick()}>⋮</button>
|
||||
</div>
|
||||
</div>
|
||||
65
src/entrypoints/popup/pages/main/Main.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import '@/assets/base.css';
|
||||
|
||||
import { onDestroy } from 'svelte';
|
||||
import Divider from '@/entrypoints/popup/components/Divider.svelte';
|
||||
import AllDisabled from '@/entrypoints/popup/pages/main/AllDisabled.svelte';
|
||||
import CopyMatch from '@/entrypoints/popup/pages/main/CopyMatch.svelte';
|
||||
import Header from '@/entrypoints/popup/pages/main/Header.svelte';
|
||||
import Match from '@/entrypoints/popup/pages/main/Match.svelte';
|
||||
import NoMatch from '@/entrypoints/popup/pages/main/NoMatch.svelte';
|
||||
import { listenMessages, MessageType, sendMessageToActiveTab } from '@/lib/communication';
|
||||
import { hosts, type Host } from '@/lib/host';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
|
||||
/* types */
|
||||
interface Props {
|
||||
onSettingsOpenRequest: () => void;
|
||||
}
|
||||
|
||||
/* states */
|
||||
let { onSettingsOpenRequest }: Props = $props();
|
||||
let currentMatch = $state<{ host: Host; url: string; domain: string } | null>(null);
|
||||
|
||||
let allHostsDisabled = $state(false);
|
||||
HostSettings.getAllHostsDisabled().then((val) => (allHostsDisabled = val));
|
||||
|
||||
/* lifecycle */
|
||||
const cancel = listenMessages((type, data) => {
|
||||
if (type !== MessageType.NotifyActiveMatch) return;
|
||||
currentMatch = {
|
||||
host: hosts.find((host) => host.id === data.id)!,
|
||||
url: data.url,
|
||||
domain: data.domain
|
||||
};
|
||||
});
|
||||
sendMessageToActiveTab(MessageType.RequestActiveMatch, undefined);
|
||||
|
||||
onDestroy(cancel);
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<Header bind:allHostsDisabled onSettingsClick={onSettingsOpenRequest} />
|
||||
<Divider />
|
||||
</div>
|
||||
<div class="px-2 h-full">
|
||||
{#if allHostsDisabled}
|
||||
<AllDisabled />
|
||||
{:else if !currentMatch}
|
||||
<NoMatch />
|
||||
{:else}
|
||||
<div class="flex flex-col justify-between h-full pb-2">
|
||||
<Match host={currentMatch.host} domain={currentMatch.domain} />
|
||||
<div class="divider border-dashed"></div>
|
||||
<div class="mt-2">
|
||||
<CopyMatch url={currentMatch.url} domain={currentMatch.domain} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
* {
|
||||
@apply select-none;
|
||||
}
|
||||
</style>
|
||||
20
src/entrypoints/popup/pages/main/Match.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { type Host } from '@/lib/host';
|
||||
|
||||
/* types */
|
||||
interface Props {
|
||||
host: Host;
|
||||
domain: string;
|
||||
}
|
||||
|
||||
/* states */
|
||||
const { host, domain }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-center justify-center w-full h-full">
|
||||
<p class="text-lg">Match found:</p>
|
||||
<div class="[&>*]:select-text">
|
||||
<span class="underline text-green-400 text-2xl font-bold">{host.name}</span>
|
||||
<span class="text-xs text-slate-300">({domain})</span>
|
||||
</div>
|
||||
</div>
|
||||
13
src/entrypoints/popup/pages/main/NoMatch.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="relative h-full">
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<p class="text-[1.05rem]">No supported video found on this site</p>
|
||||
</div>
|
||||
<div class="absolute bottom-0.5">
|
||||
<p class="text-xs text-gray-400">
|
||||
Suggestions or bugs can be submitted <a
|
||||
class="underline"
|
||||
href="https://github.com/bytedream/stream-bypass/issues">here</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
35
src/entrypoints/popup/pages/settings/Ff2mpv.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { InformationCircle } from '@steeze-ui/heroicons';
|
||||
import { Icon } from '@steeze-ui/svelte-icon';
|
||||
import Toggle from '@/entrypoints/popup/components/Toggle.svelte';
|
||||
import { FF2MPVSettings } from '@/lib/settings';
|
||||
|
||||
/* states */
|
||||
let enabled = $state(false);
|
||||
FF2MPVSettings.getEnabled().then((val) => (enabled = val));
|
||||
|
||||
/* callbacks */
|
||||
function onEnableChange(enabled: boolean) {
|
||||
if (!enabled) return true;
|
||||
|
||||
return browser.permissions.request({ permissions: ['nativeMessaging'] });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative mr-3">
|
||||
<span>Communication enabled</span>
|
||||
<a
|
||||
class="absolute -top-1 -right-4 text-sm"
|
||||
href="https://github.com/bytedream/stream-bypass/tree/main?tab=readme-ov-file#ff2mpv-use-mpv-to-directly-play-streams"
|
||||
target="_blank"><Icon src={InformationCircle} size="1rem" /></a
|
||||
>
|
||||
</div>
|
||||
{#key enabled}
|
||||
<Toggle
|
||||
bind:checked={() => enabled, (v) => FF2MPVSettings.setEnabled(v)}
|
||||
onChecked={onEnableChange}
|
||||
size="sm"
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
16
src/entrypoints/popup/pages/settings/Header.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
/* types */
|
||||
interface Props {
|
||||
onBackClick: () => void;
|
||||
}
|
||||
|
||||
/* states */
|
||||
let { onBackClick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between items-center p-2">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<button class="cursor-pointer" onclick={() => onBackClick()}>←</button>
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
</div>
|
||||
41
src/entrypoints/popup/pages/settings/HostsTable.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '@/entrypoints/popup/components/Toggle.svelte';
|
||||
import { hosts } from '@/lib/host';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
|
||||
/* states */
|
||||
let disabledHostIds = $state<Array<string>>([]);
|
||||
HostSettings.getDisabledHosts().then((val) => (disabledHostIds = val));
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-[35%_43%_22%] gap-y-0.75">
|
||||
<p class="font-bold">Host</p>
|
||||
<p class="font-bold">Domains</p>
|
||||
<p class="font-bold">Enabled</p>
|
||||
{#each hosts as host (host.id)}
|
||||
{@const domainList = host.domains.join(', ')}
|
||||
<p>{host.name}</p>
|
||||
<div>
|
||||
<label for={host.id}>
|
||||
<input id={host.id} type="checkbox" class="peer hidden" checked />
|
||||
<p
|
||||
title={domainList}
|
||||
class="cursor-pointer overflow-hidden peer-checked:text-ellipsis peer-checked:text-nowrap"
|
||||
>
|
||||
{domainList}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-[.2rem]">
|
||||
{#key disabledHostIds}
|
||||
<Toggle
|
||||
bind:checked={
|
||||
() => !disabledHostIds.includes(host.id),
|
||||
(v) => (v ? HostSettings.removeDisabledHost(host) : HostSettings.addDisabledHost(host))
|
||||
}
|
||||
size="sm"
|
||||
></Toggle>
|
||||
{/key}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
60
src/entrypoints/popup/pages/settings/Settings.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import Divider from '@/entrypoints/popup/components/Divider.svelte';
|
||||
import Ff2mpv from '@/entrypoints/popup/pages/settings/Ff2mpv.svelte';
|
||||
import Header from '@/entrypoints/popup/pages/settings/Header.svelte';
|
||||
import HostsTable from '@/entrypoints/popup/pages/settings/HostsTable.svelte';
|
||||
import { isMobile } from '@/entrypoints/popup/state';
|
||||
|
||||
/* types */
|
||||
interface Props {
|
||||
onSettingsCloseRequest: () => void;
|
||||
}
|
||||
|
||||
/* states */
|
||||
let { onSettingsCloseRequest }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<Header onBackClick={onSettingsCloseRequest} />
|
||||
<Divider />
|
||||
</div>
|
||||
<div class="flex flex-col gap-y-1 pt-1 h-full mx-2 my-1 overflow-y-scroll">
|
||||
<details class="details" open>
|
||||
<summary>Hosts</summary>
|
||||
<HostsTable />
|
||||
</details>
|
||||
{#if !$isMobile}
|
||||
<details class="details">
|
||||
<summary>ff2mpv</summary>
|
||||
<Ff2mpv />
|
||||
</details>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
* {
|
||||
@apply select-none;
|
||||
}
|
||||
|
||||
.details {
|
||||
/* using normal css instead of tailwind in the following blocks.
|
||||
for some reason tailwind fails to resolve many references */
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--color-gray-600);
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
& > summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&[open] > summary {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
3
src/entrypoints/popup/state.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const isMobile = writable(false);
|
||||
@@ -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;
|
||||
if ((match = await getMatch(window.location.host)) === undefined) {
|
||||
let id: string
|
||||
if ((id = await storageGet('redirect')) !== undefined) {
|
||||
match = matches.find(m => m.id === id)
|
||||
await storageDelete('redirect')
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const re = document.body.innerHTML.match(match.regex)
|
||||
if (re === null) {
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
36
src/lib/communication.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export enum MessageType {
|
||||
RequestActiveMatch,
|
||||
NotifyActiveMatch
|
||||
}
|
||||
|
||||
export type MessageData<T extends MessageType> = {
|
||||
[MessageType.RequestActiveMatch]: undefined;
|
||||
[MessageType.NotifyActiveMatch]: {
|
||||
id: string;
|
||||
url: string;
|
||||
domain: string;
|
||||
};
|
||||
}[T];
|
||||
|
||||
export async function sendMessage<T extends MessageType>(message: T, data: MessageData<T>) {
|
||||
await browser.runtime.sendMessage({ type: message, data: data });
|
||||
}
|
||||
|
||||
export async function sendMessageToActiveTab<T extends MessageType>(message: T, data: MessageData<T>) {
|
||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
|
||||
await browser.tabs.sendMessage(tabs[0].id!, { type: message, data: data }).catch(() => {});
|
||||
}
|
||||
|
||||
export function listenMessages(listener: (type: MessageType, data: any) => void): () => void {
|
||||
const callback = (callbackData: { type: MessageType; data: any }) => {
|
||||
const { type, data } = callbackData;
|
||||
listener(type, data);
|
||||
};
|
||||
|
||||
browser.runtime.onMessage.addListener(callback);
|
||||
|
||||
return () => {
|
||||
browser.runtime.onMessage.removeListener(callback);
|
||||
};
|
||||
}
|
||||
41
src/lib/host/doodstream.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: `${await response.text()}1234567890${match[2]}${Date.now()}`
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
17
src/lib/host/dropload.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: unpacked.match(/(?<=file:").*(?=")/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
24
src/lib/host/filemoon.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 HostSettings.addTemporaryHostDomain(this, new URL(match[0]).host);
|
||||
return null;
|
||||
}
|
||||
|
||||
const unpacked = await unpack(match[0]);
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: unpacked.match(/(?<=file:")\S*(?=")/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
15
src/lib/host/goodstream.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
name: 'Goodstream',
|
||||
id: 'goodstream',
|
||||
domains: ['goodstream.uno'],
|
||||
regex: [/(?<=file:\s+").*(?=")/g],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: match[0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
78
src/lib/host/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import Doodstream from './doodstream';
|
||||
import Dropload from './dropload';
|
||||
import Filemoon from './filemoon';
|
||||
import Goodstream from './goodstream';
|
||||
import Kwik from './kwik';
|
||||
import Loadx from './loadx';
|
||||
import Luluvdo from './luluvdo';
|
||||
import Mixdrop from './mixdrop';
|
||||
import Mp4Upload from './mp4upload';
|
||||
import Newgrounds from './newgrounds';
|
||||
import StreamA2z from './streama2z';
|
||||
import Streamtape from './streamtape';
|
||||
import Streamzz from './streamzz';
|
||||
import SuperVideo from './supervideo';
|
||||
import Upstream from './upstream';
|
||||
import Vidmoly from './vidmoly';
|
||||
import Vidoza from './vidoza';
|
||||
import Voe from './voe';
|
||||
import Vupload from './vupload';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
|
||||
export enum HostMatchType {
|
||||
NATIVE = 'native',
|
||||
HLS = 'hls'
|
||||
}
|
||||
|
||||
export interface HostMatch {
|
||||
type: HostMatchType;
|
||||
/** If null, it's interpreted that a url should be present but isn't, probably because the website broke */
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
export interface Host {
|
||||
name: string;
|
||||
id: string;
|
||||
domains: string[];
|
||||
replace?: boolean;
|
||||
regex: RegExp[];
|
||||
notice?: string;
|
||||
|
||||
match(match: RegExpMatchArray): Promise<HostMatch | null>;
|
||||
}
|
||||
|
||||
export const hosts = [
|
||||
Doodstream,
|
||||
Dropload,
|
||||
Filemoon,
|
||||
Goodstream,
|
||||
Kwik,
|
||||
Loadx,
|
||||
Luluvdo,
|
||||
Mixdrop,
|
||||
Mp4Upload,
|
||||
Newgrounds,
|
||||
StreamA2z,
|
||||
Streamtape,
|
||||
Streamzz,
|
||||
SuperVideo,
|
||||
Upstream,
|
||||
Vidmoly,
|
||||
Vidoza,
|
||||
Voe,
|
||||
Vupload
|
||||
];
|
||||
|
||||
export async function getHost(domain: string) {
|
||||
if (await HostSettings.getAllHostsDisabled()) return null;
|
||||
|
||||
const disabledIds = await HostSettings.getDisabledHosts();
|
||||
for (const host of hosts) {
|
||||
if (host.domains.includes(domain)) {
|
||||
if (!disabledIds.includes(host.id)) return host;
|
||||
else return null;
|
||||
}
|
||||
}
|
||||
|
||||
return HostSettings.checkTemporaryHostDomain(domain);
|
||||
}
|
||||
17
src/lib/host/kwik.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: unpacked.match(/(?<=source=').*(?=')/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
27
src/lib/host/loadx.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { lastPathSegment } from '@/utils/extract';
|
||||
|
||||
export default {
|
||||
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'];
|
||||
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: videoSource.replace('\\/', '/')
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
32
src/lib/host/luluvdo.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
import { lastPathSegment } from '@/utils/extract';
|
||||
|
||||
export default {
|
||||
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
|
||||
});
|
||||
|
||||
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
|
||||
const unpacked = evalMatch ? await unpack(evalMatch[0]) : responseText;
|
||||
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: unpacked.match(/(?<=file:").*(?=")/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
18
src/lib/host/mixdrop.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.NATIVE,
|
||||
url: `https:${url}`
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
18
src/lib/host/mp4upload.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.NATIVE,
|
||||
url: unpacked.match(/(?<=player.src\(").*(?=")/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
22
src/lib/host/newgrounds.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
20
src/lib/host/streama2z.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
|
||||
export default {
|
||||
name: 'Stream2Az',
|
||||
id: 'stream2az',
|
||||
domains: ['streama2z.com', 'streama2z.xyz'],
|
||||
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
if (this.domains.indexOf(window.location.hostname) !== -1) {
|
||||
await HostSettings.addTemporaryHostDomain(this, window.location.hostname);
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: match[0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
26
src/lib/host/streamtape.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: `https://streamtape.com/get_video?${match[i]}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
// use the old method as fallback
|
||||
url: `https://streamtape.com/get_video?${match.reverse()[0]}`
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
17
src/lib/host/streamzz.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
name: 'Streamzz',
|
||||
id: 'streamzz',
|
||||
domains: ['streamzz.to', 'streamz.ws'],
|
||||
regex: [/(?<=\|)\w{2,}/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
|
||||
match.sort((a, b) => b.length - a.length)[0]
|
||||
}.dll`
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
17
src/lib/host/supervideo.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: unpacked.match(/(?<=file:").*(?=")/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
17
src/lib/host/upstream.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { unpack } from '@/utils/content';
|
||||
|
||||
export default {
|
||||
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 {
|
||||
type: HostMatchType.HLS,
|
||||
url: unpacked.match(/(?<=file:").*(?=")/)![0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
15
src/lib/host/vidmoly.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
name: 'Vidmoly',
|
||||
id: 'vidmoly',
|
||||
domains: ['vidmoly.me', 'vidmoly.net', 'vidmoly.to'],
|
||||
regex: [/(?<=file:").+\.m3u8.*(?=")/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: match[0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
16
src/lib/host/vidoza.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
name: 'Vidoza',
|
||||
id: 'vidoza',
|
||||
domains: ['vidoza.net', 'videzz.net'],
|
||||
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||
replace: true,
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: match[0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
74
src/lib/host/voe.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
import { HostSettings } from '@/lib/settings';
|
||||
|
||||
function rot13(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;
|
||||
}
|
||||
|
||||
function removeSpecialSequences(input: string) {
|
||||
return input
|
||||
.replaceAll(/@\$/g, '')
|
||||
.replaceAll(/\^\^/g, '')
|
||||
.replaceAll(/~@/g, '')
|
||||
.replaceAll(/%\?/g, '')
|
||||
.replaceAll(/\*~/g, '')
|
||||
.replaceAll(/!!/g, '')
|
||||
.replaceAll(/#&/g, '');
|
||||
}
|
||||
|
||||
function shiftString(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 default {
|
||||
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') {
|
||||
await HostSettings.addTemporaryHostDomain(this, new URL(match[0]).host);
|
||||
return null;
|
||||
} else {
|
||||
let json = match[0];
|
||||
json = JSON.parse(json);
|
||||
|
||||
let deobfuscated = json[0];
|
||||
deobfuscated = rot13(deobfuscated);
|
||||
deobfuscated = removeSpecialSequences(deobfuscated);
|
||||
deobfuscated = atob(deobfuscated);
|
||||
deobfuscated = shiftString(deobfuscated);
|
||||
deobfuscated = deobfuscated.split('').reverse().join('');
|
||||
deobfuscated = atob(deobfuscated);
|
||||
|
||||
const payload = JSON.parse(deobfuscated);
|
||||
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: payload['source']
|
||||
};
|
||||
}
|
||||
}
|
||||
} satisfies Host;
|
||||
15
src/lib/host/vupload.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
name: 'Vupload',
|
||||
id: 'vupload',
|
||||
domains: ['vupload.com'],
|
||||
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: match[0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
88
src/lib/settings.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { storage } from '#imports';
|
||||
import { hosts, type Host } from '@/lib/host';
|
||||
|
||||
export class HostSettings {
|
||||
/* disabled hosts */
|
||||
private static disabledHosts = storage.defineItem<string[]>('local:disabledHosts', { fallback: [] });
|
||||
private static allHostsDisabled = storage.defineItem<boolean>('local:allHostsDisabled', { fallback: false });
|
||||
|
||||
static async addDisabledHost(host: Host) {
|
||||
const ids = await this.disabledHosts.getValue();
|
||||
|
||||
const index = ids.indexOf(host.id);
|
||||
if (index === -1) {
|
||||
ids.push(host.id);
|
||||
await this.disabledHosts.setValue(ids);
|
||||
}
|
||||
}
|
||||
static async removeDisabledHost(host: Host) {
|
||||
const ids = await this.disabledHosts.getValue();
|
||||
|
||||
const index = ids.indexOf(host.id);
|
||||
if (index !== -1) {
|
||||
ids.splice(index, 1);
|
||||
await this.disabledHosts.setValue(ids);
|
||||
}
|
||||
}
|
||||
static async getDisabledHosts() {
|
||||
return await this.disabledHosts.getValue();
|
||||
}
|
||||
|
||||
static async getAllHostsDisabled() {
|
||||
return await this.allHostsDisabled.getValue();
|
||||
}
|
||||
|
||||
static async setAllHostsDisabled(disabled: boolean) {
|
||||
await this.allHostsDisabled.setValue(disabled);
|
||||
}
|
||||
|
||||
/* tmp */
|
||||
private static temporaryHostDomain = storage.defineItem<Record<string, string>>('local:temporaryHostDomain', {
|
||||
fallback: {}
|
||||
});
|
||||
|
||||
static async addTemporaryHostDomain(host: Host, domain: string) {
|
||||
const temporaryHostDomains = await this.temporaryHostDomain.getValue();
|
||||
|
||||
temporaryHostDomains[domain] = host.id;
|
||||
await this.temporaryHostDomain.setValue(temporaryHostDomains);
|
||||
console.log(await this.temporaryHostDomain.getValue());
|
||||
}
|
||||
static async checkTemporaryHostDomain(domain: string) {
|
||||
const temporaryHostDomains = await this.temporaryHostDomain.getValue();
|
||||
|
||||
const hostId = temporaryHostDomains[domain];
|
||||
return hostId ? (hosts.find((host) => host.id === hostId) ?? null) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export class FF2MPVSettings {
|
||||
private static ff2mpvEnabled = storage.defineItem<boolean>('local:ff2mpv', { fallback: false });
|
||||
|
||||
static async getEnabled() {
|
||||
return await this.ff2mpvEnabled.getValue();
|
||||
}
|
||||
static async setEnabled(enabled: boolean) {
|
||||
console.log('set', enabled);
|
||||
await this.ff2mpvEnabled.setValue(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
export class UrlReferer {
|
||||
private static temporaryUrlReferer = storage.defineItem<Record<string, string>>('local:temporaryUrlReferer', {
|
||||
fallback: {}
|
||||
});
|
||||
|
||||
static async addTemporary(hostname: string, referer: string) {
|
||||
const tmpUrlReferer = await this.temporaryUrlReferer.getValue();
|
||||
|
||||
tmpUrlReferer[hostname] = referer;
|
||||
await this.temporaryUrlReferer.setValue(tmpUrlReferer);
|
||||
}
|
||||
|
||||
static async get(hostname: string) {
|
||||
const tmpUrlReferer = await this.temporaryUrlReferer.getValue();
|
||||
|
||||
return tmpUrlReferer[hostname] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -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.3",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -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,266 +0,0 @@
|
||||
export enum Reliability {
|
||||
HIGH = 1,
|
||||
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.HIGH
|
||||
domains = [
|
||||
'doodstream.com',
|
||||
'dood.pm',
|
||||
'dood.ws'
|
||||
]
|
||||
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 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> {
|
||||
return `https://${match[34]}.mp4upload.com:${match[89]}/d/${match[88]}/video.mp4`
|
||||
}
|
||||
}
|
||||
|
||||
class Newgrounds implements Match {
|
||||
name = 'Newgrounds'
|
||||
id = 'newgrounds'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'newgrounds.com'
|
||||
]
|
||||
regex = new RegExp(/.*/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let id = window.location.pathname.split('/').slice(-1)[0]
|
||||
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
let json = await response.json()
|
||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
|
||||
}
|
||||
}
|
||||
|
||||
class Streamtape implements Match {
|
||||
name = 'Streamtape'
|
||||
id = 'streamtape'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'streamtape.com'
|
||||
]
|
||||
regex = new RegExp(/id=.*(?=')/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.NORMAL
|
||||
domains = [
|
||||
'streamzz.to',
|
||||
'streamz.ws'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://get.${document.domain.split('.')[0]}.tw/getlink-${match.sort((a, b) => b.length - a.length)[0]}.dll`
|
||||
}
|
||||
}
|
||||
|
||||
class Upstream implements Match {
|
||||
name = 'Upstream'
|
||||
id = 'upstream'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'upstream.to'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://${match[49]}.upstreamcdn.co/hls/${match[148]}/master.m3u8`
|
||||
}
|
||||
}
|
||||
|
||||
class Vidlox implements Match {
|
||||
name = 'Vidlox'
|
||||
id = 'vidlox'
|
||||
reliability = Reliability.LOW
|
||||
domains = [
|
||||
'vidlox.me'
|
||||
]
|
||||
regex = new RegExp(/(?<=\[")\S+?(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Vidoza implements Match {
|
||||
name = 'Vidoza'
|
||||
id = 'vidoza'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'vidoza.net'
|
||||
]
|
||||
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Vivo implements Match {
|
||||
name = 'Vivo'
|
||||
id = 'vivo'
|
||||
reliability = Reliability.LOW
|
||||
domains = [
|
||||
'vivo.sx'
|
||||
]
|
||||
regex = new RegExp(/(?<=source:\s')(\S+)(?=')/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return this.rot47(decodeURIComponent(match[0]))
|
||||
}
|
||||
|
||||
// decrypts a string with the rot47 algorithm (https://en.wikipedia.org/wiki/ROT13#Variants)
|
||||
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 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/new">here</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +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,80 +0,0 @@
|
||||
import {Match, matches, Reliability} 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, match: Match) {
|
||||
const video = document.getElementById('video') as HTMLVideoElement
|
||||
video.controls = true
|
||||
video.src = url
|
||||
}
|
||||
|
||||
async function play_hls(url: string, match: Match) {
|
||||
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)
|
||||
|
||||
const loaded = await new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(false)
|
||||
}, match.reliability * 3000)
|
||||
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
|
||||
if (!loaded) {
|
||||
let message: string
|
||||
switch (match.reliability) {
|
||||
case Reliability.LOW:
|
||||
message = `The reliability for this domain is low, errors like this are common.
|
||||
Try to choose another streaming provider (if existent) or deactivate the addon for this provider (${match.name}) and try again`
|
||||
break
|
||||
case Reliability.NORMAL:
|
||||
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
|
||||
break
|
||||
case Reliability.HIGH:
|
||||
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
|
||||
Try to refresh the page and if the error still exists you might want to open a new issue.
|
||||
When you're using Tor, such errors have a slight chance to occur more often,
|
||||
so if this is the case just try to reload the page and see if it's working then`
|
||||
break
|
||||
}
|
||||
show_message(`Could not load video. ${message}`)
|
||||
}
|
||||
} 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 = 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/new">here</a>`)
|
||||
return
|
||||
}
|
||||
document.title = `Stream Bypass (${domain})`
|
||||
|
||||
url.endsWith('.m3u8') ? await play_hls(url, match) : await play_native(url, match)
|
||||
}
|
||||
|
||||
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/new">Something does not work</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,56 +0,0 @@
|
||||
body
|
||||
background-color: #2b2a33
|
||||
font-weight: bold
|
||||
max-height: 500px
|
||||
overflow-x: hidden
|
||||
overflow-y: auto
|
||||
|
||||
a, p
|
||||
color: white
|
||||
font-size: 16px
|
||||
margin: 5px 0
|
||||
cursor: default
|
||||
|
||||
a
|
||||
border: 1px solid #281515
|
||||
cursor: pointer
|
||||
font-weight: normal
|
||||
padding: 5px 8px
|
||||
|
||||
&.active
|
||||
background-color: rgba(255, 65, 65, 0.74)
|
||||
cursor: default
|
||||
|
||||
&.disabled
|
||||
background-color: grey
|
||||
cursor: not-allowed
|
||||
|
||||
&#error
|
||||
border: none
|
||||
display: block
|
||||
font-weight: lighter
|
||||
font-size: 0.8rem
|
||||
text-align: center
|
||||
padding: 10px 0 5px 0
|
||||
|
||||
hr
|
||||
margin: 3px 0
|
||||
|
||||
|
||||
#all
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin: 10px 0
|
||||
|
||||
|
||||
.low-reliability
|
||||
text-decoration: underline
|
||||
text-decoration-color: rgb(255, 0, 0)
|
||||
|
||||
.normal-reliability
|
||||
text-decoration: underline
|
||||
text-decoration-color: yellow
|
||||
|
||||
.high-reliability
|
||||
text-decoration: underline
|
||||
text-decoration-color: #00ff00
|
||||
@@ -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()
|
||||
92
src/utils/content.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;
|
||||
}
|
||||
6
src/utils/extract.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function lastPathSegment(path: string): string {
|
||||
while (path.endsWith('/')) {
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
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.sass': 'build/ui/popup/popup.css',
|
||||
'src/ui/player/player.sass': '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()
|
||||
6
tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./.wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"useDefineForClassFields": true
|
||||
}
|
||||
}
|
||||
1
wxt-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="svelte" />
|
||||
58
wxt.config.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'wxt';
|
||||
import { version } from './package.json';
|
||||
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
modules: ['@wxt-dev/module-svelte'],
|
||||
manifest: ({ browser, manifestVersion }) => ({
|
||||
name: manifestVersion === 2 ? 'Stream Bypass' : 'Stream Bypass Lite',
|
||||
icons: {
|
||||
16: 'icon/stream-bypass@16px.png',
|
||||
32: 'icon/stream-bypass@32px.png',
|
||||
48: 'icon/stream-bypass@48px.png',
|
||||
128: 'icon/stream-bypass@128px.png'
|
||||
},
|
||||
browser_specific_settings:
|
||||
browser === 'firefox'
|
||||
? {
|
||||
gecko: {
|
||||
id: '{55dd42e8-3dd9-455a-b4fe-86664881b10c}',
|
||||
data_collection_permissions: {
|
||||
required: ['none']
|
||||
}
|
||||
},
|
||||
gecko_android: {}
|
||||
}
|
||||
: undefined,
|
||||
permissions: ['storage', ...(manifestVersion === 2 ? ['webRequest', 'webRequestBlocking', '<all_urls>'] : [])],
|
||||
optional_permissions: ['nativeMessaging'],
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ['player.html'],
|
||||
// TODO: Replace this with all hosts domains if target manifest version is 3.
|
||||
// This isn't working atm because importing '@/lib/host' fails. Ahhhh I love the whole fucking JS/TS
|
||||
// environment. Maybe I'm also overlooking something and the fix is easy, but that itsn't working out of the
|
||||
// box is once again terrible DX
|
||||
matches: ['<all_urls>']
|
||||
}
|
||||
]
|
||||
}),
|
||||
zip: {
|
||||
artifactTemplate: '{{name}}-{{version}}-{{manifestVersion}}.zip'
|
||||
},
|
||||
|
||||
vite: () => ({
|
||||
define: {
|
||||
'import.meta.env.VERSION': JSON.stringify(version)
|
||||
},
|
||||
plugins: [tailwindcss()]
|
||||
}),
|
||||
|
||||
svelte: {
|
||||
vite: {
|
||||
preprocess: vitePreprocess({})
|
||||
}
|
||||
}
|
||||
});
|
||||