Compare commits

...

44 Commits
v3.1.0 ... main

Author SHA1 Message Date
607326e6d6 Update dependencies and version 2025-05-08 02:02:57 +02:00
47f6de2de8 Fix voe 2025-05-08 01:59:29 +02:00
d159fb0aa4 Add mixdrop domain 2025-05-06 22:21:09 +02:00
290733a85a Add supervideo domain 2025-05-06 22:20:04 +02:00
84eb32ab61 Fix dropload domain 2025-05-06 22:18:19 +02:00
927a4e3ae3 Update dependencies and version 2025-04-17 02:43:13 +02:00
5926177920 Update voe.sx match regex 2025-04-17 02:37:32 +02:00
9a29316641 Fix voe.sx 2025-04-17 02:26:59 +02:00
c300125f64 Set javascript target to es2021 2025-04-17 02:26:41 +02:00
59f2ffec57 Allow arbitrary properties in Match interface 2025-04-17 02:10:18 +02:00
6dff691c25 Use other notation for match function 2025-04-17 02:04:45 +02:00
c4085f9ac8 Update version 2025-04-04 02:13:20 +02:00
422702b16f Add import order prettier plugin 2025-04-04 02:10:51 +02:00
c3dad3681c Fix player media type url query 2025-04-04 02:05:04 +02:00
6eb3259ec5 Remove duplicate supported websites 2025-04-04 01:11:41 +02:00
68e445a738 Update supported websites 2025-04-04 00:58:14 +02:00
e864bc6100 Update dependencies and version 2025-04-04 00:45:03 +02:00
3ac73ac3b4 Update vite config 2025-04-04 00:44:51 +02:00
6aaf960c28 Holy fuck why does vite have a problem with this 2025-04-04 00:43:57 +02:00
8a13dea681 Format 2025-04-04 00:01:21 +02:00
8d575241fe Fix lastPathSegment util 2025-04-04 00:00:38 +02:00
50f400b8b9 Use enum to specify media type 2025-04-03 23:56:18 +02:00
f6fcfd354a Add more utils 2025-04-03 23:48:53 +02:00
c081127d35 Add loadx.ws 2025-04-03 23:43:49 +02:00
416fceba88 Update match function to manually specify media type 2025-04-03 23:43:39 +02:00
a9cf03c176 Fix luluvdo.com 2025-04-03 23:14:31 +02:00
8852318483 Update voe.sx to catch every redirect url 2025-04-03 22:55:24 +02:00
7067aaf4a0 Add luluvdo.com to supported websites table 2025-03-14 18:52:08 +01:00
bf723e2ed6 Update dependencies and version 2025-03-14 18:41:05 +01:00
9c80362a1c Update ci actions 2025-03-14 18:31:29 +01:00
ae738ff32f Add luluvdo.com site 2025-03-14 18:20:08 +01:00
16d4f2956a Add dood.work domain (#23) 2025-03-14 17:43:50 +01:00
bytedream
f6c6102436
Merge pull request #24 from muleyo/main
Added Voe.sx Domain
2025-03-13 16:08:39 +01:00
muleyo
f1525817b1
Added Voe.sx Domain
A domain for Voe.sx was missing
2025-03-10 01:42:23 +01:00
1e1d8cdfa6 Fix tick character 2024-12-19 22:50:59 +01:00
47eed2d12b Update supported websites 2024-12-15 19:08:50 +01:00
67e03eafaa Update dependencies and version 2024-12-15 18:43:06 +01:00
db0ccd9b56 Fix vidoza.net 2024-12-15 18:05:25 +01:00
fd3eda9c61 Fix vidmoly.to 2024-12-15 17:31:13 +01:00
e522916585 Fix filemoon.to 2024-12-15 17:14:46 +01:00
b9514284da Fix voe.sx 2024-12-15 16:16:38 +01:00
06beaa6ebe Update README 2024-07-29 02:35:43 +02:00
6a56c37dd2 Update package-lock.json 2024-07-29 02:31:30 +02:00
018b381732 Update README 2024-07-29 00:00:57 +02:00
24 changed files with 4555 additions and 3276 deletions

View File

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

View File

@ -1,34 +0,0 @@
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',
'no-undef': 'off'
}
};

View File

@ -9,12 +9,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install nodejs - name: Install nodejs
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 22
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
@ -32,12 +32,12 @@ jobs:
- manifest_version: 3 - manifest_version: 3
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install nodejs - name: Install nodejs
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 22
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
@ -49,7 +49,7 @@ jobs:
run: npm run build run: npm run build
- name: Upload - name: Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: stream-bypass-mv${{ matrix.manifest_version }} name: stream-bypass-mv${{ matrix.manifest_version }}
path: ./dist path: ./dist

View File

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

17
.prettierrc.cjs Normal file
View File

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

View File

@ -12,9 +12,6 @@ A multi-browser addon / extension for multiple streaming providers which redirec
<a href="https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb"> <a href="https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb">
<img src="https://img.shields.io/chrome-web-store/users/ddfpfjomnakfckhmilacnbokdaknamdb?style=flat-square&label=Chrome%20Users" alt="Chrome Store"> <img src="https://img.shields.io/chrome-web-store/users/ddfpfjomnakfckhmilacnbokdaknamdb?style=flat-square&label=Chrome%20Users" alt="Chrome Store">
</a> </a>
<a href="https://addons.mozilla.org/de/firefox/addon/stream-bypass/">
<img src="https://img.shields.io/amo/stars/stream-bypass?label=Firefox%20Store%20Stars&style=flat-square" alt="Firefox Addon Stars">
</a>
<a href="https://github.com/bytedream/stream-bypass/releases/latest"> <a href="https://github.com/bytedream/stream-bypass/releases/latest">
<img src="https://img.shields.io/github/downloads/ByteDream/stream-bypass/total?label=GitHub%20Downloads&style=flat-square" alt="GitHub Downloads"> <img src="https://img.shields.io/github/downloads/ByteDream/stream-bypass/total?label=GitHub%20Downloads&style=flat-square" alt="GitHub Downloads">
</a> </a>
@ -25,7 +22,9 @@ A multi-browser addon / extension for multiple streaming providers which redirec
<a href="#-installation">Installation 📥</a> <a href="#-installation">Installation 📥</a>
<a href="#-supported-sites">Supported Sites 📜</a> <a href="#-features">Features ✨</a>
<a href="#-supported-websites">Supported Websites 📜</a>
<a href="#%EF%B8%8F-building">Building 🛠️</a> <a href="#%EF%B8%8F-building">Building 🛠️</a>
@ -51,7 +50,7 @@ Additionally, this enables you to download the video by right-clicking it and ju
The best way to install the extension are the official browser extension stores: The best way to install the extension are the official browser extension stores:
- [Firefox Addon Store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/) - [Firefox Addon Store](https://addons.mozilla.org/de/firefox/addon/stream-bypass/) (Firefox for Android is supported too!)
- [Chrome Web Store](https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb) - [Chrome Web Store](https://chromewebstore.google.com/detail/ddfpfjomnakfckhmilacnbokdaknamdb)
### Manual installation ### Manual installation
@ -67,46 +66,49 @@ The best way to install the extension are the official browser extension stores:
- Turn on the developer mode by checking the switch in the top right corner - Turn on the developer mode by checking the switch in the top right corner
- Click `Load unpacked` and choose the unzipped directory - Click `Load unpacked` and choose the unzipped directory
## 📜 Supported sites ## ✨ Features
| Site | Supported | Note | | Feature | Firefox | Chrome | Firefox for Android |
| --------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------ | | --------------------------------------------------------------------------------------------------------------------------------- | ------- | ------ | ------------------- |
| [dropload.io](https://dropload.io) | ✔ | | | Replace site-speicifc video player with browser native video player | ✔ | ✔ | ✔ |
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | | | Support websites that are accessed via a redirect | ✔ | ❌ | ✔ |
| [filemoon.sx](https://filemoon.sx) | ✔ | | | Open video in mpv (with [ff2mpv](https://github.com/ByteDream/stream-bypass/tree/master#ff2mpv-use-mpv-to-directly-play-streams)) | ✔ | ✔ | ❌ |
| [goodstream.uno](https://goodstream.uno) | ✔ | |
| [mcloud.to](https://mcloud.to/) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) | - ✔️: Supported.
| [mixdrop.co](https://mixdrop.co) | ✔ | | - ❌: Not supported.
| [mp4upload.com](https://mp4upload.com) | ✔ | |
| [newgrounds.com](https://newgrounds.com) | ✔ | | ## 📜 Supported websites
| [streama2s.com](https://streama2z.com) | ✔ |
| [streamtape.com](https://streamtape.com) | ✔ | | | Site | Firefox & Firefox for Android | Chrome & Chromium based |
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | | | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| [supervideo.tv](https://supervideo.tv) | ✔ | | | [dropload.io](https://dropload.io) | ✔ | ✔ |
| [upstream.to](https://upstream.to) | ✔ | | | [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔ | ⚠ (redirect probably required) |
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time | | [filemoon.to](https://filemoon.to) | ✔ | ✔ |
| [vidmoly.me](https://vidmoly.me) | ✔ | | | [goodstream.uno](https://goodstream.uno) | ✔ | ✔ |
| [vidoza.net](https://vidoza.net) | ✔ | | | [kwik.cx](https://kwik.cx) | ✔ | ✔ |
| [vidstream.pro](https://vidstream.pro) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) | | [loadx.ws](https://loadx.ws) | ✔ | ❌ (background request always required) |
| [voe.sx](https://voe.sx) | ✔ | | | [luluvdo.com](https://luluvdo.com) | ✔ | ❌ (background request always required) |
| [vupload.com](https://vupload.com) | ✔ | | | [mixdrop.co](https://mixdrop.co) | ✔ | ✔ |
| [kwik.cx](https://kwik.cx) | ✔ | | | [mp4upload.com](https://mp4upload.com) | ✔ | ✔ |
| [newgrounds.com](https://newgrounds.com) | ✔ | ✔ |
| [streama2z.com](https://streama2z.com) | ✔ | ❌ (redirect always required) |
| [streamtape.com](https://streamtape.com) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) | ⚠ (correct video url can't always be extract, retrying/reloading the page might fix it) |
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | ✔ |
| [supervideo.tv](https://supervideo.tv) | ✔ | ✔ |
| [upstream.to](https://upstream.to) | ✔ | ✔ |
| [vidmoly.to](https://vidmoly.me) | ✔ | ✔ |
| [vidoza.net](https://vidoza.net) | ✔ | ✔ |
| [voe.sx](https://voe.sx) | ✔ | ❌ (redirect always required) |
| [vupload.com](https://vupload.com) | ✔ | ✔ |
- ✔️: Everything ok. - ✔️: Everything ok.
- ⚠: Included in the addon but will probably not work. See `Note` in this case, an explanation why will stand there in the most cases. - ⚠: Works with limitations.
- ❌: Not included / supported by the addon. This can have various reasons. See `Note` for an explanation. - ❌: Not supported.
_This table might not be 100% accurate, it isn't actively monitored if the addon works for every website!_
Some sites put much effort in obfuscating their code / how they receive the video stream so that it simply cost too much time for me to reverse engineer it and find out how to bypass the native video player of the site. Some sites put much effort in obfuscating their code / how they receive the video stream so that it simply cost too much time for me to reverse engineer it and find out how to bypass the native video player of the site.
<details>
<summary>Hall of dead sites</summary>
<ul>
<li><a href="https://evoload.io">evoload.io</a> - Down</li>
<li><a href="https://vidlox.me">vidlox.me</a> - Reachable but empty</li>
<li><a href="https://vivo.sx">vivo.sx</a> - Down</li>
</ul>
</details>
## 🛠️ Building ## 🛠️ Building
If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions. If you want to build the addon from source and not using the [installation](#installation) way, follow the instructions.
@ -161,11 +163,11 @@ Steps to get it set up:
- Chrome/Chromium: - Chrome/Chromium:
- Go To: Settings -> Extensions - Go To: Settings -> Extensions
- Click on `Details` of the Stream Bypass extension and copy the ID - Click on `Details` of the Stream Bypass extension and copy the ID
- Add `chrome-extension://your-id-here/` to `allowed_origins` -> - Add `chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/` to `allowed_origins` ->
``` ```
"allowed_origins": [ "allowed_origins": [
"chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/", "chrome-extension://ephjcajbkgplkjmelpglennepbpmdpjg/",
"chrome-extension://your-id-her/" "chrome-extension://ddfpfjomnakfckhmilacnbokdaknamdb/"
] ]
``` ```

40
eslint.config.mjs Normal file
View File

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

7128
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "stream-bypass", "name": "stream-bypass",
"version": "3.1.0", "version": "3.1.6",
"displayName": "Stream Bypass", "displayName": "Stream Bypass",
"author": "bytedream", "author": "bytedream",
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video", "description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
@ -11,8 +11,8 @@
"serve:firefox": "web-ext run --start-url \"about:debugging#/runtime/this-firefox\" --source-dir ./dist/", "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/", "serve:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/",
"check": "svelte-check --tsconfig ./tsconfig.json", "check": "svelte-check --tsconfig ./tsconfig.json",
"lint": "prettier --check --plugin prettier-plugin-svelte . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write --plugin prettier-plugin-svelte .", "format": "prettier --write .",
"release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox", "release:firefox": "MANIFEST_VERSION=2 vite build --outDir release/firefox",
"release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome" "release:chrome": "MANIFEST_VERSION=3 vite build --outDir release/chrome"
}, },
@ -25,27 +25,27 @@
"url": "https://github.com/bytedream/stream-bypass/issues" "url": "https://github.com/bytedream/stream-bypass/issues"
}, },
"devDependencies": { "devDependencies": {
"@samrum/vite-plugin-web-extension": "^5.1.0", "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@sveltejs/vite-plugin-svelte": "^2.5.3", "@samrum/vite-plugin-web-extension": "^5.1.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tsconfig/svelte": "^5.0.4", "@tsconfig/svelte": "^5.0.4",
"@types/chrome": "^0.0.269", "@types/chrome": "^0.0.320",
"@types/firefox-webext-browser": "^120.0.4", "@types/firefox-webext-browser": "^120.0.4",
"@typescript-eslint/eslint-plugin": "^7.17.0", "eslint": "^9.26.0",
"@typescript-eslint/parser": "^7.17.0", "eslint-config-prettier": "^10.1.3",
"eslint": "^8.57.0", "eslint-plugin-svelte": "^3.5.1",
"eslint-config-prettier": "^9.1.0", "hls.js": "^1.6.2",
"eslint-plugin-svelte": "^2.43.0", "prettier": "^3.5.3",
"hls.js": "^1.5.13", "prettier-plugin-svelte": "^3.3.3",
"prettier": "^3.3.3", "sass": "^1.87.0",
"prettier-plugin-svelte": "^3.2.6", "svelte": "^5.28.2",
"sass": "^1.77.8", "svelte-check": "^4.1.7",
"svelte": "^4.2.18", "svelte-preprocess": "^6.0.3",
"svelte-check": "^3.8.4", "tslib": "^2.8.1",
"svelte-preprocess": "^6.0.2", "typescript": "^5.8.3",
"tslib": "^2.6.3", "typescript-eslint": "^8.32.0",
"typescript": "^5.5.4", "vite": "^6.3.5",
"vite": "^4.5.3", "web-ext": "^8.6.0"
"web-ext": "^8.2.0"
}, },
"type": "module" "type": "module"
} }

View File

@ -1,23 +1,22 @@
import './shared'; import './shared';
import type { Match } from '~/lib/match'; import { getMatch, type Match } from '~/lib/match';
import { Redirect, storageDelete, storageGet } from '~/lib/settings'; import { Redirect, UrlReferer } from '~/lib/settings';
import { getMatch } from '~/lib/match';
chrome.webRequest.onBeforeSendHeaders.addListener( chrome.webRequest.onBeforeSendHeaders.addListener(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
async (details) => { async (details) => {
const referer: { domain: string } | undefined = await storageGet('referer'); const referer = await UrlReferer.get(new URL(details.url).hostname);
if (referer === undefined) return; if (!referer) return;
await UrlReferer.delete(new URL(details.url).hostname);
details.requestHeaders!.push({ details.requestHeaders!.push({
name: 'Referer', name: 'Referer',
value: `https://${referer.domain}/` value: `https://${referer}/`
}); });
await storageDelete('referer');
return { requestHeaders: details.requestHeaders }; return { requestHeaders: details.requestHeaders };
}, },
{ urls: ['<all_urls>'], types: ['xmlhttprequest'] }, { urls: ['<all_urls>'], types: ['xmlhttprequest'] },

View File

@ -1,5 +1,4 @@
import type { Match } from '~/lib/match'; import { getMatch, MatchMediaType, type Match } from '~/lib/match';
import { getMatch } from '~/lib/match';
import { Other, Redirect } from '~/lib/settings'; import { Other, Redirect } from '~/lib/settings';
async function main() { async function main() {
@ -20,22 +19,43 @@ async function main() {
return; return;
} }
const re = document.body.innerHTML.match(match.regex); let re = null;
for (const regex of match.regex) {
if ((re = document.body.innerHTML.match(regex)) !== null) {
break;
}
}
if (re === null) { if (re === null) {
return; return;
} }
if (redirect) { if (redirect) {
await Redirect.delete(); await Redirect.delete();
} }
let url: string | null; let url: string | null;
let urlType: MatchMediaType | null;
try { try {
url = await match.match(re); const matchResult = await match.match(re);
} catch (e) { if (matchResult && typeof matchResult === 'string') {
url = matchResult;
urlType = url.includes('.m3u8') ? MatchMediaType.Hls : MatchMediaType.Native;
} else if (matchResult && typeof matchResult === 'object') {
if (MatchMediaType.Hls in matchResult) {
url = matchResult[MatchMediaType.Hls];
urlType = MatchMediaType.Hls;
} else if (MatchMediaType.Native in matchResult) {
url = matchResult[MatchMediaType.Native];
urlType = MatchMediaType.Native;
}
}
} catch {
return; return;
} }
if (!url) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!url || !urlType) {
return; return;
} }
@ -44,7 +64,7 @@ async function main() {
await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url }); await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url });
} }
if (match.replace && !url.includes('.m3u8')) { if (match.replace && urlType != MatchMediaType.Hls) {
// this destroys all intervals that may spawn popups or events // this destroys all intervals that may spawn popups or events
let intervalId = window.setInterval(() => {}, 0); let intervalId = window.setInterval(() => {}, 0);
while (intervalId--) { while (intervalId--) {
@ -58,6 +78,8 @@ async function main() {
// clear completed document // clear completed document
document.documentElement.innerHTML = ''; document.documentElement.innerHTML = '';
document.body.style.backgroundColor = '#131313';
// video player // video player
const player = document.createElement('video'); const player = document.createElement('video');
player.style.width = '100%'; player.style.width = '100%';
@ -73,7 +95,7 @@ async function main() {
chrome.runtime.getURL( chrome.runtime.getURL(
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${ `src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
window.location.hostname window.location.hostname
}` }&type=${urlType}`
) )
); );
} }

View File

@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import { play } from '~/entries/player/player';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { play } from '~/entries/player/player';
let errorMessage: string | null = $state(null);
let errorMessage: string | null = null;
let videoElem: HTMLVideoElement; let videoElem: HTMLVideoElement;
onMount(async () => { onMount(async () => {
@ -10,13 +11,13 @@
await play(videoElem); await play(videoElem);
videoElem.controls = true; videoElem.controls = true;
} catch (e) { } catch (e) {
errorMessage = e; errorMessage = e as string;
} }
}); });
</script> </script>
<!-- svelte-ignore a11y-media-has-caption --> <!-- svelte-ignore a11y_media_has_caption -->
<video id="video" bind:this={videoElem} /> <video id="video" bind:this={videoElem}></video>
{#if errorMessage} {#if errorMessage}
<div id="message-container"> <div id="message-container">
<p> <p>

View File

@ -6,9 +6,10 @@
</head> </head>
<body> <body>
<script type="module"> <script type="module">
import { mount } from 'svelte';
import Player from '~/entries/player/Player.svelte'; import Player from '~/entries/player/Player.svelte';
new Player({ mount(Player, {
target: document.body target: document.body
}); });
</script> </script>

View File

@ -1,17 +1,23 @@
import { matches } from '~/lib/match';
import Hls from 'hls.js'; import Hls from 'hls.js';
import { storageSet } from '~/lib/settings'; import { matches, MatchMediaType } from '~/lib/match';
import { UrlReferer } from '~/lib/settings';
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
await UrlReferer.set(new URL(url).hostname, domain);
async function playNative(url: string, videoElem: HTMLVideoElement) {
videoElem.src = url; videoElem.src = url;
} }
async function playHls(url: string, videoElem: HTMLVideoElement) { async function playHls(url: string, domain: string, videoElem: HTMLVideoElement) {
if (videoElem.canPlayType('application/vnd.apple.mpegurl')) { if (videoElem.canPlayType('application/vnd.apple.mpegurl')) {
videoElem.src = url; videoElem.src = url;
} else if (Hls.isSupported()) { } else if (Hls.isSupported()) {
const hls = new Hls({ const hls = new Hls({
enableWorker: false enableWorker: false,
xhrSetup: async (xhr: XMLHttpRequest, url: string) => {
await UrlReferer.set(new URL(url).hostname, domain);
xhr.open('GET', url);
}
}); });
hls.loadSource(url); hls.loadSource(url);
hls.attachMedia(videoElem); hls.attachMedia(videoElem);
@ -25,6 +31,7 @@ export async function play(videoElem: HTMLVideoElement) {
const id = urlQuery.get('id') as string; const id = urlQuery.get('id') as string;
const url = decodeURIComponent(urlQuery.get('url') as string); const url = decodeURIComponent(urlQuery.get('url') as string);
const domain = urlQuery.get('domain') as string; const domain = urlQuery.get('domain') as string;
const type = urlQuery.get('type') as MatchMediaType;
const match = matches[id]; const match = matches[id];
if (match === undefined) { if (match === undefined) {
@ -32,11 +39,9 @@ export async function play(videoElem: HTMLVideoElement) {
} }
document.title = `Stream Bypass (${domain})`; document.title = `Stream Bypass (${domain})`;
await storageSet('referer', { domain: domain }); if (type === MatchMediaType.Hls) {
await playHls(url, domain, videoElem);
if (new URL(url).pathname.endsWith('.m3u8')) { } else if (type === MatchMediaType.Native) {
await playHls(url, videoElem); await playNative(url, domain, videoElem);
} else {
await playNative(url, videoElem);
} }
} }

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { type Match, matches } from '~/lib/match'; import { matches, type Match } from '~/lib/match';
import { Hosters, Other } from '~/lib/settings'; import { Hosters, Other } from '~/lib/settings';
import Toggle from './toggle.svelte'; import Toggle from './toggle.svelte';
@ -39,11 +39,11 @@
<Toggle <Toggle
bind:checked={hostersEnabled} bind:checked={hostersEnabled}
id="hosters-enabled" id="hosters-enabled"
on:change={() => Hosters.setAll(hostersEnabled)} onChange={() => Hosters.setAll(hostersEnabled)}
/> />
</div> </div>
<hr /> <hr />
{#each hosters as hoster, i} {#each hosters as hoster, i (hoster.id)}
<label for="hoster-{i}" style="cursor: {hostersEnabled ? 'pointer' : 'default'}" <label for="hoster-{i}" style="cursor: {hostersEnabled ? 'pointer' : 'default'}"
>{hoster.name}</label >{hoster.name}</label
> >
@ -52,7 +52,7 @@
bind:checked={hoster.active} bind:checked={hoster.active}
disabled={!hostersEnabled} disabled={!hostersEnabled}
id="hoster-{i}" id="hoster-{i}"
on:change={async () => { onChange={async () => {
if (hoster.active) { if (hoster.active) {
await Hosters.enable(hoster); await Hosters.enable(hoster);
} else { } else {
@ -73,7 +73,7 @@
<Toggle <Toggle
bind:checked={ff2mpvEnabled} bind:checked={ff2mpvEnabled}
id="ff2mpv" id="ff2mpv"
on:change={async () => { onChange={async () => {
ff2mpvEnabled = !ff2mpvEnabled; ff2mpvEnabled = !ff2mpvEnabled;
if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) { if (await browser.permissions.request({ permissions: ['nativeMessaging'] })) {
await Other.setFf2mpv(ff2mpvEnabled); await Other.setFf2mpv(ff2mpvEnabled);

View File

@ -7,9 +7,10 @@
</head> </head>
<body style="overflow-y: scroll"> <body style="overflow-y: scroll">
<script type="module"> <script type="module">
import { mount } from 'svelte';
import Popup from '~/entries/popup/Popup.svelte'; import Popup from '~/entries/popup/Popup.svelte';
new Popup({ mount(Popup, {
target: document.body target: document.body
}); });
</script> </script>

View File

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

View File

@ -1,18 +1,21 @@
<!-- https://flowbite.com/docs/forms/toggle/ --> <!-- https://flowbite.com/docs/forms/toggle/ -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import type { Snippet } from 'svelte';
export let checked = false; type Props = {
export let disabled = false; checked?: boolean;
export let id: string | null = null; disabled?: boolean;
id?: string | null;
const dispatch = createEventDispatcher(); onChange?: () => void;
children?: Snippet;
};
let { checked = $bindable(), disabled, id = null, onChange, children }: Props = $props();
</script> </script>
<label class="toggle"> <label class="toggle">
<slot /> {@render children?.()}
<input type="checkbox" {id} bind:checked {disabled} on:change={(e) => dispatch('change', e)} /> <input type="checkbox" {id} bind:checked {disabled} onchange={onChange} />
<span /> <span></span>
</label> </label>
<style lang="scss" global> <style lang="scss" global>

View File

@ -1,15 +1,28 @@
import { unpack } from './utils'; import { Hosters, Redirect, TmpHost } from './settings';
import { Hosters, Redirect } from './settings'; import { lastPathSegment } from './util/extract';
import { unpack } from './util/userspace';
export interface Match { export interface Match {
name: string; name: string;
id: string; id: string;
domains: string[]; domains: string[];
replace?: boolean; replace?: boolean;
regex: RegExp; regex: RegExp[];
notice?: string; notice?: string;
match(match: RegExpMatchArray): Promise<string | null>; match(
match: RegExpMatchArray
): Promise<
string | { [MatchMediaType.Hls]: string } | { [MatchMediaType.Native]: string } | null
>;
// allow other properties that may be implemented by the objects that use this interface declaration
[other: string]: any;
}
export enum MatchMediaType {
Hls = 'hls',
Native = 'native'
} }
export const Doodstream: Match = { export const Doodstream: Match = {
@ -23,6 +36,7 @@ export const Doodstream: Match = {
'dood.cx', 'dood.cx',
'dood.sh', 'dood.sh',
'dood.watch', 'dood.watch',
'dood.work',
'dood.to', 'dood.to',
'dood.so', 'dood.so',
'dood.la', 'dood.la',
@ -35,9 +49,9 @@ export const Doodstream: Match = {
'd000d.com' 'd000d.com'
], ],
replace: true, replace: true,
regex: /(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s, regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const response = await fetch(`https://${window.location.host}${match[1]}`, { const response = await fetch(`https://${window.location.host}${match[1]}`, {
headers: { headers: {
Range: 'bytes=0-' Range: 'bytes=0-'
@ -53,10 +67,10 @@ export const Doodstream: Match = {
export const DropLoad: Match = { export const DropLoad: Match = {
name: 'Dropload', name: 'Dropload',
id: 'dropload', id: 'dropload',
domains: ['dropload.ui'], domains: ['dropload.io'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0]; return unpacked.match(/(?<=file:").*(?=")/)![0];
} }
@ -65,12 +79,20 @@ export const DropLoad: Match = {
export const Filemoon: Match = { export const Filemoon: Match = {
name: 'Filemoon', name: 'Filemoon',
id: 'filemoon', id: 'filemoon',
domains: ['filemoon.sx', 'filemoon.in'], domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/(?<=<iframe\s*src=")\S*(?=")/s, /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
replace: true,
match: async function (match: RegExpMatchArray) {
if (window.location.host.startsWith('filemoon')) {
await TmpHost.set(new URL(match[0]).host, Filemoon);
return null;
}
await TmpHost.delete();
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0]; return unpacked.match(/(?<=file:")\S*(?=")/)![0];
} }
}; };
@ -78,9 +100,9 @@ export const GoodStream: Match = {
name: 'Goodstream', name: 'Goodstream',
id: 'goodstream', id: 'goodstream',
domains: ['goodstream.uno'], domains: ['goodstream.uno'],
regex: /(?<=file:\s+").*(?=")/g, regex: [/(?<=file:\s+").*(?=")/g],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
return match[0]; return match[0];
} }
}; };
@ -89,21 +111,80 @@ export const Kwik: Match = {
name: 'Kwik', name: 'Kwik',
id: 'kwik', id: 'kwik',
domains: ['kwik.cx'], domains: ['kwik.cx'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=source=').*(?=')/)![0]; return unpacked.match(/(?<=source=').*(?=')/)![0];
} }
}; };
export const LoadX: Match = {
name: 'LoadX',
id: 'loadx',
domains: ['loadx.ws'],
regex: [/./gm],
match: async () => {
const hash = encodeURIComponent(lastPathSegment(window.location.href));
const response = await fetch(
`https://${window.location.host}/player/index.php?data=${hash}&do=getVideo`,
{
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}
);
const responseJson = await response.json();
const videoSource: string = responseJson['videoSource'];
// extension of extracted url is '.txt', so we have to manually specify that it's a hls
return { [MatchMediaType.Hls]: videoSource.replace('\\/', '/') };
}
};
export const Luluvdo: Match = {
name: 'Luluvdo',
id: 'luluvdo',
domains: ['luluvdo.com'],
regex: [/./gm],
match: async () => {
const requestBody = new FormData();
requestBody.set('op', 'embed');
requestBody.set('file_code', lastPathSegment(window.location.href));
const response = await fetch(`https://${window.location.host}/dl`, {
method: 'POST',
body: requestBody,
referrer: window.location.href
});
let unpacked;
const responseText = await response.text();
const evalMatch = responseText.match(/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms)!;
// sometimes is packed, sometimes it's not. looks like someone forgets to obfuscate the code when pushing to
// production
if (evalMatch) {
unpacked = await unpack(evalMatch[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
} else {
unpacked = responseText;
}
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Mixdrop: Match = { export const Mixdrop: Match = {
name: 'Mixdrop', name: 'Mixdrop',
id: 'mixdrop', id: 'mixdrop',
domains: ['mixdrop.co', 'mixdrop.to', 'mixdrop.ch', 'mixdrop.bz', 'mixdrop.gl'], domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0]; const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
return `https:${url}`; return `https:${url}`;
@ -115,9 +196,9 @@ export const Mp4Upload: Match = {
id: 'mp4upload', id: 'mp4upload',
domains: ['mp4upload.com'], domains: ['mp4upload.com'],
replace: true, replace: true,
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=player.src\(").*(?=")/)![0]; return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
} }
@ -127,7 +208,7 @@ export const Newgrounds: Match = {
name: 'Newgrounds', name: 'Newgrounds',
id: 'newgrounds', id: 'newgrounds',
domains: ['newgrounds.com'], domains: ['newgrounds.com'],
regex: /.*/gm, regex: [/.*/gm],
match: async () => { match: async () => {
const id = window.location.pathname.split('/').slice(-1)[0]; const id = window.location.pathname.split('/').slice(-1)[0];
@ -145,9 +226,9 @@ export const StreamA2z: Match = {
name: 'Stream2Az', name: 'Stream2Az',
id: 'stream2az', id: 'stream2az',
domains: ['streama2z.com', 'streama2z.xyz'], domains: ['streama2z.com', 'streama2z.xyz'],
regex: /https?:\/\/\S*m3u8.+(?=['"])/gm, regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) { if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
await Redirect.set(StreamA2z); await Redirect.set(StreamA2z);
return null; return null;
@ -160,9 +241,9 @@ export const Streamtape: Match = {
name: 'Streamtape', name: 'Streamtape',
id: 'streamtape', id: 'streamtape',
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'], domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
regex: /id=.*(?=')/gm, regex: [/id=.*(?=')/gm],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
let i = 0; let i = 0;
while (i < match.length) { while (i < match.length) {
if (match[++i - 1] == match[i]) { if (match[++i - 1] == match[i]) {
@ -179,9 +260,9 @@ export const Streamzz: Match = {
name: 'Streamzz', name: 'Streamzz',
id: 'streamzz', id: 'streamzz',
domains: ['streamzz.to', 'streamz.ws'], domains: ['streamzz.to', 'streamz.ws'],
regex: /(?<=\|)\w{2,}/gm, regex: [/(?<=\|)\w{2,}/gm],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${ return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
match.sort((a, b) => b.length - a.length)[0] match.sort((a, b) => b.length - a.length)[0]
}.dll`; }.dll`;
@ -191,10 +272,10 @@ export const Streamzz: Match = {
export const SuperVideo: Match = { export const SuperVideo: Match = {
name: 'Supervideo', name: 'Supervideo',
id: 'supervideo', id: 'supervideo',
domains: ['supervideo.tv'], domains: ['supervideo.cc', 'supervideo.tv'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0]; return unpacked.match(/(?<=file:").*(?=")/)![0];
} }
@ -204,9 +285,9 @@ export const Upstream: Match = {
name: 'Upstream', name: 'Upstream',
id: 'upstream', id: 'upstream',
domains: ['upstream.to'], domains: ['upstream.to'],
regex: /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms, regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]); const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0]; return unpacked.match(/(?<=file:").*(?=")/)![0];
} }
@ -216,10 +297,10 @@ export const Vidmoly: Match = {
name: 'Vidmoly', name: 'Vidmoly',
id: 'vidmoly', id: 'vidmoly',
domains: ['vidmoly.me', 'vidmoly.to'], domains: ['vidmoly.me', 'vidmoly.to'],
regex: /(?<=file:").+\.m3u8/gm, regex: [/(?<=file:").+\.m3u8/gm],
replace: true,
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
alert('a');
return match[0]; return match[0];
} }
}; };
@ -227,11 +308,11 @@ export const Vidmoly: Match = {
export const Vidoza: Match = { export const Vidoza: Match = {
name: 'Vidoza', name: 'Vidoza',
id: 'vidoza', id: 'vidoza',
domains: ['vidoza.net'], domains: ['vidoza.net', 'videzz.net'],
regex: /(?<=src:\s?").+?(?=")/gm, regex: [/(?<=src:\s?").+?(?=")/gm],
replace: true, replace: true,
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
return match[0]; return match[0];
} }
}; };
@ -240,10 +321,67 @@ export const Voe: Match = {
name: 'Voe', name: 'Voe',
id: 'voe', id: 'voe',
domains: ['voe.sx'], domains: ['voe.sx'],
regex: /https?:\/\/\S*m3u8.+(?=['"])/gm, regex: [
// voe.sx
/(?<=window\.location\.href\s=\s')\S*(?=')/gm,
// whatever site voe.sx redirects to
/(?<=<script type="application\/json">).*(?=<\/script>)/m
],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
return match[0]; if (window.location.host === 'voe.sx') {
const redirectUrl = new URL(match[0]);
await TmpHost.set(redirectUrl.host, Voe);
return null;
} else {
let json = match[0];
json = JSON.parse(json);
let deobfuscated = json[0];
deobfuscated = this.rot13(deobfuscated);
deobfuscated = this.removeSpecialSequences(deobfuscated);
deobfuscated = atob(deobfuscated);
deobfuscated = this.shiftString(deobfuscated);
deobfuscated = deobfuscated.split('').reverse().join('');
deobfuscated = atob(deobfuscated);
const payload = JSON.parse(deobfuscated);
return payload['source'];
}
},
rot13: function (encrypted: string) {
let decrypted = '';
for (let i = 0; i < encrypted.length; i++) {
let char = encrypted.charCodeAt(i);
if (char >= 65 && char <= 90) {
char = ((char - 65 + 13) % 26) + 65;
} else if (char >= 97 && char <= 122) {
char = ((char - 97 + 13) % 26) + 97;
}
decrypted += String.fromCharCode(char);
}
return decrypted;
},
removeSpecialSequences: function (input: string) {
return input
.replaceAll(/@\$/g, '')
.replaceAll(/\^\^/g, '')
.replaceAll(/~@/g, '')
.replaceAll(/%\?/g, '')
.replaceAll(/\*~/g, '')
.replaceAll(/!!/g, '')
.replaceAll(/#&/g, '');
},
shiftString: function (input: string) {
let shifted = '';
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
const shiftedChar = char - 3;
shifted += String.fromCharCode(shiftedChar);
}
return shifted;
} }
}; };
@ -251,9 +389,9 @@ export const Vupload: Match = {
name: 'Vupload', name: 'Vupload',
id: 'vupload', id: 'vupload',
domains: ['vupload.com'], domains: ['vupload.com'],
regex: /(?<=src:\s?").+?(?=")/gm, regex: [/(?<=src:\s?").+?(?=")/gm],
match: async (match: RegExpMatchArray) => { match: async function (match: RegExpMatchArray) {
return match[0]; return match[0];
} }
}; };
@ -264,6 +402,8 @@ export const matches = {
[Filemoon.id]: Filemoon, [Filemoon.id]: Filemoon,
[GoodStream.id]: GoodStream, [GoodStream.id]: GoodStream,
[Kwik.id]: Kwik, [Kwik.id]: Kwik,
[LoadX.id]: LoadX,
[Luluvdo.id]: Luluvdo,
[Mixdrop.id]: Mixdrop, [Mixdrop.id]: Mixdrop,
[Mp4Upload.id]: Mp4Upload, [Mp4Upload.id]: Mp4Upload,
[Newgrounds.id]: Newgrounds, [Newgrounds.id]: Newgrounds,
@ -292,5 +432,10 @@ export async function getMatch(domain: string): Promise<Match | null> {
} }
} }
const tmpHost = await TmpHost.get();
if (tmpHost && tmpHost[0] === domain) {
return tmpHost[1];
}
return null; return null;
} }

View File

@ -1,5 +1,4 @@
import type { Match } from './match'; import { matches, type Match } from './match';
import { matches } from './match';
export const Hosters = { export const Hosters = {
getDisabled: async () => { getDisabled: async () => {
@ -42,6 +41,34 @@ export const Redirect = {
} }
}; };
export const TmpHost = {
get: async (): Promise<[string, Match] | null> => {
const tmphost = await storageGet<[string, number]>('tmphost');
if (tmphost === undefined) {
return null;
}
return [tmphost[0], matches[tmphost[1]]];
},
set: async (domain: string, match: Match) => {
await storageSet('tmphost', [domain, match.id]);
},
delete: async () => {
await storageDelete('tmphost');
}
};
export const UrlReferer = {
get: async (url: string): Promise<string | null> => {
return (await storageGet(`urlReferer.${url}`)) || null;
},
set: async (url: string, referer: string) => {
await storageSet(`urlReferer.${url}`, referer);
},
delete: async (url: string) => {
await storageDelete(`urlReferer.${url}`);
}
};
export const Other = { export const Other = {
getFf2mpv: async () => { getFf2mpv: async () => {
return await storageGet('other.ff2mpv', false); return await storageGet('other.ff2mpv', false);
@ -51,7 +78,7 @@ export const Other = {
} }
}; };
export async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> { async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
let resolve: (value: T | undefined) => void; let resolve: (value: T | undefined) => void;
const promise = new Promise<T | undefined>((r) => (resolve = r)); const promise = new Promise<T | undefined>((r) => (resolve = r));
@ -63,7 +90,7 @@ export async function storageGet<T>(key: string, defaultValue?: T): Promise<T |
return promise; return promise;
} }
export async function storageSet<T>(key: string, value: T) { async function storageSet<T>(key: string, value: T) {
let resolve: () => void; let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r)); const promise = new Promise<void>((r) => (resolve = r));
@ -75,7 +102,7 @@ export async function storageSet<T>(key: string, value: T) {
return promise; return promise;
} }
export async function storageDelete(key: string) { async function storageDelete(key: string) {
let resolve: () => void; let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r)); const promise = new Promise<void>((r) => (resolve = r));

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

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

View File

@ -16,7 +16,7 @@ export async function unpack(packed: string): Promise<string> {
${packed} ${packed}
}; };
return packed; return packed;
}' }
`; `;
const res = (await runInPageContext(toExecute)) as string; const res = (await runInPageContext(toExecute)) as string;
@ -32,8 +32,9 @@ export async function unpack(packed: string): Promise<string> {
async function runInPageContext<T>(toExecute: string): Promise<T | null> { async function runInPageContext<T>(toExecute: string): Promise<T | null> {
// test that we are running with the allow-scripts permission // test that we are running with the allow-scripts permission
try { try {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
window.sessionStorage; window.sessionStorage;
} catch (ignore) { } catch {
return null; return null;
} }
@ -48,18 +49,17 @@ async function runInPageContext<T>(toExecute: string): Promise<T | null> {
scriptElm.textContent = ` scriptElm.textContent = `
( (
async function () { async function () {
const response = {
id: '${resultMessageId}'
};
const response = { try {
id: ${resultMessageId} response.result = JSON.stringify(await (${toExecute})() ); // run script
}; } catch(err) {
response.error = JSON.stringify(err);
try { }
response.result = JSON.stringify(await (${toExecute})() ); // run script
} catch(err) {
response.error = JSON.stringify(err);
}
window.postMessage(response, '*'); window.postMessage(response, '*');
} }
)(); )();
`; `;

View File

@ -2,7 +2,7 @@
"extends": "@tsconfig/svelte/tsconfig.json", "extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"target": "es2019", "target": "es2021",
"module": "esnext", "module": "esnext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"resolveJsonModule": true, "resolveJsonModule": true,

View File

@ -1,9 +1,9 @@
import { defineConfig, loadEnv } from 'vite'; import { fileURLToPath } from 'url';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import webExtension from '@samrum/vite-plugin-web-extension'; import webExtension from '@samrum/vite-plugin-web-extension';
import path from 'path'; import { svelte } from '@sveltejs/vite-plugin-svelte';
import { getManifest } from './src/manifest'; import { defineConfig, loadEnv, type PluginOption } from 'vite';
import { matches } from './src/lib/match'; import { matches } from './src/lib/match';
import { getManifest } from './src/manifest';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
@ -11,7 +11,7 @@ export default defineConfig(({ mode }) => {
return { return {
plugins: [ plugins: [
svelte(), svelte() as PluginOption,
webExtension({ webExtension({
manifest: getManifest(Number(env.MANIFEST_VERSION)), manifest: getManifest(Number(env.MANIFEST_VERSION)),
additionalInputs: { additionalInputs: {
@ -27,12 +27,10 @@ export default defineConfig(({ mode }) => {
} }
] ]
} }
}) }) as unknown as PluginOption
], ],
resolve: { resolve: {
alias: { alias: [{ find: '~', replacement: fileURLToPath(new URL('./src', import.meta.url)) }]
'~': path.resolve(__dirname, './src')
}
} }
}; };
}); });