mirror of
https://github.com/bytedream/stream-bypass.git
synced 2026-11-04 15:22:08 +01:00
update
This commit is contained in:
1 parent
607326e6d6
commit
fdd1ca350e
86 files changed
+4403
-5373
No files matched your search
@@ -0,0 +1,39 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- 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/firefox-mv3
|
||||
@@ -1,56 +0,0 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install nodejs
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- manifest_version: 2
|
||||
- manifest_version: 3
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install nodejs
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
MANIFEST_VERSION: ${{ matrix.manifest_version }}
|
||||
run: npm run build
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: stream-bypass-mv${{ matrix.manifest_version }}
|
||||
path: ./dist
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,23 @@
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
+27
-4
@@ -1,4 +1,27 @@
|
||||
.idea/
|
||||
dist/
|
||||
release/
|
||||
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?
|
||||
+2
-1
@@ -5,7 +5,8 @@ module.exports = {
|
||||
useTabs: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'none',
|
||||
printWidth: 100,
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
plugins: ['prettier-plugin-svelte', '@ianvs/prettier-plugin-sort-imports'],
|
||||
/* prettier-plugin-svelte */
|
||||
overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
|
||||
|
||||
@@ -55,31 +55,31 @@ The best way to install the extension are the official browser extension stores:
|
||||
|
||||
### Manual installation
|
||||
|
||||
- Firefox
|
||||
- Download `stream-bypass-<version>-mv2.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that)
|
||||
- Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar
|
||||
- Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the unzipped directory
|
||||
- Chromium / Google Chrome
|
||||
> As nearly every browser other than Firefox is based on Chromium, this should be the same for most of them
|
||||
- Download `stream-bypass-<version>-mv3.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that)
|
||||
- Go into your browser and type `chrome://extensions` in the address bar
|
||||
- Turn on the developer mode by checking the switch in the top right corner
|
||||
- Click `Load unpacked` and choose the unzipped directory
|
||||
- Firefox (mv2)
|
||||
- Download `stream-bypass-<version>-mv2.zip` from the [latest release](https://github.com/ByteDream/stream-bypass/releases/latest) and unzip it (with [7zip](https://www.7-zip.org/) or something like that)
|
||||
- Go into your browser and type `about:debugging#/runtime/this-firefox` in the address bar
|
||||
- Click the `Load Temporary Add-on...` button and choose the `manifest.json` file in the unzipped directory
|
||||
- Chromium / Google Chrome (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 (with [7zip](https://www.7-zip.org/) or something like that)
|
||||
- Go into your browser and type `chrome://extensions` in the address bar
|
||||
- Turn on the developer mode by checking the switch in the top right corner
|
||||
- Click `Load unpacked` and choose the unzipped directory
|
||||
|
||||
## ✨ Features
|
||||
|
||||
| Feature | Firefox | Chrome | Firefox for Android |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------- | ------- | ------ | ------------------- |
|
||||
| Replace site-speicifc video player with browser native video player | ✔ | ✔ | ✔ |
|
||||
| Support websites that are accessed via a redirect | ✔ | ❌ | ✔ |
|
||||
| Open video in mpv (with [ff2mpv](https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams)) | ✔ | ✔ | ❌ |
|
||||
| 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.
|
||||
- ❌: Not supported.
|
||||
|
||||
## 📜 Supported websites
|
||||
|
||||
| Site | Firefox & Firefox for Android | Chrome & Chromium based |
|
||||
| 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) |
|
||||
@@ -153,23 +153,23 @@ Steps to get it set up:
|
||||
- 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/"
|
||||
]
|
||||
```
|
||||
- Firefox: Add `{55dd42e8-3dd9-455a-b4fe-86664881b10c}` to `allowed_extensions` ->
|
||||
```
|
||||
"allowed_extensions": [
|
||||
"ff2mpv@yossarian.net",
|
||||
"{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
|
||||
]
|
||||
```
|
||||
- Chrome/Chromium:
|
||||
- Go To: Settings -> Extensions
|
||||
- Click on `Details` of the Stream Bypass extension and copy the ID
|
||||
- Add `chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/` to `allowed_origins` ->
|
||||
```
|
||||
"allowed_origins": [
|
||||
"chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/",
|
||||
"chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/"
|
||||
]
|
||||
```
|
||||
|
||||
## ⚖ License
|
||||
|
||||
|
||||
+8
-17
@@ -1,17 +1,21 @@
|
||||
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';
|
||||
|
||||
export default ts.config(
|
||||
const gitignorePath = fileURLToPath(new URL('.gitignore', import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
...svelte.configs[('flat/recommended', 'flat/prettier')],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
includeIgnoreFile(gitignorePath),
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
@@ -23,18 +27,5 @@ export default ts.config(
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'.DS_Store',
|
||||
'node_modules',
|
||||
'dist',
|
||||
'release',
|
||||
'.idea',
|
||||
'.env',
|
||||
'.env.*',
|
||||
'!.env.example',
|
||||
'package-lock.json'
|
||||
]
|
||||
}
|
||||
);
|
||||
Generated
+2830
-4031
File diff suppressed because it is too large.
Load diff
+38
-35
@@ -4,18 +4,6 @@
|
||||
"displayName": "Stream Bypass",
|
||||
"author": "bytedream",
|
||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch --mode development --minify false",
|
||||
"dev": "vite",
|
||||
"serve:firefox": "web-ext run --start-url \"about:debugging#/runtime/this-firefox\" --source-dir ./dist/",
|
||||
"serve:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
|
||||
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,28 +12,43 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/bytedream/stream-bypass/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||
"@samrum/vite-plugin-web-extension": "^5.1.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/chrome": "^0.0.320",
|
||||
"@types/firefox-webext-browser": "^120.0.4",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-config-prettier": "^10.1.3",
|
||||
"eslint-plugin-svelte": "^3.5.1",
|
||||
"hls.js": "^1.6.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"sass": "^1.87.0",
|
||||
"svelte": "^5.28.2",
|
||||
"svelte-check": "^4.1.7",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.0",
|
||||
"vite": "^6.3.5",
|
||||
"web-ext": "^8.6.0"
|
||||
"type": "module",
|
||||
"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 ."
|
||||
},
|
||||
"type": "module"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -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,39 +0,0 @@
|
||||
import './shared';
|
||||
|
||||
import { getMatch, type Match } from '~/lib/match';
|
||||
import { Redirect, UrlReferer } from '~/lib/settings';
|
||||
|
||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
async (details) => {
|
||||
const referer = await UrlReferer.get(new URL(details.url).hostname);
|
||||
if (!referer) return;
|
||||
|
||||
await UrlReferer.delete(new URL(details.url).hostname);
|
||||
|
||||
details.requestHeaders!.push({
|
||||
name: 'Referer',
|
||||
value: `https://${referer}/`
|
||||
});
|
||||
|
||||
return { requestHeaders: details.requestHeaders };
|
||||
},
|
||||
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] },
|
||||
['blocking', 'requestHeaders']
|
||||
);
|
||||
|
||||
chrome.webRequest.onBeforeRedirect.addListener(
|
||||
async (details) => {
|
||||
// check if redirects origins from a previous redirect
|
||||
if ((await Redirect.get()) == null) {
|
||||
let match: Match | null;
|
||||
if ((match = await getMatch(new URL(details.url).hostname)) !== null) {
|
||||
await Redirect.set(match);
|
||||
}
|
||||
} else {
|
||||
await Redirect.delete();
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'], types: ['main_frame', 'sub_frame'] }
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
import './shared';
|
||||
@@ -1,5 +0,0 @@
|
||||
chrome.runtime.onMessage.addListener(async (message) => {
|
||||
if (message.action == 'ff2mpv') {
|
||||
await chrome.runtime.sendNativeMessage('ff2mpv', { url: message.url });
|
||||
}
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
import { getMatch, MatchMediaType, type Match } from '~/lib/match';
|
||||
import { Other, Redirect } from '~/lib/settings';
|
||||
|
||||
async function main() {
|
||||
let match: Match | null;
|
||||
let redirect = false;
|
||||
if ((match = await getMatch(window.location.host)) === null) {
|
||||
if ((match = await Redirect.get()) === null) {
|
||||
return;
|
||||
}
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
// some sites have a javascript based redirect, e.g. example.com redirects to example.org by changing
|
||||
// window.location.href instead of a 3XX http redirect. an empty body is a sign that such a javascript redirect
|
||||
// occurred
|
||||
if (document.body == null) {
|
||||
await Redirect.set(match);
|
||||
return;
|
||||
}
|
||||
|
||||
let re = null;
|
||||
for (const regex of match.regex) {
|
||||
if ((re = document.body.innerHTML.match(regex)) !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (re === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (redirect) {
|
||||
await Redirect.delete();
|
||||
}
|
||||
|
||||
let url: string | null;
|
||||
let urlType: MatchMediaType | null;
|
||||
try {
|
||||
const matchResult = await match.match(re);
|
||||
if (matchResult && typeof matchResult === 'string') {
|
||||
url = matchResult;
|
||||
urlType = url.includes('.m3u8') ? MatchMediaType.Hls : MatchMediaType.Native;
|
||||
} else if (matchResult && typeof matchResult === 'object') {
|
||||
if (MatchMediaType.Hls in matchResult) {
|
||||
url = matchResult[MatchMediaType.Hls];
|
||||
urlType = MatchMediaType.Hls;
|
||||
} else if (MatchMediaType.Native in matchResult) {
|
||||
url = matchResult[MatchMediaType.Native];
|
||||
urlType = MatchMediaType.Native;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (!url || !urlType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application
|
||||
if (await Other.getFf2mpv()) {
|
||||
await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url });
|
||||
}
|
||||
|
||||
if (match.replace && urlType != MatchMediaType.Hls) {
|
||||
// this destroys all intervals that may spawn popups or events
|
||||
let intervalId = window.setInterval(() => {}, 0);
|
||||
while (intervalId--) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
let timeoutId = window.setTimeout(() => {}, 0);
|
||||
while (timeoutId--) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
// clear completed document
|
||||
document.documentElement.innerHTML = '';
|
||||
|
||||
document.body.style.backgroundColor = '#131313';
|
||||
|
||||
// video player
|
||||
const player = document.createElement('video');
|
||||
player.style.width = '100%';
|
||||
player.style.height = '100%';
|
||||
player.controls = true;
|
||||
player.src = url;
|
||||
|
||||
// add video player to document body
|
||||
document.body.style.margin = '0';
|
||||
document.body.append(player);
|
||||
} else {
|
||||
window.location.assign(
|
||||
chrome.runtime.getURL(
|
||||
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
|
||||
window.location.hostname
|
||||
}&type=${urlType}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,65 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { play } from '~/entries/player/player';
|
||||
|
||||
let errorMessage: string | null = $state(null);
|
||||
|
||||
let videoElem: HTMLVideoElement;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await play(videoElem);
|
||||
videoElem.controls = true;
|
||||
} catch (e) {
|
||||
errorMessage = e as string;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video id="video" bind:this={videoElem}></video>
|
||||
{#if errorMessage}
|
||||
<div id="message-container">
|
||||
<p>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html errorMessage}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- eslint-disable -->
|
||||
<style lang="scss" global>
|
||||
body {
|
||||
background-color: #131313;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#message-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
|
||||
& a,
|
||||
& a:visited {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,47 +0,0 @@
|
||||
import Hls from 'hls.js';
|
||||
import { matches, MatchMediaType } from '~/lib/match';
|
||||
import { UrlReferer } from '~/lib/settings';
|
||||
|
||||
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
|
||||
await UrlReferer.set(new URL(url).hostname, domain);
|
||||
|
||||
videoElem.src = url;
|
||||
}
|
||||
|
||||
async function playHls(url: string, domain: string, videoElem: HTMLVideoElement) {
|
||||
if (videoElem.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
videoElem.src = url;
|
||||
} else if (Hls.isSupported()) {
|
||||
const hls = new Hls({
|
||||
enableWorker: false,
|
||||
xhrSetup: async (xhr: XMLHttpRequest, url: string) => {
|
||||
await UrlReferer.set(new URL(url).hostname, domain);
|
||||
xhr.open('GET', url);
|
||||
}
|
||||
});
|
||||
hls.loadSource(url);
|
||||
hls.attachMedia(videoElem);
|
||||
} else {
|
||||
throw 'Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>';
|
||||
}
|
||||
}
|
||||
|
||||
export async function play(videoElem: HTMLVideoElement) {
|
||||
const urlQuery = new URLSearchParams(window.location.search);
|
||||
const id = urlQuery.get('id') as string;
|
||||
const url = decodeURIComponent(urlQuery.get('url') as string);
|
||||
const domain = urlQuery.get('domain') as string;
|
||||
const type = urlQuery.get('type') as MatchMediaType;
|
||||
|
||||
const match = matches[id];
|
||||
if (match === undefined) {
|
||||
throw `Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`;
|
||||
}
|
||||
document.title = `Stream Bypass (${domain})`;
|
||||
|
||||
if (type === MatchMediaType.Hls) {
|
||||
await playHls(url, domain, videoElem);
|
||||
} else if (type === MatchMediaType.Native) {
|
||||
await playNative(url, domain, videoElem);
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { matches, type Match } from '~/lib/match';
|
||||
import { Hosters, Other } from '~/lib/settings';
|
||||
import Toggle from './toggle.svelte';
|
||||
|
||||
let hostersEnabled: boolean;
|
||||
let hosters: (Match & { active: boolean; disabled: boolean })[] = [];
|
||||
(async () => {
|
||||
hostersEnabled = !(await Hosters.getAllDisabled());
|
||||
|
||||
const disabled = await Hosters.getDisabled();
|
||||
hosters = Object.values(matches).map((m: any) => {
|
||||
m['active'] = disabled.findIndex((p) => p.id == m.id) == -1;
|
||||
return m;
|
||||
}) as typeof hosters;
|
||||
})();
|
||||
|
||||
let isMobile: boolean;
|
||||
(async () => {
|
||||
isMobile = (await browser.runtime.getPlatformInfo()).os === 'android';
|
||||
})();
|
||||
|
||||
let ff2mpvEnabled: boolean;
|
||||
(async () => {
|
||||
ff2mpvEnabled = (await Other.getFf2mpv()) as boolean;
|
||||
})();
|
||||
</script>
|
||||
|
||||
<main
|
||||
style={isMobile
|
||||
? 'height: 100vh; display: flex; flex-direction: column; align-items: center'
|
||||
: 'height: 500px'}
|
||||
>
|
||||
<fieldset>
|
||||
<legend>Hoster</legend>
|
||||
<div class="setting-container" style={isMobile ? 'grid-column-gap: 5rem' : ''}>
|
||||
<label for="hosters-enabled">Enabled</label>
|
||||
<div>
|
||||
<Toggle
|
||||
bind:checked={hostersEnabled}
|
||||
id="hosters-enabled"
|
||||
onChange={() => Hosters.setAll(hostersEnabled)}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
{#each hosters as hoster, i (hoster.id)}
|
||||
<label for="hoster-{i}" style="cursor: {hostersEnabled ? 'pointer' : 'default'}"
|
||||
>{hoster.name}</label
|
||||
>
|
||||
<div>
|
||||
<Toggle
|
||||
bind:checked={hoster.active}
|
||||
disabled={!hostersEnabled}
|
||||
id="hoster-{i}"
|
||||
onChange={async () => {
|
||||
if (hoster.active) {
|
||||
await Hosters.enable(hoster);
|
||||
} else {
|
||||
await Hosters.disable(hoster);
|
||||
}
|
||||
}}
|
||||
></Toggle>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</fieldset>
|
||||
{#if !isMobile}
|
||||
<fieldset>
|
||||
<legend>Other</legend>
|
||||
<div class="setting-container">
|
||||
<label for="ff2mpv">ff2mpv</label>
|
||||
<div>
|
||||
<Toggle
|
||||
bind:checked={ff2mpvEnabled}
|
||||
id="ff2mpv"
|
||||
onChange={async () => {
|
||||
ff2mpvEnabled = !ff2mpvEnabled;
|
||||
if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) {
|
||||
await Other.setFf2mpv(ff2mpvEnabled);
|
||||
ff2mpvEnabled = !ff2mpvEnabled;
|
||||
}
|
||||
}}
|
||||
></Toggle>
|
||||
<a
|
||||
class="info-questionmark"
|
||||
href="https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams"
|
||||
>?</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
<a id="report-notice" href="https://github.com/ByteDream/stream-bypass/issues"
|
||||
>Report issues or requests</a
|
||||
>
|
||||
</main>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
<style lang="scss" global>
|
||||
body {
|
||||
background-color: #2b2a33;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border-radius: 5px;
|
||||
border-color: gray;
|
||||
}
|
||||
|
||||
#report-notice {
|
||||
border: none;
|
||||
color: white;
|
||||
display: block;
|
||||
font-weight: lighter;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 16px;
|
||||
margin: 5px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.setting-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-column-gap: 5px;
|
||||
grid-row-gap: 4px;
|
||||
align-items: end;
|
||||
width: 100%;
|
||||
|
||||
& > label {
|
||||
height: 34px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
& > hr {
|
||||
grid-column: 1 / span 2;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.info-questionmark {
|
||||
display: inline-block;
|
||||
transform: translateX(-40%) translateY(-100%);
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!doctype html>
|
||||
<html style="overflow-y: hidden" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Stream Bypass</title>
|
||||
</head>
|
||||
<body style="overflow-y: scroll">
|
||||
<script type="module">
|
||||
import { mount } from 'svelte';
|
||||
import Popup from '~/entries/popup/Popup.svelte';
|
||||
|
||||
mount(Popup, {
|
||||
target: document.body
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,68 +0,0 @@
|
||||
<!-- https://flowbite.com/docs/forms/toggle/ -->
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
type Props = {
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
id?: string | null;
|
||||
onChange?: () => void;
|
||||
children?: Snippet;
|
||||
};
|
||||
let { checked = $bindable(), disabled, id = null, onChange, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<label class="toggle">
|
||||
{@render children?.()}
|
||||
<input type="checkbox" {id} bind:checked {disabled} onchange={onChange} />
|
||||
<span></span>
|
||||
</label>
|
||||
|
||||
<style lang="scss" global>
|
||||
.toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
clip: rect(0, 0, 0, 0);
|
||||
position: absolute;
|
||||
|
||||
&:checked + span {
|
||||
background: limegreen;
|
||||
|
||||
&:after {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled + span {
|
||||
background: gray;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
width: 2.75rem;
|
||||
height: 1.5rem;
|
||||
background: #cf0000;
|
||||
border-radius: 9999px;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
inset-inline-start: 2px;
|
||||
background: white;
|
||||
border-radius: 9999px;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input:disabled) {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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']
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -3,11 +3,20 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Stream Bypass</title>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-[#131313]">
|
||||
<script type="module">
|
||||
import { mount } from 'svelte';
|
||||
import Player from '~/entries/player/Player.svelte';
|
||||
import Player from './Player.svelte';
|
||||
|
||||
mount(Player, {
|
||||
target: document.body
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
<div class="w-full border-b-[1px] border-gray-400"></div>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { mount } from 'svelte';
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.getElementById('app')!
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<p class="text-[1.05rem]">Extension disabled</p>
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const isMobile = writable(false);
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { HostMatchType, type Host } from '@/lib/host';
|
||||
|
||||
export default {
|
||||
name: 'Vidmoly',
|
||||
id: 'vidmoly',
|
||||
domains: ['vidmoly.me', 'vidmoly.to'],
|
||||
regex: [/(?<=file:").+\.m3u8/gm],
|
||||
replace: true,
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return {
|
||||
type: HostMatchType.HLS,
|
||||
url: match[0]
|
||||
};
|
||||
}
|
||||
} satisfies Host;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,441 +0,0 @@
|
||||
import { Hosters, Redirect, TmpHost } from './settings';
|
||||
import { lastPathSegment } from './util/extract';
|
||||
import { unpack } from './util/userspace';
|
||||
|
||||
export interface Match {
|
||||
name: string;
|
||||
id: string;
|
||||
domains: string[];
|
||||
replace?: boolean;
|
||||
regex: RegExp[];
|
||||
notice?: string;
|
||||
|
||||
match(
|
||||
match: RegExpMatchArray
|
||||
): Promise<
|
||||
string | { [MatchMediaType.Hls]: string } | { [MatchMediaType.Native]: string } | null
|
||||
>;
|
||||
|
||||
// allow other properties that may be implemented by the objects that use this interface declaration
|
||||
[other: string]: any;
|
||||
}
|
||||
|
||||
export enum MatchMediaType {
|
||||
Hls = 'hls',
|
||||
Native = 'native'
|
||||
}
|
||||
|
||||
export const Doodstream: Match = {
|
||||
name: 'Doodstream',
|
||||
id: 'doodstream',
|
||||
domains: [
|
||||
'doodstream.com',
|
||||
'dood.pm',
|
||||
'dood.ws',
|
||||
'dood.wf',
|
||||
'dood.cx',
|
||||
'dood.sh',
|
||||
'dood.watch',
|
||||
'dood.work',
|
||||
'dood.to',
|
||||
'dood.so',
|
||||
'dood.la',
|
||||
'dood.li',
|
||||
'dood.re',
|
||||
'dood.yt',
|
||||
'doods.pro',
|
||||
'ds2play.com',
|
||||
'dooood.com',
|
||||
'd000d.com'
|
||||
],
|
||||
replace: true,
|
||||
regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const response = await fetch(`https://${window.location.host}${match[1]}`, {
|
||||
headers: {
|
||||
Range: 'bytes=0-'
|
||||
},
|
||||
referrer: `https://${window.location.host}/e/${
|
||||
window.location.pathname.split('/').slice(-1)[0]
|
||||
}`
|
||||
});
|
||||
return `${await response.text()}1234567890${match[2]}${Date.now()}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const DropLoad: Match = {
|
||||
name: 'Dropload',
|
||||
id: 'dropload',
|
||||
domains: ['dropload.io'],
|
||||
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Filemoon: Match = {
|
||||
name: 'Filemoon',
|
||||
id: 'filemoon',
|
||||
domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'],
|
||||
regex: [/(?<=<iframe\s*src=")\S*(?=")/s, /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
replace: true,
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
if (window.location.host.startsWith('filemoon')) {
|
||||
await TmpHost.set(new URL(match[0]).host, Filemoon);
|
||||
return null;
|
||||
}
|
||||
|
||||
await TmpHost.delete();
|
||||
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:")\S*(?=")/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const GoodStream: Match = {
|
||||
name: 'Goodstream',
|
||||
id: 'goodstream',
|
||||
domains: ['goodstream.uno'],
|
||||
regex: [/(?<=file:\s+").*(?=")/g],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Kwik: Match = {
|
||||
name: 'Kwik',
|
||||
id: 'kwik',
|
||||
domains: ['kwik.cx'],
|
||||
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=source=').*(?=')/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const LoadX: Match = {
|
||||
name: 'LoadX',
|
||||
id: 'loadx',
|
||||
domains: ['loadx.ws'],
|
||||
regex: [/./gm],
|
||||
|
||||
match: async () => {
|
||||
const hash = encodeURIComponent(lastPathSegment(window.location.href));
|
||||
const response = await fetch(
|
||||
`https://${window.location.host}/player/index.php?data=${hash}&do=getVideo`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const responseJson = await response.json();
|
||||
const videoSource: string = responseJson['videoSource'];
|
||||
|
||||
// extension of extracted url is '.txt', so we have to manually specify that it's a hls
|
||||
return { [MatchMediaType.Hls]: videoSource.replace('\\/', '/') };
|
||||
}
|
||||
};
|
||||
|
||||
export const Luluvdo: Match = {
|
||||
name: 'Luluvdo',
|
||||
id: 'luluvdo',
|
||||
domains: ['luluvdo.com'],
|
||||
regex: [/./gm],
|
||||
|
||||
match: async () => {
|
||||
const requestBody = new FormData();
|
||||
requestBody.set('op', 'embed');
|
||||
requestBody.set('file_code', lastPathSegment(window.location.href));
|
||||
const response = await fetch(`https://${window.location.host}/dl`, {
|
||||
method: 'POST',
|
||||
body: requestBody,
|
||||
referrer: window.location.href
|
||||
});
|
||||
|
||||
let unpacked;
|
||||
|
||||
const responseText = await response.text();
|
||||
const evalMatch = responseText.match(/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms)!;
|
||||
// sometimes is packed, sometimes it's not. looks like someone forgets to obfuscate the code when pushing to
|
||||
// production
|
||||
if (evalMatch) {
|
||||
unpacked = await unpack(evalMatch[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||
} else {
|
||||
unpacked = responseText;
|
||||
}
|
||||
|
||||
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Mixdrop: Match = {
|
||||
name: 'Mixdrop',
|
||||
id: 'mixdrop',
|
||||
domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
|
||||
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const unpacked = await unpack(match[0]);
|
||||
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
|
||||
return `https:${url}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const Mp4Upload: Match = {
|
||||
name: 'Mp4Upload',
|
||||
id: 'mp4upload',
|
||||
domains: ['mp4upload.com'],
|
||||
replace: true,
|
||||
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Newgrounds: Match = {
|
||||
name: 'Newgrounds',
|
||||
id: 'newgrounds',
|
||||
domains: ['newgrounds.com'],
|
||||
regex: [/.*/gm],
|
||||
|
||||
match: async () => {
|
||||
const id = window.location.pathname.split('/').slice(-1)[0];
|
||||
const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
});
|
||||
const json = await response.json();
|
||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']);
|
||||
}
|
||||
};
|
||||
|
||||
export const StreamA2z: Match = {
|
||||
name: 'Stream2Az',
|
||||
id: 'stream2az',
|
||||
domains: ['streama2z.com', 'streama2z.xyz'],
|
||||
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
|
||||
await Redirect.set(StreamA2z);
|
||||
return null;
|
||||
}
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Streamtape: Match = {
|
||||
name: 'Streamtape',
|
||||
id: 'streamtape',
|
||||
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
|
||||
regex: [/id=.*(?=')/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
let i = 0;
|
||||
while (i < match.length) {
|
||||
if (match[++i - 1] == match[i]) {
|
||||
return `https://streamtape.com/get_video?${match[i]}`;
|
||||
}
|
||||
}
|
||||
|
||||
// use the old method as fallback
|
||||
return `https://streamtape.com/get_video?${match.reverse()[0]}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const Streamzz: Match = {
|
||||
name: 'Streamzz',
|
||||
id: 'streamzz',
|
||||
domains: ['streamzz.to', 'streamz.ws'],
|
||||
regex: [/(?<=\|)\w{2,}/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
|
||||
match.sort((a, b) => b.length - a.length)[0]
|
||||
}.dll`;
|
||||
}
|
||||
};
|
||||
|
||||
export const SuperVideo: Match = {
|
||||
name: 'Supervideo',
|
||||
id: 'supervideo',
|
||||
domains: ['supervideo.cc', 'supervideo.tv'],
|
||||
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Upstream: Match = {
|
||||
name: 'Upstream',
|
||||
id: 'upstream',
|
||||
domains: ['upstream.to'],
|
||||
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)![0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Vidmoly: Match = {
|
||||
name: 'Vidmoly',
|
||||
id: 'vidmoly',
|
||||
domains: ['vidmoly.me', 'vidmoly.to'],
|
||||
regex: [/(?<=file:").+\.m3u8/gm],
|
||||
replace: true,
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Vidoza: Match = {
|
||||
name: 'Vidoza',
|
||||
id: 'vidoza',
|
||||
domains: ['vidoza.net', 'videzz.net'],
|
||||
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||
replace: true,
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Voe: Match = {
|
||||
name: 'Voe',
|
||||
id: 'voe',
|
||||
domains: ['voe.sx'],
|
||||
regex: [
|
||||
// voe.sx
|
||||
/(?<=window\.location\.href\s=\s')\S*(?=')/gm,
|
||||
// whatever site voe.sx redirects to
|
||||
/(?<=<script type="application\/json">).*(?=<\/script>)/m
|
||||
],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
if (window.location.host === 'voe.sx') {
|
||||
const redirectUrl = new URL(match[0]);
|
||||
await TmpHost.set(redirectUrl.host, Voe);
|
||||
return null;
|
||||
} else {
|
||||
let json = match[0];
|
||||
json = JSON.parse(json);
|
||||
|
||||
let deobfuscated = json[0];
|
||||
deobfuscated = this.rot13(deobfuscated);
|
||||
deobfuscated = this.removeSpecialSequences(deobfuscated);
|
||||
deobfuscated = atob(deobfuscated);
|
||||
deobfuscated = this.shiftString(deobfuscated);
|
||||
deobfuscated = deobfuscated.split('').reverse().join('');
|
||||
deobfuscated = atob(deobfuscated);
|
||||
|
||||
const payload = JSON.parse(deobfuscated);
|
||||
|
||||
return payload['source'];
|
||||
}
|
||||
},
|
||||
|
||||
rot13: function (encrypted: string) {
|
||||
let decrypted = '';
|
||||
for (let i = 0; i < encrypted.length; i++) {
|
||||
let char = encrypted.charCodeAt(i);
|
||||
if (char >= 65 && char <= 90) {
|
||||
char = ((char - 65 + 13) % 26) + 65;
|
||||
} else if (char >= 97 && char <= 122) {
|
||||
char = ((char - 97 + 13) % 26) + 97;
|
||||
}
|
||||
decrypted += String.fromCharCode(char);
|
||||
}
|
||||
return decrypted;
|
||||
},
|
||||
removeSpecialSequences: function (input: string) {
|
||||
return input
|
||||
.replaceAll(/@\$/g, '')
|
||||
.replaceAll(/\^\^/g, '')
|
||||
.replaceAll(/~@/g, '')
|
||||
.replaceAll(/%\?/g, '')
|
||||
.replaceAll(/\*~/g, '')
|
||||
.replaceAll(/!!/g, '')
|
||||
.replaceAll(/#&/g, '');
|
||||
},
|
||||
shiftString: function (input: string) {
|
||||
let shifted = '';
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const char = input.charCodeAt(i);
|
||||
const shiftedChar = char - 3;
|
||||
shifted += String.fromCharCode(shiftedChar);
|
||||
}
|
||||
return shifted;
|
||||
}
|
||||
};
|
||||
|
||||
export const Vupload: Match = {
|
||||
name: 'Vupload',
|
||||
id: 'vupload',
|
||||
domains: ['vupload.com'],
|
||||
regex: [/(?<=src:\s?").+?(?=")/gm],
|
||||
|
||||
match: async function (match: RegExpMatchArray) {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const matches = {
|
||||
[Doodstream.id]: Doodstream,
|
||||
[DropLoad.id]: DropLoad,
|
||||
[Filemoon.id]: Filemoon,
|
||||
[GoodStream.id]: GoodStream,
|
||||
[Kwik.id]: Kwik,
|
||||
[LoadX.id]: LoadX,
|
||||
[Luluvdo.id]: Luluvdo,
|
||||
[Mixdrop.id]: Mixdrop,
|
||||
[Mp4Upload.id]: Mp4Upload,
|
||||
[Newgrounds.id]: Newgrounds,
|
||||
[StreamA2z.id]: StreamA2z,
|
||||
[Streamtape.id]: Streamtape,
|
||||
[Streamzz.id]: Streamzz,
|
||||
[SuperVideo.id]: SuperVideo,
|
||||
[Upstream.id]: Upstream,
|
||||
[Vidmoly.id]: Vidmoly,
|
||||
[Vidoza.id]: Vidoza,
|
||||
[Voe.id]: Voe,
|
||||
[Vupload.id]: Vupload
|
||||
};
|
||||
|
||||
export async function getMatch(domain: string): Promise<Match | null> {
|
||||
if (await Hosters.getAllDisabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const match of Object.values(matches)) {
|
||||
if (
|
||||
match.domains.indexOf(domain) !== -1 &&
|
||||
!(await Hosters.getDisabled().then((d) => d.find((p) => p.id == match.id)))
|
||||
) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
const tmpHost = await TmpHost.get();
|
||||
if (tmpHost && tmpHost[0] === domain) {
|
||||
return tmpHost[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
+66
-90
@@ -1,112 +1,88 @@
|
||||
import { matches, type Match } from './match';
|
||||
import { storage } from '#imports';
|
||||
import { hosts, type Host } from '@/lib/host';
|
||||
|
||||
export const Hosters = {
|
||||
getDisabled: async () => {
|
||||
const disabled = (await storageGet('hosters.disabled', [])) as string[];
|
||||
return disabled.map((id) => matches[id]).filter((m) => m !== undefined);
|
||||
},
|
||||
disable: async (match: Match) => {
|
||||
const disabled = (await storageGet('hosters.disabled', [])) as string[];
|
||||
const index = disabled.indexOf(match.id);
|
||||
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) {
|
||||
disabled.push(match.id);
|
||||
await storageSet('hosters.disabled', disabled);
|
||||
ids.push(host.id);
|
||||
await this.disabledHosts.setValue(ids);
|
||||
}
|
||||
},
|
||||
enable: async (match: Match) => {
|
||||
const disabled = (await storageGet('hosters.disabled', [])) as string[];
|
||||
const index = disabled.indexOf(match.id);
|
||||
}
|
||||
static async removeDisabledHost(host: Host) {
|
||||
const ids = await this.disabledHosts.getValue();
|
||||
|
||||
const index = ids.indexOf(host.id);
|
||||
if (index !== -1) {
|
||||
disabled.splice(index, 1);
|
||||
await storageSet('hosters.disabled', disabled);
|
||||
ids.splice(index, 1);
|
||||
await this.disabledHosts.setValue(ids);
|
||||
}
|
||||
},
|
||||
getAllDisabled: async () => {
|
||||
return await storageGet<boolean>('hosters.allDisabled', false);
|
||||
},
|
||||
setAll: async (enable: boolean) => {
|
||||
await storageSet('hosters.allDisabled', !enable);
|
||||
}
|
||||
};
|
||||
|
||||
export const Redirect = {
|
||||
get: async (): Promise<Match | null> => {
|
||||
return matches[(await storageGet('redirect')) as string] || null;
|
||||
},
|
||||
set: async (match: Match) => {
|
||||
await storageSet('redirect', match.id);
|
||||
},
|
||||
delete: async () => {
|
||||
await storageDelete('redirect');
|
||||
static async getDisabledHosts() {
|
||||
return await this.disabledHosts.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
export const TmpHost = {
|
||||
get: async (): Promise<[string, Match] | null> => {
|
||||
const tmphost = await storageGet<[string, number]>('tmphost');
|
||||
if (tmphost === undefined) {
|
||||
return null;
|
||||
}
|
||||
return [tmphost[0], matches[tmphost[1]]];
|
||||
},
|
||||
set: async (domain: string, match: Match) => {
|
||||
await storageSet('tmphost', [domain, match.id]);
|
||||
},
|
||||
delete: async () => {
|
||||
await storageDelete('tmphost');
|
||||
static async getAllHostsDisabled() {
|
||||
return await this.allHostsDisabled.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
export const UrlReferer = {
|
||||
get: async (url: string): Promise<string | null> => {
|
||||
return (await storageGet(`urlReferer.${url}`)) || null;
|
||||
},
|
||||
set: async (url: string, referer: string) => {
|
||||
await storageSet(`urlReferer.${url}`, referer);
|
||||
},
|
||||
delete: async (url: string) => {
|
||||
await storageDelete(`urlReferer.${url}`);
|
||||
static async setAllHostsDisabled(disabled: boolean) {
|
||||
await this.allHostsDisabled.setValue(disabled);
|
||||
}
|
||||
};
|
||||
|
||||
export const Other = {
|
||||
getFf2mpv: async () => {
|
||||
return await storageGet('other.ff2mpv', false);
|
||||
},
|
||||
setFf2mpv: async (enable: boolean) => {
|
||||
await storageSet('other.ff2mpv', enable);
|
||||
}
|
||||
};
|
||||
|
||||
async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
|
||||
let resolve: (value: T | undefined) => void;
|
||||
const promise = new Promise<T | undefined>((r) => (resolve = r));
|
||||
|
||||
chrome.storage.local.get(key, (entry) => {
|
||||
const value = entry[key];
|
||||
resolve(value === undefined ? defaultValue : value);
|
||||
/* tmp */
|
||||
private static temporaryHostDomain = storage.defineItem<Record<string, string>>('local:temporaryHostDomain', {
|
||||
fallback: {}
|
||||
});
|
||||
|
||||
return promise;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
async function storageSet<T>(key: string, value: T) {
|
||||
let resolve: () => void;
|
||||
const promise = new Promise<void>((r) => (resolve = r));
|
||||
export class FF2MPVSettings {
|
||||
private static ff2mpvEnabled = storage.defineItem<boolean>('local:ff2mpv', { fallback: false });
|
||||
|
||||
const obj = {
|
||||
[key]: value
|
||||
};
|
||||
chrome.storage.local.set(obj, () => resolve());
|
||||
|
||||
return promise;
|
||||
static async getEnabled() {
|
||||
return await this.ff2mpvEnabled.getValue();
|
||||
}
|
||||
static async setEnabled(enabled: boolean) {
|
||||
console.log('set', enabled);
|
||||
await this.ff2mpvEnabled.setValue(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
async function storageDelete(key: string) {
|
||||
let resolve: () => void;
|
||||
const promise = new Promise<void>((r) => (resolve = r));
|
||||
export class UrlReferer {
|
||||
private static temporaryUrlReferer = storage.defineItem<Record<string, string>>('local:temporaryUrlReferer', {
|
||||
fallback: {}
|
||||
});
|
||||
|
||||
chrome.storage.local.remove(key, () => resolve());
|
||||
static async addTemporary(hostname: string, referer: string) {
|
||||
const tmpUrlReferer = await this.temporaryUrlReferer.getValue();
|
||||
|
||||
return promise;
|
||||
tmpUrlReferer[hostname] = referer;
|
||||
await this.temporaryUrlReferer.setValue(tmpUrlReferer);
|
||||
}
|
||||
|
||||
static async get(hostname: string) {
|
||||
const tmpUrlReferer = await this.temporaryUrlReferer.getValue();
|
||||
|
||||
return tmpUrlReferer[hostname] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import pkg from '../package.json';
|
||||
import { matches } from './lib/match';
|
||||
|
||||
const sharedManifest: Partial<chrome.runtime.ManifestBase> = {
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: '{55dd42e8-3dd9-455a-b4fe-86664881b10c}'
|
||||
},
|
||||
gecko_android: {}
|
||||
},
|
||||
content_scripts: [
|
||||
{
|
||||
all_frames: true,
|
||||
matches: Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`)),
|
||||
js: ['src/entries/contentScript/main.ts'],
|
||||
run_at: 'document_end'
|
||||
}
|
||||
],
|
||||
icons: {
|
||||
16: 'icons/stream-bypass@16px.png',
|
||||
32: 'icons/stream-bypass@32px.png',
|
||||
48: 'icons/stream-bypass@48px.png',
|
||||
96: 'icons/stream-bypass@96px.png',
|
||||
128: 'icons/stream-bypass@128px.png'
|
||||
},
|
||||
permissions: ['storage'],
|
||||
optional_permissions: ['nativeMessaging']
|
||||
};
|
||||
|
||||
const browserAction = {
|
||||
default_icon: {
|
||||
16: 'icons/stream-bypass@16px.png',
|
||||
32: 'icons/stream-bypass@32px.png'
|
||||
},
|
||||
default_popup: 'src/entries/popup/index.html'
|
||||
};
|
||||
|
||||
const ManifestV2 = {
|
||||
...sharedManifest,
|
||||
background: {
|
||||
scripts: ['src/entries/background/mv2.ts'],
|
||||
persistent: true
|
||||
},
|
||||
content_scripts: [{ ...sharedManifest.content_scripts![0], matches: ['<all_urls>'] }],
|
||||
browser_action: browserAction,
|
||||
permissions: [...sharedManifest.permissions, 'webRequest', 'webRequestBlocking', '<all_urls>']
|
||||
};
|
||||
|
||||
const ManifestV3 = {
|
||||
...sharedManifest,
|
||||
action: browserAction,
|
||||
background: {
|
||||
service_worker: 'src/entries/background/mv3.ts'
|
||||
}
|
||||
};
|
||||
|
||||
export function getManifest(
|
||||
manifestVersion: number
|
||||
): chrome.runtime.ManifestV2 | chrome.runtime.ManifestV3 {
|
||||
const manifest = {
|
||||
author: pkg.author,
|
||||
description: pkg.description,
|
||||
name: pkg.displayName ?? pkg.name,
|
||||
version: pkg.version
|
||||
};
|
||||
|
||||
if (manifestVersion === 2) {
|
||||
return {
|
||||
...manifest,
|
||||
...ManifestV2,
|
||||
manifest_version: manifestVersion
|
||||
} as chrome.runtime.ManifestV2;
|
||||
}
|
||||
|
||||
if (manifestVersion === 3) {
|
||||
return {
|
||||
...manifest,
|
||||
// just like all the adblockers which are unable to fully work under MV3, we need access to every website
|
||||
// the user enters in order to work correctly, which is forbidden when using MV3
|
||||
name: `${manifest.name} Lite`,
|
||||
...ManifestV3,
|
||||
manifest_version: manifestVersion
|
||||
} as chrome.runtime.ManifestV3;
|
||||
}
|
||||
|
||||
throw new Error(`Missing manifest definition for manifestVersion ${manifestVersion}`);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="@samrum/vite-plugin-web-extension/client" />
|
||||
@@ -1,7 +0,0 @@
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
|
||||
export default {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
||||
+3
-15
@@ -1,18 +1,6 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"extends": "./.wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2021",
|
||||
"module": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"resolveJsonModule": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["src/*"]
|
||||
},
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
"useDefineForClassFields": true
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { fileURLToPath } from 'url';
|
||||
import webExtension from '@samrum/vite-plugin-web-extension';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import { defineConfig, loadEnv, type PluginOption } from 'vite';
|
||||
import { matches } from './src/lib/match';
|
||||
import { getManifest } from './src/manifest';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
svelte() as PluginOption,
|
||||
webExtension({
|
||||
manifest: getManifest(Number(env.MANIFEST_VERSION)),
|
||||
additionalInputs: {
|
||||
html: [
|
||||
{
|
||||
fileName: 'src/entries/player/player.html',
|
||||
webAccessible: {
|
||||
matches:
|
||||
Number(env.MANIFEST_VERSION) === 3
|
||||
? Object.values(matches).flatMap((m) => m.domains.map((d) => `*://${d}/*`))
|
||||
: ['<all_urls>']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}) as unknown as PluginOption
|
||||
],
|
||||
resolve: {
|
||||
alias: [{ find: '~', replacement: fileURLToPath(new URL('./src', import.meta.url)) }]
|
||||
}
|
||||
};
|
||||
});
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="svelte" />
|
||||
@@ -0,0 +1,45 @@
|
||||
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']
|
||||
}),
|
||||
|
||||
vite: () => ({
|
||||
define: {
|
||||
'import.meta.env.VERSION': JSON.stringify(version)
|
||||
},
|
||||
plugins: [tailwindcss()]
|
||||
}),
|
||||
|
||||
svelte: {
|
||||
vite: {
|
||||
preprocess: vitePreprocess({})
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user