Rewrite
9
.eslintignore
Normal file
@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
/release
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
package-lock.json
|
33
.eslintrc.cjs
Normal file
@ -0,0 +1,33 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off'
|
||||
}
|
||||
};
|
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
.idea/
|
||||
build/
|
||||
dist/
|
||||
release/
|
||||
node_modules/
|
||||
|
2
.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
auto-install-peers=false
|
||||
strict-peer-dependencies=false
|
8
.prettierignore
Normal file
@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
package-lock.json
|
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
17
README.md
@ -70,7 +70,7 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
|
||||
## 📜 Supported sites
|
||||
|
||||
| Site | Supported | Note |
|
||||
|-----------------------------------------------------------------------|-----------|--------------------------------------------------------------------------------------------------------------|
|
||||
| --------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | |
|
||||
| [filemoon.sx](https://filemoon.sx) | ✔ | |
|
||||
| [mcloud.to](https://mcloud.to/) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) |
|
||||
@ -110,10 +110,12 @@ Some sites put much effort in obfuscating their code / how they receive the vide
|
||||
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
|
||||
|
||||
Requirements:
|
||||
|
||||
- `npm` installed.
|
||||
- A copy of this repository and a shell / console open in the copied directory.
|
||||
|
||||
If the requirements are satisfied, you can continue with the following commands:
|
||||
|
||||
```shell
|
||||
# install all dependencies
|
||||
$ npm install
|
||||
@ -129,22 +131,24 @@ $ npm run bundle
|
||||
|
||||
If you want to use the addon in Chromium or any browser which is based on it (almost every other, Google Chrome, Opera, ...), follow the steps in [installation](#-installation).
|
||||
When using firefox, use the following:
|
||||
|
||||
1. Type `about:debugging` in the browser's address bar.
|
||||
2. Select 'This Firefox' tab (maybe named different, depending on your language).
|
||||
3. Under `Temporary Extensions`, click `Load Temporary Add-on`.
|
||||
4. Choose any file in the directory where the compiled sources are.
|
||||
|
||||
|
||||
## ⚙️ Settings
|
||||
|
||||
### <ins>ff2mpv: use mpv to directly play streams</ins>
|
||||
|
||||
ff2mpv is located at this repository: https://github.com/woodruffw/ff2mpv
|
||||
|
||||
Steps to get it set up:
|
||||
- In the [Usage](https://github.com/woodruffw/ff2mpv#usage) section of the ff2mpv repository pick the installation instruction for your operating system (Linux/Windows/macOS; you do not need the browser addon).
|
||||
- Scroll down to `Install manually`
|
||||
- Follow instructions for Firefox/Chrome
|
||||
- Edit the `ff2mpv.json` you created:
|
||||
|
||||
- In the [Usage](https://github.com/woodruffw/ff2mpv#usage) section of the ff2mpv repository pick the installation instruction for your operating system (Linux/Windows/macOS; you do not need the browser addon).
|
||||
- Scroll down to `Install manually`
|
||||
- Follow instructions for Firefox/Chrome
|
||||
- Edit the `ff2mpv.json` you created:
|
||||
- Firefox: Add `{55dd42e8-3dd9-455a-b4fe-86664881b10c}` to `allowed_extensions` ->
|
||||
```
|
||||
"allowed_extensions": [
|
||||
@ -163,7 +167,6 @@ Steps to get it set up:
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## ⚖ License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for more details.
|
||||
|
7637
package-lock.json
generated
Normal file
62
package.json
@ -1,40 +1,52 @@
|
||||
{
|
||||
"name": "stream-bypass",
|
||||
"version": "2.2.0",
|
||||
"version": "3.0.0",
|
||||
"displayName": "Stream Bypass",
|
||||
"author": "ByteDream",
|
||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "node tasks/build.ts",
|
||||
"bundle": "node tasks/build.ts && web-ext build -s build -a dist -o",
|
||||
"bundle:all": "node tasks/build.ts && web-ext build -s build -a dist -o && web-ext sign -s build -a dist",
|
||||
"check": "web-ext lint -s build"
|
||||
"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 --plugin prettier-plugin-svelte . && eslint .",
|
||||
"format": "prettier --write --plugin prettier-plugin-svelte .",
|
||||
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
|
||||
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ByteDream/stream-bypass.git"
|
||||
},
|
||||
"author": "ByteDream",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ByteDream/stream-bypass/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^11.1.0",
|
||||
"@types/chrome": "^0.0.234",
|
||||
"@types/node-sass": "^4.11.2",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"eslint": "^8.17.0",
|
||||
"hls.js": "^1.1.5",
|
||||
"rollup": "^3.21.0",
|
||||
"sass": "^1.62.1",
|
||||
"tippy.js": "^6.3.7",
|
||||
"@samrum/vite-plugin-web-extension": "^5.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||
"@tsconfig/svelte": "^4.0.1",
|
||||
"@types/chrome": "^0.0.228",
|
||||
"@types/webextension-polyfill": "^0.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-svelte": "^2.35.0",
|
||||
"hls.js": "^1.4.12",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"sass": "^1.69.5",
|
||||
"svelte": "^3.58.0",
|
||||
"svelte-check": "^3.2.0",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.4",
|
||||
"web-ext": "^7.0.0"
|
||||
}
|
||||
"vite": "~4.3.3",
|
||||
"web-ext": "^7.6.2",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
1
public/icons/stream-bypass.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:#fff;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/stream-bypass@128px.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
public/icons/stream-bypass@16px.png
Normal file
After Width: | Height: | Size: 713 B |
BIN
public/icons/stream-bypass@32px.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/stream-bypass@48px.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
public/icons/stream-bypass@96px.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
1
public/icons/stream-bypass_disabled.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:gray;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/stream-bypass_disabled@128px.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
public/icons/stream-bypass_disabled@16px.png
Normal file
After Width: | Height: | Size: 792 B |
BIN
public/icons/stream-bypass_disabled@32px.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/icons/stream-bypass_disabled@48px.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
public/icons/stream-bypass_disabled@96px.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
@ -1,25 +0,0 @@
|
||||
import {getMatch} from "./match/match";
|
||||
import {storageDelete, storageGet, storageSet} from "./store/store";
|
||||
import {Match} from "./match/matches";
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender) => {
|
||||
if (message.action == "ff2mpv") {
|
||||
chrome.runtime.sendNativeMessage("ff2mpv", {url: message.url})
|
||||
.catch((error) => {console.error(error)})
|
||||
}
|
||||
})
|
||||
|
||||
chrome.webRequest.onBeforeRedirect.addListener(async details => {
|
||||
// check if redirects origins from a previous redirect
|
||||
if (await storageGet('redirect') === undefined) {
|
||||
let match: Match
|
||||
if ((match = await getMatch(new URL(details.url).host)) !== undefined) {
|
||||
await storageSet('redirect', match.id)
|
||||
}
|
||||
} else {
|
||||
await storageDelete('redirect')
|
||||
}
|
||||
}, {
|
||||
urls: ['<all_urls>'],
|
||||
types: ['main_frame', 'sub_frame']
|
||||
})
|
24
src/entries/background/main.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Match } from '~/lib/match';
|
||||
import { storageDelete, storageGet, storageSet } from '~/lib/settings';
|
||||
import { getMatch } from '~/lib/match';
|
||||
|
||||
chrome.runtime.onMessage.addListener(async (message) => {
|
||||
if (message.action == 'ff2mpv') {
|
||||
await chrome.runtime.sendNativeMessage('ff2mpv', { url: message.url });
|
||||
}
|
||||
});
|
||||
|
||||
chrome.webRequest.onBeforeRedirect.addListener(
|
||||
async (details) => {
|
||||
// check if redirects origins from a previous redirect
|
||||
if ((await storageGet('redirect')) === undefined) {
|
||||
let match: Match;
|
||||
if ((match = await getMatch(new URL(details.url).hostname)) !== undefined) {
|
||||
await storageSet('redirect', match.id);
|
||||
}
|
||||
} else {
|
||||
await storageDelete('redirect');
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'], types: ['main_frame', 'sub_frame'] }
|
||||
);
|
51
src/entries/contentScript/main.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { Match } from '~/lib/match';
|
||||
import { getMatch } from '~/lib/match';
|
||||
import { Other, Redirect } from '~/lib/settings';
|
||||
import browser from 'webextension-polyfill';
|
||||
|
||||
async function main() {
|
||||
let match: Match;
|
||||
let redirect = false;
|
||||
if ((match = await getMatch(window.location.host)) === null) {
|
||||
if ((match = await Redirect.get()) === null) {
|
||||
return;
|
||||
}
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
const re = document.body.innerHTML.match(match.regex);
|
||||
if (re === null) {
|
||||
return;
|
||||
}
|
||||
if (redirect) {
|
||||
await Redirect.delete();
|
||||
}
|
||||
|
||||
const url = await match.match(re);
|
||||
|
||||
// send the url to the ff2mpv (https://github.com/woodruffw/ff2mpv) application
|
||||
if (await Other.getFf2mpv()) {
|
||||
await browser.runtime.sendMessage({ action: 'ff2mpv', url: url });
|
||||
}
|
||||
|
||||
if (match.replace && !url.includes('.m3u8')) {
|
||||
const player = document.createElement('video');
|
||||
player.style.width = '100%';
|
||||
player.style.height = '100%';
|
||||
player.controls = true;
|
||||
player.src = url;
|
||||
|
||||
document.body.innerHTML = '';
|
||||
document.body.append(player);
|
||||
} else {
|
||||
window.location.assign(
|
||||
browser.runtime.getURL(
|
||||
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
|
||||
window.location.hostname
|
||||
}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
62
src/entries/player/Player.svelte
Normal file
@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { play } from '~/entries/player/player';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let errorMessage: string | null = null;
|
||||
let videoElem: HTMLVideoElement;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await play(videoElem);
|
||||
videoElem.controls = true;
|
||||
} catch (e) {
|
||||
errorMessage = e;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video id="video" bind:this={videoElem} />
|
||||
{#if errorMessage}
|
||||
<div id="message-container">
|
||||
<p>
|
||||
{@html errorMessage}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<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>
|
16
src/entries/player/player.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Stream Bypass</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import Player from '~/entries/player/Player.svelte';
|
||||
|
||||
new Player({
|
||||
target: document.body
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
39
src/entries/player/player.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { matches } from '~/lib/match';
|
||||
import Hls from 'hls.js';
|
||||
|
||||
async function playNative(url: string, videoElem: HTMLVideoElement) {
|
||||
videoElem.src = url;
|
||||
}
|
||||
|
||||
async function playHls(url: string, videoElem: HTMLVideoElement) {
|
||||
if (videoElem.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
videoElem.src = url;
|
||||
} else if (Hls.isSupported()) {
|
||||
const hls = new Hls({
|
||||
enableWorker: false
|
||||
});
|
||||
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');
|
||||
const url = decodeURIComponent(urlQuery.get('url'));
|
||||
const domain = urlQuery.get('domain');
|
||||
|
||||
const match = matches[id];
|
||||
if (match === undefined) {
|
||||
throw `Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`;
|
||||
}
|
||||
document.title = `Stream Bypass (${domain})`;
|
||||
|
||||
if (new URL(url).pathname.endsWith('.m3u8')) {
|
||||
await playHls(url, videoElem);
|
||||
} else {
|
||||
await playNative(url, videoElem);
|
||||
}
|
||||
}
|
217
src/entries/popup/Popup.svelte
Normal file
@ -0,0 +1,217 @@
|
||||
<script lang="ts">
|
||||
import { matches, Reliability } from '~/lib/match';
|
||||
import { Hosters, Other } from '~/lib/settings';
|
||||
|
||||
let hostersEnabled: boolean;
|
||||
let hosters = [];
|
||||
(async () => {
|
||||
hostersEnabled = !(await Hosters.getAllDisabled());
|
||||
|
||||
const disabled = await Hosters.getDisabled();
|
||||
hosters = Object.values(matches).map((m) => {
|
||||
m['active'] = disabled.findIndex((p) => p.id == m.id) == -1;
|
||||
m['disabled'] = !hostersEnabled;
|
||||
return m;
|
||||
});
|
||||
})();
|
||||
|
||||
let ff2mpvEnabled: boolean;
|
||||
(async () => {
|
||||
ff2mpvEnabled = await Other.getFf2mpv();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
<h3 class="header">Hosters</h3>
|
||||
<div class="buttons super-buttons">
|
||||
<button
|
||||
class:active={hostersEnabled}
|
||||
on:click={async () => {
|
||||
await Hosters.enableAll();
|
||||
hostersEnabled = true;
|
||||
hosters = hosters.map((m) => {
|
||||
m['disabled'] = false;
|
||||
return m;
|
||||
});
|
||||
}}>On</button
|
||||
>
|
||||
<button
|
||||
class:active={!hostersEnabled}
|
||||
on:click={async () => {
|
||||
await Hosters.disableAll();
|
||||
hostersEnabled = false;
|
||||
hosters = hosters.map((m) => {
|
||||
m['disabled'] = true;
|
||||
return m;
|
||||
});
|
||||
}}>Off</button
|
||||
>
|
||||
</div>
|
||||
<table class="setting-table">
|
||||
{#each hosters as hoster}
|
||||
<tr>
|
||||
<td class="setting-name">
|
||||
<p
|
||||
class:reliability-low={hoster.reliability === Reliability.LOW}
|
||||
class:reliability-normal={hoster.reliability === Reliability.NORMAL}
|
||||
class:reliability-high={hoster.reliability === Reliability.HIGH}
|
||||
>
|
||||
{hoster.name}
|
||||
</p>
|
||||
</td>
|
||||
<td class="buttons">
|
||||
<button
|
||||
class:disabled={hoster.disabled}
|
||||
class:active={hoster.active}
|
||||
on:click={async () => {
|
||||
if (hoster.disabled) return;
|
||||
await Hosters.enable(hoster);
|
||||
hoster.active = true;
|
||||
}}>On</button
|
||||
>
|
||||
<button
|
||||
class:disabled={hoster.disabled}
|
||||
class:active={!hoster.active}
|
||||
on:click={async () => {
|
||||
if (hoster.disabled) return;
|
||||
await Hosters.disable(hoster);
|
||||
hoster.active = false;
|
||||
}}>Off</button
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
<h3 class="header">Other</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="setting-name">
|
||||
<p>ff2mpv</p>
|
||||
</td>
|
||||
<td class="buttons">
|
||||
<button
|
||||
class:active={ff2mpvEnabled}
|
||||
on:click={async () => {
|
||||
await Other.setFf2mpv(true);
|
||||
ff2mpvEnabled = true;
|
||||
}}>On</button
|
||||
>
|
||||
<button
|
||||
class:active={!ff2mpvEnabled}
|
||||
on:click={async () => {
|
||||
await Other.setFf2mpv(false);
|
||||
ff2mpvEnabled = false;
|
||||
}}>Off</button
|
||||
>
|
||||
<a
|
||||
href="https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams"
|
||||
>🛈</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr />
|
||||
<a id="bug-notice" href="https://github.com/ByteDream/stream-bypass/issues"
|
||||
>Something does not work</a
|
||||
>
|
||||
</main>
|
||||
|
||||
<style lang="scss" global>
|
||||
body {
|
||||
background-color: #2b2a33;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#bug-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-table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.setting-name {
|
||||
height: 34px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 34px;
|
||||
|
||||
button,
|
||||
a {
|
||||
border: 1px solid #281515;
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px 8px;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
|
||||
&.active {
|
||||
background-color: rgba(255, 65, 65, 0.74);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: gray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&.super-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.reliability-low {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: red;
|
||||
}
|
||||
.reliability-normal {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: yellow;
|
||||
}
|
||||
.reliability-high {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #00ff00;
|
||||
}
|
||||
</style>
|
16
src/entries/popup/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Stream Bypass</title>
|
||||
</head>
|
||||
<body style="width: fit-content; height: 500px">
|
||||
<script type="module">
|
||||
import Popup from '~/entries/popup/Popup.svelte';
|
||||
|
||||
new Popup({
|
||||
target: document.body
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
5
src/entries/popup/main.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import App from './Popup.svelte';
|
||||
|
||||
new App({
|
||||
target: document.getElementById('app')
|
||||
});
|
@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="195.32812mm"
|
||||
height="195.32812mm"
|
||||
viewBox="0 0 195.32812 195.32812"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="logo-disabled.svg"
|
||||
inkscape:export-filename="disabled_128.png"
|
||||
inkscape:export-xdpi="16.644812"
|
||||
inkscape:export-ydpi="16.644812"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.90509668"
|
||||
inkscape:cx="428.68349"
|
||||
inkscape:cy="362.94465"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:path-effect
|
||||
effect="powerstroke"
|
||||
id="path-effect3140"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
offset_points="3,0"
|
||||
not_jump="false"
|
||||
sort_points="true"
|
||||
interpolator_type="CubicBezierJohan"
|
||||
interpolator_beta="0.2"
|
||||
start_linecap_type="zerowidth"
|
||||
linejoin_type="extrp_arc"
|
||||
miter_limit="4"
|
||||
scale_width="1"
|
||||
end_linecap_type="zerowidth" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-11.238281,-30.525391)">
|
||||
<path
|
||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
||||
d="m 108.90234,30.525391 c -53.858772,0 -97.664059,43.805286 -97.664059,97.664059 0,53.85878 43.805287,97.66407 97.664059,97.66407 53.85878,0 97.66407,-43.80529 97.66407,-97.66407 0,-53.858773 -43.80529,-97.664059 -97.66407,-97.664059 z m 0,13.433593 c 46.59895,0 84.23047,37.631526 84.23047,84.230466 0,46.59895 -37.63152,84.23047 -84.23047,84.23047 -46.59894,0 -84.232418,-37.63152 -84.232418,-84.23047 0,-46.59894 37.633478,-84.230466 84.232418,-84.230466 z"
|
||||
id="path2592" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
||||
d="m 8.9863281,105.29688 v 96.92578 l 83.9394529,-48.46289 -7.699219,-4.44532 z m 10.2656249,17.78125 53.142578,30.68164 -53.142578,30.68164 z"
|
||||
id="path2646"
|
||||
transform="matrix(1.3085265,0,0,1.3085265,56.033246,-73.00885)" />
|
||||
<path
|
||||
style="fill:#808080;stroke:#000000;stroke-width:5.46616"
|
||||
d="M 345.13114,686.08528 C 222.64086,675.79533 119.51392,599.94853 74.016249,486.68834 64.419992,462.79978 58.296197,439.49115 53.966274,410.37335 51.203358,391.79333 51.205528,346.25661 53.970219,327.58931 61.4703,276.94849 77.618278,233.84988 103.92601,194.25835 143.90493,134.09253 200.40964,90.698198 268.39759,67.947934 322.37862,49.884696 378.75198,46.533168 433.87378,58.109989 c 82.82951,17.396066 153.43381,65.362801 200.69115,136.344381 10.701,16.07313 25.83502,46.0053 32.67962,64.63397 6.70243,18.2417 13.10531,43.27443 16.2797,63.64715 2.0974,13.46078 2.48648,20.4825 2.53407,45.73343 0.0589,31.24975 -0.58565,38.59247 -5.57665,63.52913 -26.77909,133.797 -136.64661,235.61462 -272.39375,252.4357 -17.24628,2.13706 -47.67501,2.9353 -62.95678,1.65153 z M 422.60679,489.0315 C 535.93722,423.59631 628.78214,369.70078 628.92885,369.26367 629.20612,368.43746 217.06471,130.09256 214.27041,129.46316 c -1.37426,-0.30954 -1.58595,31.67289 -1.58595,239.60604 0,227.99566 0.0965,239.93763 1.9335,239.44933 1.06342,-0.28267 94.65839,-54.05183 207.98883,-119.48703 z"
|
||||
id="path2606"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,11.238281,30.525391)" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 2.7 KiB |
@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="195.32812mm"
|
||||
height="195.32812mm"
|
||||
viewBox="0 0 195.32812 195.32812"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:export-filename="logo_128.png"
|
||||
inkscape:export-xdpi="16.644812"
|
||||
inkscape:export-ydpi="16.644812"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.90509668"
|
||||
inkscape:cx="198.32136"
|
||||
inkscape:cy="397.19514"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:path-effect
|
||||
effect="powerstroke"
|
||||
id="path-effect3140"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
offset_points="3,0"
|
||||
not_jump="false"
|
||||
sort_points="true"
|
||||
interpolator_type="CubicBezierJohan"
|
||||
interpolator_beta="0.2"
|
||||
start_linecap_type="zerowidth"
|
||||
linejoin_type="extrp_arc"
|
||||
miter_limit="4"
|
||||
scale_width="1"
|
||||
end_linecap_type="zerowidth" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-11.238281,-30.525391)">
|
||||
<path
|
||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
||||
d="m 108.90234,30.525391 c -53.858772,0 -97.664059,43.805286 -97.664059,97.664059 0,53.85878 43.805287,97.66407 97.664059,97.66407 53.85878,0 97.66407,-43.80529 97.66407,-97.66407 0,-53.858773 -43.80529,-97.664059 -97.66407,-97.664059 z m 0,13.433593 c 46.59895,0 84.23047,37.631526 84.23047,84.230466 0,46.59895 -37.63152,84.23047 -84.23047,84.23047 -46.59894,0 -84.232418,-37.63152 -84.232418,-84.23047 0,-46.59894 37.633478,-84.230466 84.232418,-84.230466 z"
|
||||
id="path2592" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;-inkscape-stroke:none"
|
||||
d="m 8.9863281,105.29688 v 96.92578 l 83.9394529,-48.46289 -7.699219,-4.44532 z m 10.2656249,17.78125 53.142578,30.68164 -53.142578,30.68164 z"
|
||||
id="path2646"
|
||||
transform="matrix(1.3085265,0,0,1.3085265,56.033246,-73.00885)" />
|
||||
<path
|
||||
style="fill:#ffffff;stroke:#000000;stroke-width:5.46616"
|
||||
d="M 345.13114,686.08528 C 222.64086,675.79533 119.51392,599.94853 74.016249,486.68834 64.419992,462.79978 58.296197,439.49115 53.966274,410.37335 51.203358,391.79333 51.205528,346.25661 53.970219,327.58931 61.4703,276.94849 77.618278,233.84988 103.92601,194.25835 143.90493,134.09253 200.40964,90.698198 268.39759,67.947933 322.37862,49.884696 378.75198,46.533168 433.87378,58.109989 c 82.82951,17.396066 153.43381,65.362801 200.69115,136.344371 10.701,16.07314 25.83502,46.00531 32.67962,64.63398 6.70243,18.2417 13.10531,43.27443 16.2797,63.64715 2.0974,13.46078 2.48648,20.4825 2.53407,45.73343 0.0589,31.24975 -0.58565,38.59247 -5.57665,63.52913 -26.7791,133.797 -136.64661,235.61462 -272.39375,252.43569 -17.24628,2.13707 -47.67501,2.9353 -62.95678,1.65154 z M 422.60679,489.0315 C 535.93722,423.59631 628.78214,369.70078 628.92885,369.26367 629.20612,368.43746 217.06471,130.09256 214.27041,129.46316 c -1.37426,-0.30954 -1.58595,31.67289 -1.58595,239.60604 0,227.99566 0.0965,239.93763 1.9335,239.44933 1.06342,-0.28267 94.65839,-54.05183 207.98883,-119.48703 z"
|
||||
id="path374"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,11.238281,30.525391)" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
42
src/index.ts
@ -1,42 +0,0 @@
|
||||
import {getMatch} from "./match/match";
|
||||
import {storageDelete, storageGet, getSetting} from "./store/store";
|
||||
import {Match, matches} from "./match/matches";
|
||||
|
||||
async function main() {
|
||||
let match: Match;
|
||||
let redirect = false;
|
||||
if ((match = await getMatch(window.location.host)) === undefined) {
|
||||
let id: string
|
||||
if ((id = await storageGet('redirect')) !== undefined) {
|
||||
redirect = true
|
||||
match = matches.find(m => m.id === id)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const re = document.body.innerHTML.match(match.regex)
|
||||
if (re === null) return
|
||||
if (redirect) await storageDelete('redirect')
|
||||
|
||||
const url = await match.match(re)
|
||||
|
||||
if (await getSetting("ff2mpv")) {
|
||||
chrome.runtime.sendMessage({action: "ff2mpv", url: url})
|
||||
}
|
||||
|
||||
if (match.replace && !url.includes('.m3u8')) {
|
||||
const player = document.createElement('video')
|
||||
player.style.width = '100%'
|
||||
player.style.height = '100%'
|
||||
player.controls = true
|
||||
player.src = url
|
||||
|
||||
document.body.innerHTML = ''
|
||||
document.body.append(player)
|
||||
} else {
|
||||
window.location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${window.location.host}`))
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
277
src/lib/match.ts
Normal file
@ -0,0 +1,277 @@
|
||||
import { unpack } from '~/lib/utils';
|
||||
import { Hosters } from '~/lib/settings';
|
||||
|
||||
export enum Reliability {
|
||||
HIGH,
|
||||
NORMAL,
|
||||
LOW
|
||||
}
|
||||
|
||||
export interface Match {
|
||||
name: string;
|
||||
id: string;
|
||||
reliability: Reliability;
|
||||
domains: string[];
|
||||
replace?: boolean;
|
||||
regex: RegExp;
|
||||
notice?: string;
|
||||
|
||||
match(match: RegExpMatchArray): Promise<string>;
|
||||
}
|
||||
|
||||
export const Doodstream: Match = {
|
||||
name: 'Doodstream',
|
||||
id: 'doodstream',
|
||||
reliability: Reliability.NORMAL,
|
||||
domains: [
|
||||
'doodstream.com',
|
||||
'dood.pm',
|
||||
'dood.ws',
|
||||
'dood.wf',
|
||||
'dood.cx',
|
||||
'dood.sh',
|
||||
'dood.watch',
|
||||
'dood.to',
|
||||
'dood.so',
|
||||
'dood.la',
|
||||
'dood.re',
|
||||
'dood.yt',
|
||||
'ds2play.com',
|
||||
'dooood.com'
|
||||
],
|
||||
replace: true,
|
||||
regex: /(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
const response = await fetch(`https://${window.location.host}${match[1]}`, {
|
||||
headers: {
|
||||
Range: 'bytes=0-'
|
||||
},
|
||||
referrer: `https://${window.location.host}/e/${
|
||||
window.location.pathname.split('/').slice(-1)[0]
|
||||
}`
|
||||
});
|
||||
return `${await response.text()}1234567890${match[2]}${Date.now()}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const DropLoad: Match = {
|
||||
name: 'Dropload',
|
||||
id: 'dropload',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['dropload.ui'],
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Filemoon: Match = {
|
||||
name: 'Filemoon',
|
||||
id: 'filemoon',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['filemoon.sx', 'filemoon.in'],
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
const unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const GoodStream: Match = {
|
||||
name: 'Goodstream',
|
||||
id: 'goodstream',
|
||||
reliability: Reliability.NORMAL,
|
||||
domains: ['goodstream.uno'],
|
||||
regex: /(?<=file:\s+").*(?=")/g,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Kwik: Match = {
|
||||
name: 'Kwik',
|
||||
id: 'kwik',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['kwik.cx'],
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=source=').*(?=')/)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Mixdrop: Match = {
|
||||
name: 'Mixdrop',
|
||||
id: 'mixdrop',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['mixdrop.co', 'mixdrop.to', 'mixdrop.ch', 'mixdrop.bz', 'mixdrop.gl'],
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let unpacked = await unpack(match[0]);
|
||||
let url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)[0];
|
||||
return `https:${url}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const Mp4Upload: Match = {
|
||||
name: 'Mp4Upload',
|
||||
id: 'mp4upload',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['mp4upload.com'],
|
||||
replace: true,
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=player.src\(").*(?=")/)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Newgrounds: Match = {
|
||||
name: 'Newgrounds',
|
||||
id: 'newgrounds',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['newgrounds.com'],
|
||||
regex: /.*/gm,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let id = window.location.pathname.split('/').slice(-1)[0];
|
||||
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
});
|
||||
let json = await response.json();
|
||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']);
|
||||
}
|
||||
};
|
||||
|
||||
export const Streamtape: Match = {
|
||||
name: 'Streamtape',
|
||||
id: 'streamtape',
|
||||
reliability: Reliability.NORMAL,
|
||||
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
|
||||
regex: /id=.*(?=')/gm,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
return `https://streamtape.com/get_video?${match.reverse()[0]}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const Streamzz: Match = {
|
||||
name: 'Streamzz',
|
||||
id: 'streamzz',
|
||||
reliability: Reliability.LOW,
|
||||
domains: ['streamzz.to', 'streamz.ws'],
|
||||
regex: /(?<=\|)\w{2,}/gm,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
|
||||
match.sort((a, b) => b.length - a.length)[0]
|
||||
}.dll`;
|
||||
}
|
||||
};
|
||||
|
||||
export const SuperVideo: Match = {
|
||||
name: 'Supervideo',
|
||||
id: 'supervideo',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['supervideo.tv'],
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Upstream: Match = {
|
||||
name: 'Upstream',
|
||||
id: 'upstream',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['upstream.to'],
|
||||
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
let unpacked = await unpack(match[0]);
|
||||
return unpacked.match(/(?<=file:").*(?=")/)[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Vidoza: Match = {
|
||||
name: 'Vidoza',
|
||||
id: 'vidoza',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['vidoza.net'],
|
||||
regex: /(?<=src:\s?").+?(?=")/gm,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Voe: Match = {
|
||||
name: 'Voe',
|
||||
id: 'voe',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['voe.sx'],
|
||||
regex: /https?:\/\/\S*m3u8.+(?=')/gm,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const Vupload: Match = {
|
||||
name: 'Vupload',
|
||||
id: 'vupload',
|
||||
reliability: Reliability.HIGH,
|
||||
domains: ['vupload.com'],
|
||||
regex: /(?<=src:\s?").+?(?=")/gm,
|
||||
|
||||
match: async (match: RegExpMatchArray) => {
|
||||
return match[0];
|
||||
}
|
||||
};
|
||||
|
||||
export const matches = {
|
||||
[Doodstream.id]: Doodstream,
|
||||
[DropLoad.id]: DropLoad,
|
||||
[Filemoon.id]: Filemoon,
|
||||
[GoodStream.id]: GoodStream,
|
||||
[Kwik.id]: Kwik,
|
||||
[Mixdrop.id]: Mixdrop,
|
||||
[Mp4Upload.id]: Mp4Upload,
|
||||
[Newgrounds.id]: Newgrounds,
|
||||
[Streamtape.id]: Streamtape,
|
||||
[Streamzz.id]: Streamzz,
|
||||
[SuperVideo.id]: SuperVideo,
|
||||
[Upstream.id]: Upstream,
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
73
src/lib/settings.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import type { Match } from '~/lib/match';
|
||||
import { matches } from '~/lib/match';
|
||||
|
||||
export const Hosters = {
|
||||
getDisabled: async () => {
|
||||
const disabled = await storageGet<string[]>('hosters.disabled', []);
|
||||
return disabled.map((id) => matches[id]).filter((m) => m !== undefined);
|
||||
},
|
||||
disable: async (match: Match) => {
|
||||
const disabled = await storageGet('hosters.disabled', []);
|
||||
const index = disabled.indexOf(match.id);
|
||||
if (index === -1) {
|
||||
disabled.push(match.id);
|
||||
await storageSet('hosters.disabled', disabled);
|
||||
}
|
||||
},
|
||||
enable: async (match: Match) => {
|
||||
let disabled = await storageGet('hosters.disabled', []);
|
||||
const index = disabled.indexOf(match.id);
|
||||
if (index !== -1) {
|
||||
disabled.splice(index, 1);
|
||||
await storageSet('hosters.disabled', disabled);
|
||||
}
|
||||
},
|
||||
getAllDisabled: async () => {
|
||||
return await storageGet<boolean>('hosters.allDisabled', false);
|
||||
},
|
||||
disableAll: async () => {
|
||||
await storageSet('hosters.allDisabled', true);
|
||||
},
|
||||
enableAll: async () => {
|
||||
await storageSet('hosters.allDisabled', false);
|
||||
}
|
||||
};
|
||||
|
||||
export const Redirect = {
|
||||
get: async (): Promise<Match | null> => {
|
||||
return matches[await storageGet<string>('redirect')] || null;
|
||||
},
|
||||
set: async (match: Match) => {
|
||||
await storageSet('redirect', match.id);
|
||||
},
|
||||
delete: async () => {
|
||||
await storageDelete('redirect');
|
||||
}
|
||||
};
|
||||
|
||||
export const Other = {
|
||||
getFf2mpv: async () => {
|
||||
return await storageGet('other.ff2mpv', true);
|
||||
},
|
||||
setFf2mpv: async (enable: boolean) => {
|
||||
await storageSet('other.ff2mpv', enable);
|
||||
}
|
||||
};
|
||||
|
||||
export async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
|
||||
const entry = await browser.storage.local.get(key);
|
||||
const value = entry[key];
|
||||
return value === undefined ? defaultValue : value;
|
||||
}
|
||||
|
||||
export async function storageSet<T>(key: string, value: T) {
|
||||
const obj = {
|
||||
[key]: value
|
||||
};
|
||||
await browser.storage.local.set(obj);
|
||||
}
|
||||
|
||||
export async function storageDelete(key: string) {
|
||||
await browser.storage.local.remove(key);
|
||||
}
|
94
src/lib/utils.ts
Normal file
@ -0,0 +1,94 @@
|
||||
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
|
||||
export async function unpack(packed: string): Promise<string> {
|
||||
let context = `
|
||||
{
|
||||
eval: function (c) {
|
||||
packed = c;
|
||||
},
|
||||
window: {},
|
||||
document: {}
|
||||
}
|
||||
`;
|
||||
const toExecute = `
|
||||
function() {
|
||||
let packed = "";
|
||||
with(${context}) {
|
||||
${packed}
|
||||
};
|
||||
return packed;
|
||||
}'
|
||||
`;
|
||||
|
||||
const res: string = await runInPageContext(toExecute);
|
||||
return res
|
||||
.replace(/;/g, ';\n')
|
||||
.replace(/{/g, '\n{\n')
|
||||
.replace(/}/g, '\n}\n')
|
||||
.replace(/\n;\n/g, ';\n')
|
||||
.replace(/\n\\n/g, '\n');
|
||||
}
|
||||
|
||||
// Adapted from: https://github.com/arikw/extension-page-context
|
||||
async function runInPageContext<T>(toExecute: string): Promise<T> {
|
||||
// test that we are running with the allow-scripts permission
|
||||
try {
|
||||
window.sessionStorage;
|
||||
} catch (ignore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// returned value container
|
||||
const resultMessageId = crypto.randomUUID();
|
||||
|
||||
// prepare script container
|
||||
const scriptElm = document.createElement('script');
|
||||
scriptElm.setAttribute('type', 'application/javascript');
|
||||
|
||||
const code = `
|
||||
(
|
||||
async function () {
|
||||
|
||||
const response = {
|
||||
id: ${resultMessageId}
|
||||
};
|
||||
|
||||
try {
|
||||
response.result = JSON.stringify(await (${toExecute})() ); // run script
|
||||
} catch(err) {
|
||||
response.error = JSON.stringify(err);
|
||||
}
|
||||
|
||||
window.postMessage(response, '*');
|
||||
}
|
||||
)();
|
||||
`;
|
||||
|
||||
// inject the script
|
||||
scriptElm.textContent = code;
|
||||
|
||||
// run the script
|
||||
document.documentElement.appendChild(scriptElm);
|
||||
|
||||
// clean up script element
|
||||
scriptElm.remove();
|
||||
|
||||
let resolve: (value: T) => void;
|
||||
let reject: (value: any) => void;
|
||||
const promise = new Promise<T>((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
function onResult(event: MessageEvent) {
|
||||
if (event.data.id === resultMessageId) {
|
||||
window.removeEventListener('message', onResult);
|
||||
if (event.data.error !== undefined) {
|
||||
return reject(JSON.parse(event.data.error));
|
||||
}
|
||||
return resolve(event.data.result !== undefined ? JSON.parse(event.data.result) : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', onResult);
|
||||
|
||||
return await promise;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Stream Bypass",
|
||||
"author": "ByteDream",
|
||||
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
||||
"version": "2.2.0",
|
||||
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "{55dd42e8-3dd9-455a-b4fe-86664881b10c}"
|
||||
}
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"all_frames": true,
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"index.js"
|
||||
],
|
||||
"run_at": "document_end"
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"scripts": [
|
||||
"background.js"
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"webRequest",
|
||||
"nativeMessaging",
|
||||
"<all_urls>"
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
"ui/player/*"
|
||||
],
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"48": "icons/logo_48.png",
|
||||
"128": "icons/logo_128.png"
|
||||
},
|
||||
"default_title": "Stream Bypass",
|
||||
"default_popup": "ui/popup/popup.html"
|
||||
},
|
||||
"icons": {
|
||||
"48": "icons/logo_48.png",
|
||||
"128": "icons/logo_128.png"
|
||||
}
|
||||
}
|
80
src/manifest.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import pkg from '../package.json';
|
||||
|
||||
const sharedManifest: Partial<chrome.runtime.ManifestBase> = {
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: '{55dd42e8-3dd9-455a-b4fe-86664881b10c}'
|
||||
}
|
||||
},
|
||||
content_scripts: [
|
||||
{
|
||||
all_frames: true,
|
||||
matches: ['<all_urls>'],
|
||||
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', 'webRequest', 'nativeMessaging', '<all_urls>']
|
||||
};
|
||||
|
||||
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/main.ts'],
|
||||
persistent: true
|
||||
},
|
||||
browser_action: browserAction,
|
||||
permissions: [...sharedManifest.permissions]
|
||||
};
|
||||
|
||||
const ManifestV3 = {
|
||||
...sharedManifest,
|
||||
action: browserAction,
|
||||
background: {
|
||||
service_worker: 'src/entries/background/main.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
|
||||
};
|
||||
}
|
||||
|
||||
if (manifestVersion === 3) {
|
||||
return {
|
||||
...manifest,
|
||||
...ManifestV3,
|
||||
manifest_version: manifestVersion
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Missing manifest definition for manifestVersion ${manifestVersion}`);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import {Match, matches} from "./matches";
|
||||
import {getAllDisabled, getDisabled} from "../store/store";
|
||||
|
||||
export async function getMatch(host: string): Promise<Match | undefined> {
|
||||
if (await getAllDisabled()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const match of matches) {
|
||||
if (match.domains.some(v => host.indexOf(v) !== -1) && !((await getDisabled()).some(v => v === match))) {
|
||||
return match
|
||||
}
|
||||
}
|
||||
}
|
@ -1,299 +0,0 @@
|
||||
import {unPack} from "./unpack";
|
||||
|
||||
export enum Reliability {
|
||||
HIGH,
|
||||
NORMAL,
|
||||
LOW,
|
||||
}
|
||||
|
||||
export abstract class Match {
|
||||
name: string
|
||||
id: string
|
||||
reliability: Reliability
|
||||
domains: string[]
|
||||
replace?: boolean
|
||||
regex: RegExp
|
||||
abstract match(match: RegExpMatchArray): Promise<string>
|
||||
|
||||
notice?: string
|
||||
}
|
||||
|
||||
class Doodstream implements Match {
|
||||
name = 'Doodstream'
|
||||
id = 'doodstream'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'doodstream.com',
|
||||
'dood.pm',
|
||||
'dood.ws',
|
||||
'dood.wf',
|
||||
'dood.cx',
|
||||
'dood.sh',
|
||||
'dood.watch',
|
||||
'dood.to',
|
||||
'dood.so',
|
||||
'dood.la',
|
||||
'dood.re',
|
||||
'dood.yt',
|
||||
'ds2play.com',
|
||||
'dooood.com'
|
||||
]
|
||||
replace = true
|
||||
regex = new RegExp(/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const response = await fetch(`https://${window.location.host}${match[1]}`, {
|
||||
headers: {
|
||||
'Range': 'bytes=0-'
|
||||
},
|
||||
referrer: `https://${window.location.host}/e/${window.location.pathname.split('/').slice(-1)[0]}`,
|
||||
});
|
||||
|
||||
return `${await response.text()}1234567890${match[2]}${Date.now()}`
|
||||
}
|
||||
}
|
||||
|
||||
class Filemoon implements Match {
|
||||
name = 'Filemoon'
|
||||
id = 'filemoon'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'filemoon.sx',
|
||||
'filemoon.in'
|
||||
]
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
class Mixdrop implements Match {
|
||||
name = 'Mixdrop'
|
||||
id = 'mixdrop'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'mixdrop.co',
|
||||
'mixdrop.to',
|
||||
'mixdrop.ch',
|
||||
'mixdrop.bz',
|
||||
'mixdrop.gl'
|
||||
]
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)[0]
|
||||
return `https:${url}`
|
||||
}
|
||||
}
|
||||
|
||||
class Mp4Upload implements Match {
|
||||
name = 'Mp4Upload'
|
||||
id = 'mp4upload'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'mp4upload.com'
|
||||
]
|
||||
replace = true
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=player.src\(").*(?=")/)[0]
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
class Newgrounds implements Match {
|
||||
name = 'Newgrounds'
|
||||
id = 'newgrounds'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'newgrounds.com'
|
||||
]
|
||||
regex = new RegExp(/.*/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let id = window.location.pathname.split('/').slice(-1)[0]
|
||||
let response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
let json = await response.json()
|
||||
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
|
||||
}
|
||||
}
|
||||
|
||||
class Streamtape implements Match {
|
||||
name = 'Streamtape'
|
||||
id = 'streamtape'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'streamtape.com',
|
||||
'streamtape.net',
|
||||
'shavetape.cash'
|
||||
]
|
||||
regex = new RegExp(/id=.*(?=')/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://streamtape.com/get_video?${match.reverse()[0]}`
|
||||
}
|
||||
}
|
||||
|
||||
class Streamzz implements Match {
|
||||
name = 'Streamzz'
|
||||
id = 'streamzz'
|
||||
reliability = Reliability.LOW
|
||||
domains = [
|
||||
'streamzz.to',
|
||||
'streamz.ws'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://get.${document.domain.split('.')[0]}.tw/getlink-${match.sort((a, b) => b.length - a.length)[0]}.dll`
|
||||
}
|
||||
}
|
||||
|
||||
class Upstream implements Match {
|
||||
name = 'Upstream'
|
||||
id = 'upstream'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'upstream.to'
|
||||
]
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
class Vidoza implements Match {
|
||||
name = 'Vidoza'
|
||||
id = 'vidoza'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'vidoza.net'
|
||||
]
|
||||
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Voe implements Match {
|
||||
name = 'Voe'
|
||||
id = 'voe'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'voe.sx'
|
||||
]
|
||||
regex = new RegExp(/https?:\/\/\S*m3u8.+(?=')/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Vupload implements Match {
|
||||
name = 'Vupload'
|
||||
id = 'vupload'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'vupload.com'
|
||||
]
|
||||
regex = new RegExp(/(?<=src:\s?").+?(?=")/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
class Kwik implements Match {
|
||||
name = 'Kwik'
|
||||
id = 'kwik'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'kwik.cx'
|
||||
]
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
console.log(match[0]);
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=source=').*(?=')/)[0]
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
class DropLoad implements Match {
|
||||
name = 'Dropload'
|
||||
id = 'dropload'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'dropload.io'
|
||||
]
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
class SuperVideo implements Match {
|
||||
name = 'Supervideo'
|
||||
id = 'supervideo'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'supervideo.tv'
|
||||
]
|
||||
regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
let unpacked = await unPack(match[0])
|
||||
let url = unpacked.match(/(?<=file:").*(?=")/)[0]
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
class GoodStream implements Match {
|
||||
name = 'Goodstream'
|
||||
id = 'goodstream'
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'goodstream.uno'
|
||||
]
|
||||
regex = new RegExp(/(?<=file:\s+").*(?=")/g)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return match[0]
|
||||
}
|
||||
}
|
||||
|
||||
export const matches = [
|
||||
new Doodstream(),
|
||||
new Filemoon(),
|
||||
new Mixdrop(),
|
||||
new Mp4Upload(),
|
||||
new Newgrounds(),
|
||||
new Streamtape(),
|
||||
new Streamzz(),
|
||||
new Upstream(),
|
||||
new Vidoza(),
|
||||
new Voe(),
|
||||
new Vupload(),
|
||||
new Kwik(),
|
||||
new DropLoad(),
|
||||
new SuperVideo(),
|
||||
new GoodStream()
|
||||
]
|
@ -1,84 +0,0 @@
|
||||
function runInPageContext(to_execute: string) {
|
||||
// Adapted from: https://github.com/arikw/extension-page-context
|
||||
const doc = document;
|
||||
|
||||
// test that we are running with the allow-scripts permission
|
||||
try { window.sessionStorage; } catch (ignore) { return null; }
|
||||
|
||||
// returned value container
|
||||
const resultMessageId = parseInt('' + Math.floor((Math.random() * 100) + 1) + ((new Date()).getTime()));
|
||||
|
||||
// prepare script container
|
||||
let scriptElm = doc.createElement('script');
|
||||
scriptElm.setAttribute("type", "application/javascript");
|
||||
|
||||
const code = `
|
||||
(
|
||||
async function () {
|
||||
|
||||
const response = {
|
||||
id: ${resultMessageId}
|
||||
};
|
||||
|
||||
try {
|
||||
response.result = JSON.stringify(await (${to_execute})() ); // run script
|
||||
} catch(err) {
|
||||
response.error = JSON.stringify(err);
|
||||
}
|
||||
|
||||
window.postMessage(response, '*');
|
||||
}
|
||||
)();
|
||||
`;
|
||||
|
||||
// inject the script
|
||||
scriptElm.textContent = code;
|
||||
|
||||
// run the script
|
||||
doc.documentElement.appendChild(scriptElm);
|
||||
|
||||
// clean up script element
|
||||
scriptElm.remove();
|
||||
|
||||
// create a "flat" promise
|
||||
let resolve, reject;
|
||||
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
||||
|
||||
|
||||
// resolve on result
|
||||
function onResult(event) {
|
||||
const data = Object(event.data);
|
||||
if (data.id === resultMessageId) {
|
||||
window.removeEventListener('message', onResult);
|
||||
if (data.error !== undefined) {
|
||||
return reject(JSON.parse(data.error));
|
||||
}
|
||||
return resolve((data.result !== undefined) ? JSON.parse(data.result) : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', onResult);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
export const unPack = async (packed: String): Promise<string> => {
|
||||
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
|
||||
|
||||
let context = `
|
||||
{
|
||||
eval: function (c) {
|
||||
packed = c;
|
||||
},
|
||||
window: {},
|
||||
document: {}
|
||||
}
|
||||
`
|
||||
|
||||
const to_execute = `function() { let packed = ""; with(${context}) { ` + packed + '}; return packed; }'
|
||||
|
||||
const res = await runInPageContext(to_execute);
|
||||
|
||||
|
||||
return (res+"").replace(/;/g, ";\n").replace(/{/g, "\n{\n").replace(/}/g, "\n}\n").replace(/\n;\n/g, ";\n").replace(/\n\\n/g, "\n");
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import { storageSet, storageGet } from "./store"
|
||||
|
||||
export class Setting {
|
||||
name: string
|
||||
info_url?: string
|
||||
|
||||
constructor(name: string, info_url?: string) {
|
||||
this.name = name
|
||||
this.info_url = info_url
|
||||
}
|
||||
|
||||
async enable() {
|
||||
await storageSet(this.name, true)
|
||||
}
|
||||
|
||||
async disable() {
|
||||
await storageSet(this.name, false)
|
||||
}
|
||||
|
||||
async get_status() {
|
||||
return await storageGet(this.name)
|
||||
}
|
||||
}
|
||||
|
||||
export const Settings = [
|
||||
new Setting("ff2mpv", "https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams")
|
||||
]
|
@ -1,70 +0,0 @@
|
||||
import {Match, matches} from "../match/matches";
|
||||
|
||||
export async function storageGet(key: string): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.local.get(key, (value) => {
|
||||
resolve(value[key])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function storageSet(key: string, value: any) {
|
||||
const obj = {}
|
||||
obj[key] = value
|
||||
await chrome.storage.local.set(obj)
|
||||
}
|
||||
|
||||
export async function storageDelete(key: string) {
|
||||
await chrome.storage.local.remove(key)
|
||||
}
|
||||
|
||||
export async function getDisabled(): Promise<Match[]> {
|
||||
const localMatches = []
|
||||
|
||||
for (const disabled of (await storageGet('disabled') as string[]) || []) {
|
||||
let m: Match
|
||||
if ((m = matches.find((v) => v.id === disabled)) !== undefined) {
|
||||
localMatches.push(m)
|
||||
}
|
||||
}
|
||||
|
||||
return localMatches
|
||||
}
|
||||
|
||||
export async function getAllDisabled(): Promise<boolean> {
|
||||
const value = await storageGet('all')
|
||||
return value !== undefined ? String(value).toLowerCase() === 'true' : false
|
||||
}
|
||||
|
||||
export async function enableAll() {
|
||||
await storageSet('all', false)
|
||||
await chrome.browserAction.setIcon({
|
||||
path: null
|
||||
})
|
||||
}
|
||||
|
||||
export async function disableAll() {
|
||||
await storageSet('all', true)
|
||||
await chrome.browserAction.setIcon({
|
||||
path: {
|
||||
48: chrome.runtime.getURL('icons/disabled_48.png'),
|
||||
128: chrome.runtime.getURL('icons/disabled_128.png')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function enable(match: Match) {
|
||||
const disabled = await storageGet('disabled') || []
|
||||
disabled.splice(disabled.indexOf(match.id))
|
||||
await storageSet('disabled', disabled)
|
||||
}
|
||||
|
||||
export async function disable(match: Match) {
|
||||
const disabled = await storageGet('disabled') as string[] || []
|
||||
if (disabled.indexOf(match.id) !== undefined) {
|
||||
disabled.push(match.id)
|
||||
await storageSet('disabled', disabled)
|
||||
}
|
||||
}
|
||||
|
||||
export const getSetting = storageGet
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "es2019",
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
"scripthost",
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
],
|
||||
},
|
||||
"exclude": [
|
||||
"../node_modules"
|
||||
]
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Stream Bypass</title>
|
||||
<link rel="stylesheet" href="player.css">
|
||||
<script src="player.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<div id="message-container">
|
||||
<p id="message"></p>
|
||||
<br>
|
||||
<p>Open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,34 +0,0 @@
|
||||
body {
|
||||
background-color: #131313;
|
||||
}
|
||||
|
||||
html, body, video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#message-container {
|
||||
visibility: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
|
||||
& * {
|
||||
visibility: inherit;
|
||||
}
|
||||
|
||||
& a, & a:visited {
|
||||
color: red
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import {matches} from "../../match/matches";
|
||||
// @ts-ignore
|
||||
import Hls from "hls.js";
|
||||
|
||||
function show_message(message: string) {
|
||||
document.getElementById('message').innerText = message
|
||||
document.getElementById('message-container').style.visibility = 'visible'
|
||||
document.getElementById('video').hidden = true
|
||||
}
|
||||
|
||||
async function play_native(url: string) {
|
||||
const video = document.getElementById('video') as HTMLVideoElement
|
||||
video.controls = true
|
||||
video.src = url
|
||||
}
|
||||
|
||||
async function play_hls(url: string) {
|
||||
const video = document.getElementById('video') as HTMLVideoElement
|
||||
video.controls = true
|
||||
|
||||
if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url
|
||||
} else if (Hls.isSupported()) {
|
||||
const hls = new Hls({
|
||||
enableWorker: false
|
||||
})
|
||||
hls.loadSource(url)
|
||||
hls.attachMedia(video)
|
||||
} else {
|
||||
show_message('Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>')
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const urlQuery = new URLSearchParams(window.location.search)
|
||||
const id = urlQuery.get('id')
|
||||
const url = decodeURIComponent(urlQuery.get('url'))
|
||||
const domain = urlQuery.get('domain')
|
||||
|
||||
const match = matches.find((m) => m.id === id)
|
||||
if (match === undefined) {
|
||||
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`)
|
||||
return
|
||||
}
|
||||
document.title = `Stream Bypass (${domain})`
|
||||
|
||||
new URL(url).pathname.endsWith('.m3u8') ? await play_hls(url) : await play_native(url)
|
||||
}
|
||||
|
||||
main()
|
@ -1,31 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Stream-Bypass</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
<script src="popup.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="all">
|
||||
<div class="buttons">
|
||||
<a>On</a>
|
||||
<a>Off</a>
|
||||
</div>
|
||||
<p>Hosters</p>
|
||||
<table id="sub-container">
|
||||
</table>
|
||||
<hr class="upper-hr">
|
||||
<p>Settings</p>
|
||||
<table id="settings-container">
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<table id="sub-container">
|
||||
</table>
|
||||
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues">Something does not work</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,76 +0,0 @@
|
||||
body {
|
||||
background-color: #2b2a33;
|
||||
font-weight: bold;
|
||||
max-height: 500px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
a, p {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin: 5px 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 1px solid #281515;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
padding: 5px 8px;
|
||||
|
||||
&.active {
|
||||
background-color: rgba(255, 65, 65, 0.74);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: grey;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&#error {
|
||||
border: none;
|
||||
display: block;
|
||||
font-weight: lighter;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
padding: 10px 0 5px 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
hr {
|
||||
margin: 3px 0;
|
||||
|
||||
&.upper-hr {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin: 10px 0
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.low-reliability {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgb(255, 0, 0);
|
||||
}
|
||||
|
||||
.normal-reliability {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: yellow;
|
||||
}
|
||||
|
||||
.high-reliability {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #00ff00;
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
import {getDisabled, disable, enable, getAllDisabled, enableAll, disableAll} from "../../store/store";
|
||||
import {matches, Reliability} from "../../match/matches";
|
||||
import { Settings, Setting } from "../../store/settings";
|
||||
|
||||
async function main() {
|
||||
const disabled = await getDisabled()
|
||||
|
||||
const subContainer = document.getElementById('sub-container')
|
||||
for (const m of matches) {
|
||||
const row = document.createElement('tr')
|
||||
|
||||
const name = document.createElement('td')
|
||||
const nameValue = document.createElement('p')
|
||||
nameValue.innerText = m.name
|
||||
switch (m.reliability) {
|
||||
case Reliability.LOW:
|
||||
nameValue.classList.add('low-reliability')
|
||||
break
|
||||
case Reliability.NORMAL:
|
||||
nameValue.classList.add('normal-reliability')
|
||||
break
|
||||
case Reliability.HIGH:
|
||||
nameValue.classList.add('high-reliability')
|
||||
break
|
||||
}
|
||||
|
||||
const buttons = document.createElement('td')
|
||||
buttons.classList.add('buttons')
|
||||
const on = document.createElement('a')
|
||||
on.innerText = 'On'
|
||||
const off = document.createElement('a')
|
||||
off.innerText = 'Off'
|
||||
disabled.find((v) => v.id === m.id) === undefined ? on.classList.add('active') : off.classList.add('active')
|
||||
|
||||
on.onclick = async function () {
|
||||
if (!on.classList.contains('disabled')) {
|
||||
await enable(m)
|
||||
on.classList.add('active')
|
||||
off.classList.remove('active')
|
||||
}
|
||||
}
|
||||
off.onclick = async function () {
|
||||
if (!off.classList.contains('disabled')) {
|
||||
await disable(m)
|
||||
on.classList.remove('active')
|
||||
off.classList.add('active')
|
||||
}
|
||||
}
|
||||
|
||||
name.append(nameValue)
|
||||
buttons.append(on, off)
|
||||
row.append(name, buttons)
|
||||
subContainer.append(row)
|
||||
}
|
||||
|
||||
const settingsContainer = document.getElementById("settings-container")
|
||||
for (const s of Settings) {
|
||||
const row = document.createElement('tr')
|
||||
|
||||
const name = document.createElement('td')
|
||||
const nameValue = document.createElement('p')
|
||||
nameValue.innerText = s.name
|
||||
|
||||
const buttons = document.createElement('td')
|
||||
buttons.classList.add('buttons')
|
||||
const on = document.createElement('a')
|
||||
on.innerText = 'On'
|
||||
const off = document.createElement('a')
|
||||
off.innerText = 'Off'
|
||||
const info = document.createElement('a')
|
||||
info.target = "_blank"
|
||||
info.href = s.info_url
|
||||
info.innerText = "🛈"
|
||||
|
||||
await s.get_status() ? on.classList.add('active') : off.classList.add('active')
|
||||
|
||||
on.onclick = async function () {
|
||||
if (!on.classList.contains('disabled')) {
|
||||
await s.enable()
|
||||
on.classList.add('active')
|
||||
off.classList.remove('active')
|
||||
}
|
||||
}
|
||||
off.onclick = async function () {
|
||||
if (!off.classList.contains('disabled')) {
|
||||
await s.disable()
|
||||
on.classList.remove('active')
|
||||
off.classList.add('active')
|
||||
}
|
||||
}
|
||||
|
||||
name.append(nameValue)
|
||||
buttons.append(on, off)
|
||||
if (s.info_url) {
|
||||
buttons.append(info)
|
||||
}
|
||||
row.append(name, buttons)
|
||||
settingsContainer.append(row)
|
||||
}
|
||||
|
||||
const allOnButton = document.getElementById('all').getElementsByTagName('a')[0]
|
||||
const allOffButton = document.getElementById('all').getElementsByTagName('a')[1]
|
||||
|
||||
if (await getAllDisabled()) {
|
||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
||||
for (let i = 0; i < allBtns.length; i++) {
|
||||
allBtns[i].classList.add('disabled')
|
||||
}
|
||||
allOffButton.classList.add('active')
|
||||
} else {
|
||||
allOnButton.classList.add('active')
|
||||
}
|
||||
|
||||
allOnButton.onclick = async () => {
|
||||
if (!allOnButton.classList.contains('active')) {
|
||||
allOnButton.classList.add('active')
|
||||
allOffButton.classList.remove('active')
|
||||
|
||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
||||
for (let i = 0; i < allBtns.length; i++) {
|
||||
allBtns[i].classList.remove('disabled')
|
||||
}
|
||||
|
||||
await enableAll()
|
||||
}
|
||||
}
|
||||
allOffButton.onclick = async () => {
|
||||
if (!allOffButton.classList.contains('active')) {
|
||||
allOffButton.classList.add('active')
|
||||
allOnButton.classList.remove('active')
|
||||
|
||||
const allBtns = document.getElementById('sub-container').getElementsByTagName('a')
|
||||
for (let i = 0; i < allBtns.length; i++) {
|
||||
allBtns[i].classList.add('disabled')
|
||||
}
|
||||
|
||||
await disableAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
3
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="@samrum/vite-plugin-web-extension/client" />
|
7
svelte.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
|
||||
export default {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
103
tasks/build.ts
@ -1,103 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const rollup = require('rollup')
|
||||
const rollupPluginCommonJs = require('@rollup/plugin-commonjs')
|
||||
const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default
|
||||
const rollupPluginReplace = require('@rollup/plugin-replace')
|
||||
const rollupPluginTypescript = require('@rollup/plugin-typescript')
|
||||
const sass = require('sass')
|
||||
const typescript = require('typescript')
|
||||
|
||||
async function buildManifest() {
|
||||
const manifest = JSON.parse(fs.readFileSync('src/manifest.json'))
|
||||
|
||||
manifest['version'] = process.env.npm_package_version
|
||||
|
||||
fs.writeFileSync('src/manifest.json', JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
async function buildMisc() {
|
||||
const files = {
|
||||
'src/manifest.json': 'build/manifest.json',
|
||||
|
||||
'src/icons/logo_48.png': 'build/icons/logo_48.png',
|
||||
'src/icons/logo_128.png': 'build/icons/logo_128.png',
|
||||
'src/icons/disabled_48.png': 'build/icons/disabled_48.png',
|
||||
'src/icons/disabled_128.png': 'build/icons/disabled_128.png',
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
||||
fs.copyFileSync(src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
async function buildHtml() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.html': 'build/ui/popup/popup.html',
|
||||
'src/ui/player/player.html': 'build/ui/player/player.html'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
||||
fs.copyFileSync(src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
async function buildCss() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.scss': 'build/ui/popup/popup.css',
|
||||
'src/ui/player/player.scss': 'build/ui/player/player.css'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
const compiled = await sass.compileAsync(src)
|
||||
fs.mkdirSync(path.dirname(dst), {recursive: true})
|
||||
fs.writeFileSync(dst, compiled.css)
|
||||
}
|
||||
}
|
||||
|
||||
async function buildJs() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.ts': 'build/ui/popup/popup.js',
|
||||
'src/ui/player/player.ts': 'build/ui/player/player.js',
|
||||
|
||||
'src/index.ts': 'build/index.js',
|
||||
'src/background.ts': 'build/background.js'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
const bundle = await rollup.rollup({
|
||||
input: src,
|
||||
plugins: [
|
||||
rollupPluginNodeResolve({
|
||||
browser: true
|
||||
}),
|
||||
rollupPluginReplace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
}),
|
||||
rollupPluginTypescript({
|
||||
typescript,
|
||||
tsconfig: 'src/tsconfig.json'
|
||||
}),
|
||||
rollupPluginCommonJs({
|
||||
extensions: ['.js', '.ts']
|
||||
})
|
||||
]
|
||||
})
|
||||
await bundle.write({
|
||||
file: dst,
|
||||
strict: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function build() {
|
||||
await buildManifest()
|
||||
await buildMisc()
|
||||
await buildHtml()
|
||||
await buildCss()
|
||||
await buildJs()
|
||||
}
|
||||
|
||||
build()
|
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2019",
|
||||
"module": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"resolveJsonModule": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["src/*"]
|
||||
},
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
35
vite.config.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import webExtension from '@samrum/vite-plugin-web-extension';
|
||||
import path from 'path';
|
||||
import { getManifest } from './src/manifest';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
svelte(),
|
||||
webExtension({
|
||||
manifest: getManifest(Number(env.MANIFEST_VERSION)),
|
||||
additionalInputs: {
|
||||
html: [
|
||||
{
|
||||
fileName: 'src/entries/player/player.html',
|
||||
webAccessible: {
|
||||
matches: ['<all_urls>'],
|
||||
excludeEntryFile: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': path.resolve(__dirname, './src')
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|