This commit is contained in:
bytedream 2023-11-11 21:07:44 +01:00
parent 0054f5da0e
commit 6e7410b088
60 changed files with 8809 additions and 1314 deletions

1
.env Normal file
View File

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

9
.eslintignore Normal file
View File

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

33
.eslintrc.cjs Normal file
View 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
View File

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

2
.npmrc Normal file
View File

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

8
.prettierignore Normal file
View File

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

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:#fff;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="738.248" height="738.248" viewBox="0 0 195.328 195.328"><path d="M108.902 30.525c-53.858 0-97.664 43.806-97.664 97.664 0 53.86 43.806 97.665 97.664 97.665 53.86 0 97.664-43.806 97.664-97.665 0-53.858-43.805-97.664-97.664-97.664zm0 13.434c46.6 0 84.23 37.632 84.23 84.23 0 46.6-37.63 84.23-84.23 84.23-46.599 0-84.232-37.63-84.232-84.23 0-46.598 37.633-84.23 84.232-84.23z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="translate(-11.238 -30.525)"/><path d="M8.986 105.297v96.926l83.94-48.463-7.7-4.446zm10.266 17.781 53.143 30.682-53.143 30.681z" style="color:#000;fill:#000;-inkscape-stroke:none" transform="matrix(1.30853 0 0 1.30853 44.795 -103.534)"/><path d="M345.131 686.085c-122.49-10.29-225.617-86.136-271.115-199.397-9.596-23.888-15.72-47.197-20.05-76.315-2.763-18.58-2.76-64.116.004-82.784 7.5-50.64 23.648-93.74 49.956-133.33 39.979-60.166 96.484-103.56 164.472-126.311 53.98-18.063 110.354-21.415 165.476-9.838 82.83 17.396 153.434 65.363 200.69 136.344 10.702 16.073 25.836 46.006 32.68 64.634 6.703 18.242 13.106 43.275 16.28 63.647 2.098 13.461 2.487 20.483 2.534 45.734.06 31.25-.585 38.592-5.576 63.53-26.78 133.796-136.647 235.614-272.394 252.435-17.246 2.137-47.675 2.935-62.957 1.651zm77.476-197.054c113.33-65.435 206.175-119.33 206.322-119.767.277-.827-411.864-239.171-414.659-239.8-1.374-.31-1.586 31.672-1.586 239.605 0 227.996.097 239.938 1.934 239.45 1.063-.283 94.658-54.052 207.989-119.487z" style="fill:gray;stroke:#000;stroke-width:5.46616" transform="scale(.26458)"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -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']
})

View 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'] }
);

View 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();

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

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

View 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);
}
}

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

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

View File

@ -0,0 +1,5 @@
import App from './Popup.svelte';
new App({
target: document.getElementById('app')
});

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -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
View 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
View 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
View 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;
}

View File

@ -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
View 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}`);
}

View File

@ -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
}
}
}

View File

@ -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()
]

View File

@ -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");
}

View File

@ -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")
]

View File

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

View File

@ -1,18 +0,0 @@
{
"compilerOptions": {
"module": "esnext",
"target": "es2019",
"removeComments": true,
"esModuleInterop": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015.collection",
"es2015.promise"
],
},
"exclude": [
"../node_modules"
]
}

View File

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

View File

@ -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
}
}

View File

@ -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()

View File

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

View File

@ -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;
}

View File

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

@ -0,0 +1,3 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
/// <reference types="@samrum/vite-plugin-web-extension/client" />

7
svelte.config.js Normal file
View File

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

View File

@ -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
View 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
View 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')
}
}
};
});