Compare commits

..

No commits in common. "main" and "v3.1.2" have entirely different histories.
main ... v3.1.2

19 changed files with 340 additions and 2134 deletions

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

@ -1,17 +0,0 @@
// @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

@ -85,9 +85,6 @@ The best way to install the extension are the official browser extension stores:
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔ | ⚠ (redirect probably required) |
| [filemoon.to](https://filemoon.to) | ✔ | ✔ |
| [goodstream.uno](https://goodstream.uno) | ✔ | ✔ |
| [kwik.cx](https://kwik.cx) | ✔ | ✔ |
| [loadx.ws](https://loadx.ws) | ✔ | ❌ (background request always required) |
| [luluvdo.com](https://luluvdo.com) | ✔ | ❌ (background request always required) |
| [mixdrop.co](https://mixdrop.co) | ✔ | ✔ |
| [mp4upload.com](https://mp4upload.com) | ✔ | ✔ |
| [newgrounds.com](https://newgrounds.com) | ✔ | ✔ |
@ -100,6 +97,7 @@ The best way to install the extension are the official browser extension stores:
| [vidoza.net](https://vidoza.net) | ✔ | ✔ |
| [voe.sx](https://voe.sx) | ✔ | ❌ (redirect always required) |
| [vupload.com](https://vupload.com) | ✔ | ✔ |
| [kwik.cx](https://kwik.cx) | ✔ | ✔ |
- ✔️: Everything ok.
- ⚠: Works with limitations.

View File

@ -1,5 +1,5 @@
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import ts from 'typescript-eslint';

2170
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "stream-bypass",
"version": "3.1.6",
"version": "3.1.2",
"displayName": "Stream Bypass",
"author": "bytedream",
"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:chrome": "web-ext run -t chromium --start-url \"https://example.com\" --source-dir ./dist/",
"check": "svelte-check --tsconfig ./tsconfig.json",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"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"
},
@ -25,27 +25,26 @@
"url": "https://github.com/bytedream/stream-bypass/issues"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@samrum/vite-plugin-web-extension": "^5.1.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tsconfig/svelte": "^5.0.4",
"@types/chrome": "^0.0.320",
"@types/chrome": "^0.0.309",
"@types/firefox-webext-browser": "^120.0.4",
"eslint": "^9.26.0",
"eslint-config-prettier": "^10.1.3",
"eslint-plugin-svelte": "^3.5.1",
"hls.js": "^1.6.2",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-svelte": "^3.1.0",
"hls.js": "^1.5.20",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3",
"sass": "^1.87.0",
"svelte": "^5.28.2",
"svelte-check": "^4.1.7",
"sass": "^1.85.1",
"svelte": "^5.23.0",
"svelte-check": "^4.1.5",
"svelte-preprocess": "^6.0.3",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.0",
"vite": "^6.3.5",
"web-ext": "^8.6.0"
"typescript": "^5.8.2",
"typescript-eslint": "^8.26.1",
"vite": "^6.2.2",
"web-ext": "^8.4.0"
},
"type": "module"
}

View File

@ -1,7 +1,8 @@
import './shared';
import { getMatch, type Match } from '~/lib/match';
import type { Match } from '~/lib/match';
import { Redirect, UrlReferer } from '~/lib/settings';
import { getMatch } from '~/lib/match';
chrome.webRequest.onBeforeSendHeaders.addListener(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

View File

@ -1,4 +1,5 @@
import { getMatch, MatchMediaType, type Match } from '~/lib/match';
import type { Match } from '~/lib/match';
import { getMatch } from '~/lib/match';
import { Other, Redirect } from '~/lib/settings';
async function main() {
@ -34,28 +35,13 @@ async function main() {
}
let url: string | null;
let urlType: MatchMediaType | null;
try {
const matchResult = await match.match(re);
if (matchResult && typeof matchResult === 'string') {
url = matchResult;
urlType = url.includes('.m3u8') ? MatchMediaType.Hls : MatchMediaType.Native;
} else if (matchResult && typeof matchResult === 'object') {
if (MatchMediaType.Hls in matchResult) {
url = matchResult[MatchMediaType.Hls];
urlType = MatchMediaType.Hls;
} else if (MatchMediaType.Native in matchResult) {
url = matchResult[MatchMediaType.Native];
urlType = MatchMediaType.Native;
}
}
url = await match.match(re);
} catch {
return;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!url || !urlType) {
if (!url) {
return;
}
@ -64,7 +50,7 @@ async function main() {
await chrome.runtime.sendMessage({ action: 'ff2mpv', url: url });
}
if (match.replace && urlType != MatchMediaType.Hls) {
if (match.replace && !url.includes('.m3u8')) {
// this destroys all intervals that may spawn popups or events
let intervalId = window.setInterval(() => {}, 0);
while (intervalId--) {
@ -95,7 +81,7 @@ async function main() {
chrome.runtime.getURL(
`src/entries/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${
window.location.hostname
}&type=${urlType}`
}`
)
);
}

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import { play } from '~/entries/player/player';
import { onMount } from 'svelte';
let errorMessage: string | null = $state(null);

View File

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

View File

@ -1,5 +1,5 @@
import { matches } from '~/lib/match';
import Hls from 'hls.js';
import { matches, MatchMediaType } from '~/lib/match';
import { UrlReferer } from '~/lib/settings';
async function playNative(url: string, domain: string, videoElem: HTMLVideoElement) {
@ -31,7 +31,6 @@ export async function play(videoElem: HTMLVideoElement) {
const id = urlQuery.get('id') as string;
const url = decodeURIComponent(urlQuery.get('url') as string);
const domain = urlQuery.get('domain') as string;
const type = urlQuery.get('type') as MatchMediaType;
const match = matches[id];
if (match === undefined) {
@ -39,9 +38,9 @@ export async function play(videoElem: HTMLVideoElement) {
}
document.title = `Stream Bypass (${domain})`;
if (type === MatchMediaType.Hls) {
if (new URL(url).pathname.endsWith('.m3u8')) {
await playHls(url, domain, videoElem);
} else if (type === MatchMediaType.Native) {
} else {
await playNative(url, domain, videoElem);
}
}

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { matches, type Match } from '~/lib/match';
import { type Match, matches } from '~/lib/match';
import { Hosters, Other } from '~/lib/settings';
import Toggle from './toggle.svelte';

View File

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

View File

@ -1,6 +1,5 @@
import { unpack } from './utils';
import { Hosters, Redirect, TmpHost } from './settings';
import { lastPathSegment } from './util/extract';
import { unpack } from './util/userspace';
export interface Match {
name: string;
@ -10,19 +9,7 @@ export interface Match {
regex: RegExp[];
notice?: string;
match(
match: RegExpMatchArray
): Promise<
string | { [MatchMediaType.Hls]: string } | { [MatchMediaType.Native]: string } | null
>;
// allow other properties that may be implemented by the objects that use this interface declaration
[other: string]: any;
}
export enum MatchMediaType {
Hls = 'hls',
Native = 'native'
match(match: RegExpMatchArray): Promise<string | null>;
}
export const Doodstream: Match = {
@ -51,7 +38,7 @@ export const Doodstream: Match = {
replace: true,
regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const response = await fetch(`https://${window.location.host}${match[1]}`, {
headers: {
Range: 'bytes=0-'
@ -67,10 +54,10 @@ export const Doodstream: Match = {
export const DropLoad: Match = {
name: 'Dropload',
id: 'dropload',
domains: ['dropload.io'],
domains: ['dropload.ui'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
@ -83,7 +70,7 @@ export const Filemoon: Match = {
regex: [/(?<=<iframe\s*src=")\S*(?=")/s, /eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
replace: true,
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
if (window.location.host.startsWith('filemoon')) {
await TmpHost.set(new URL(match[0]).host, Filemoon);
return null;
@ -102,7 +89,7 @@ export const GoodStream: Match = {
domains: ['goodstream.uno'],
regex: [/(?<=file:\s+").*(?=")/g],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
@ -113,38 +100,12 @@ export const Kwik: Match = {
domains: ['kwik.cx'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=source=').*(?=')/)![0];
}
};
export const LoadX: Match = {
name: 'LoadX',
id: 'loadx',
domains: ['loadx.ws'],
regex: [/./gm],
match: async () => {
const hash = encodeURIComponent(lastPathSegment(window.location.href));
const response = await fetch(
`https://${window.location.host}/player/index.php?data=${hash}&do=getVideo`,
{
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}
);
const responseJson = await response.json();
const videoSource: string = responseJson['videoSource'];
// extension of extracted url is '.txt', so we have to manually specify that it's a hls
return { [MatchMediaType.Hls]: videoSource.replace('\\/', '/') };
}
};
export const Luluvdo: Match = {
name: 'Luluvdo',
id: 'luluvdo',
@ -152,28 +113,22 @@ export const Luluvdo: Match = {
regex: [/./gm],
match: async () => {
const requestBody = new FormData();
requestBody.set('op', 'embed');
requestBody.set('file_code', lastPathSegment(window.location.href));
const post_match = window.location.href.match(/(?<=\/embed\/)\S*(\/.*)?/)!;
const request_body = new FormData();
request_body.set('op', 'embed');
request_body.set('file_code', post_match[0]);
const response = await fetch(`https://${window.location.host}/dl`, {
method: 'POST',
body: requestBody,
body: request_body,
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;
}
const eval_match = (await response.text()).match(
/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms
)!;
const unpacked = await unpack(eval_match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
@ -181,10 +136,10 @@ export const Luluvdo: Match = {
export const Mixdrop: Match = {
name: 'Mixdrop',
id: 'mixdrop',
domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
domains: ['mixdrop.co', 'mixdrop.to', 'mixdrop.ch', 'mixdrop.bz', 'mixdrop.gl'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
return `https:${url}`;
@ -198,7 +153,7 @@ export const Mp4Upload: Match = {
replace: true,
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
}
@ -228,7 +183,7 @@ export const StreamA2z: Match = {
domains: ['streama2z.com', 'streama2z.xyz'],
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
await Redirect.set(StreamA2z);
return null;
@ -243,7 +198,7 @@ export const Streamtape: Match = {
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
regex: [/id=.*(?=')/gm],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
let i = 0;
while (i < match.length) {
if (match[++i - 1] == match[i]) {
@ -262,7 +217,7 @@ export const Streamzz: Match = {
domains: ['streamzz.to', 'streamz.ws'],
regex: [/(?<=\|)\w{2,}/gm],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
return `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
match.sort((a, b) => b.length - a.length)[0]
}.dll`;
@ -272,10 +227,10 @@ export const Streamzz: Match = {
export const SuperVideo: Match = {
name: 'Supervideo',
id: 'supervideo',
domains: ['supervideo.cc', 'supervideo.tv'],
domains: ['supervideo.tv'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
@ -287,7 +242,7 @@ export const Upstream: Match = {
domains: ['upstream.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
@ -300,7 +255,7 @@ export const Vidmoly: Match = {
regex: [/(?<=file:").+\.m3u8/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
@ -312,7 +267,7 @@ export const Vidoza: Match = {
regex: [/(?<=src:\s?").+?(?=")/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
@ -320,68 +275,11 @@ export const Vidoza: Match = {
export const Voe: Match = {
name: 'Voe',
id: 'voe',
domains: ['voe.sx'],
regex: [
// voe.sx
/(?<=window\.location\.href\s=\s')\S*(?=')/gm,
// whatever site voe.sx redirects to
/(?<=<script type="application\/json">).*(?=<\/script>)/m
],
domains: ['voe.sx', 'maxfinishseveral.com'],
regex: [/(?<='hls':\s*')\S*(?=')/gm],
match: async function (match: RegExpMatchArray) {
if (window.location.host === 'voe.sx') {
const redirectUrl = new URL(match[0]);
await TmpHost.set(redirectUrl.host, Voe);
return null;
} else {
let json = match[0];
json = JSON.parse(json);
let deobfuscated = json[0];
deobfuscated = this.rot13(deobfuscated);
deobfuscated = this.removeSpecialSequences(deobfuscated);
deobfuscated = atob(deobfuscated);
deobfuscated = this.shiftString(deobfuscated);
deobfuscated = deobfuscated.split('').reverse().join('');
deobfuscated = atob(deobfuscated);
const payload = JSON.parse(deobfuscated);
return payload['source'];
}
},
rot13: function (encrypted: string) {
let decrypted = '';
for (let i = 0; i < encrypted.length; i++) {
let char = encrypted.charCodeAt(i);
if (char >= 65 && char <= 90) {
char = ((char - 65 + 13) % 26) + 65;
} else if (char >= 97 && char <= 122) {
char = ((char - 97 + 13) % 26) + 97;
}
decrypted += String.fromCharCode(char);
}
return decrypted;
},
removeSpecialSequences: function (input: string) {
return input
.replaceAll(/@\$/g, '')
.replaceAll(/\^\^/g, '')
.replaceAll(/~@/g, '')
.replaceAll(/%\?/g, '')
.replaceAll(/\*~/g, '')
.replaceAll(/!!/g, '')
.replaceAll(/#&/g, '');
},
shiftString: function (input: string) {
let shifted = '';
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
const shiftedChar = char - 3;
shifted += String.fromCharCode(shiftedChar);
}
return shifted;
match: async (match: RegExpMatchArray) => {
return atob(match[0]);
}
};
@ -391,7 +289,7 @@ export const Vupload: Match = {
domains: ['vupload.com'],
regex: [/(?<=src:\s?").+?(?=")/gm],
match: async function (match: RegExpMatchArray) {
match: async (match: RegExpMatchArray) => {
return match[0];
}
};
@ -402,7 +300,6 @@ export const matches = {
[Filemoon.id]: Filemoon,
[GoodStream.id]: GoodStream,
[Kwik.id]: Kwik,
[LoadX.id]: LoadX,
[Luluvdo.id]: Luluvdo,
[Mixdrop.id]: Mixdrop,
[Mp4Upload.id]: Mp4Upload,

View File

@ -1,4 +1,5 @@
import { matches, type Match } from './match';
import type { Match } from './match';
import { matches } from './match';
export const Hosters = {
getDisabled: async () => {

View File

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

View File

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

View File

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