From a9208329459b5b0be07f860e99ce212350083d71 Mon Sep 17 00:00:00 2001 From: sdaqo Date: Fri, 28 Apr 2023 18:19:01 +0200 Subject: [PATCH] Add Kwik and improve reliabilty for several other hosters (#12) * add Kwik, use unpacker to improve reliabilty * use packer for filemoon * use packer for upstream * Update README.md * Revert "Update README.md" This reverts commit affb6000968beb798423a5403508215b7f6e4670. * add kwik to hosters in readme * unpack without using eval --- README.md | 1 + src/background.ts | 1 + src/index.ts | 2 +- src/manifest.json | 2 +- src/match/matches.ts | 73 +++++++++++++++++++++++--------------- src/match/unpack.ts | 84 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 src/match/unpack.ts diff --git a/README.md b/README.md index ad6ce73..9ce7eaa 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla | [vidstream.pro](https://vidstream.pro) | ❌ | Reverse engineering the site costs too much time ([#5](https://github.com/ByteDream/stream-bypass/issues/5)) | | [voe.sx](https://voe.sx) | ✔ | | | [vupload.com](https://vupload.com) | ✔ | | +| [kwik.cx](https://kwik.cx) | ✔ | | - ✔️: 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. diff --git a/src/background.ts b/src/background.ts index 7858908..78e5145 100644 --- a/src/background.ts +++ b/src/background.ts @@ -2,6 +2,7 @@ import {getMatch} from "./match/match"; import {storageDelete, storageGet, storageSet} from "./store/store"; import {Match} from "./match/matches"; + chrome.webRequest.onBeforeRedirect.addListener(async details => { // check if redirects origins from a previous redirect if (await storageGet('redirect') === undefined) { diff --git a/src/index.ts b/src/index.ts index d968097..ab2e63f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,7 @@ async function main() { const url = await match.match(re) - if (match.replace && !url.endsWith('.m3u8')) { + if (match.replace && !url.includes('.m3u8')) { const player = document.createElement('video') player.style.width = '100%' player.style.height = '100%' diff --git a/src/manifest.json b/src/manifest.json index dc6d23d..0b4b949 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -3,7 +3,7 @@ "name": "Stream Bypass", "author": "ByteDream", "description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.", - "version": "2.1.6", + "version": "2.1.7", "homepage_url": "https://github.com/ByteDream/stream-bypass", "browser_specific_settings": { "gecko": { diff --git a/src/match/matches.ts b/src/match/matches.ts index 32967f5..f809627 100644 --- a/src/match/matches.ts +++ b/src/match/matches.ts @@ -1,3 +1,5 @@ +import {unPack} from "./unpack"; + export enum Reliability { HIGH, NORMAL, @@ -55,57 +57,51 @@ class Filemoon implements Match { domains = [ 'filemoon.sx' ] - regex = new RegExp(/(?<=\|)\w{2,}/gm) + regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms) async match(match: RegExpMatchArray): Promise { - const start_idx = match.indexOf('moon') - - const prefix = `${match[start_idx]}-${match[start_idx-1]}-${match[start_idx-2]}-${match[start_idx-3]}` - const time = match.find(m => m.length === 10 && !isNaN(parseInt(m))) - const offset = !isNaN(parseInt(match[start_idx-12])) && parseInt(match[start_idx-12]).toString().length == match[start_idx-12].length ? 0 : -1 - - return `https://${prefix}.filemoon.${match[start_idx-4]}/${match[start_idx-5]}/${match[start_idx-6]}/${match[start_idx-7]}/${match[start_idx-8]}/master.m3u8?t=${match[start_idx-11]}${offset != 0 ? `-${match[start_idx-12]}` : ''}&s=${time}&e=${match[start_idx + offset - 12]}&sp=${match[start_idx + offset - 18]}` + let unpacked = await unPack(match[0]) + let url = unpacked.match(/(?<=file:").*(?=")/)[0] + console.log(url) + return url } } class Mixdrop implements Match { name = 'Mixdrop' id = 'mixdrop' - reliability = Reliability.NORMAL + reliability = Reliability.HIGH domains = [ 'mixdrop.co', 'mixdrop.to', 'mixdrop.ch', - 'mixdrop.bz' + 'mixdrop.bz', + 'mixdrop.gl' ] - regex = new RegExp(/(?<=\|)\w{2,}/gm) + regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms) async match(match: RegExpMatchArray): Promise { - const prefix = /(?<=\/\/)[a|s](?=-)/.exec(document.body.innerHTML)[0] - const subdomain = match[1].length < match[2].length ? match[1] : match[2] - const domain = match.slice().sort((a, b) => b.length - a.length).find(m => /^[a-z]+$/.test(m)) - const id = match[1].length > match[2].length ? match[1] : match[2] - const tld = match.find(m => ['net', 'io', 'to', 'sx', 'com'].indexOf(m) !== -1) - const s = match.slice().sort((a, b) => b.length - a.length).slice(1)[0] - const e_t = match.find(m => m.length === 10 && !isNaN(parseInt(m))) - - return `https://${prefix}-${subdomain}.${domain}.${tld}/v/${id}.mp4?s=${s}&e=${e_t}&_t=${e_t}` + 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.NORMAL + reliability = Reliability.HIGH domains = [ 'mp4upload.com' ] replace = true - regex = new RegExp(/(?<=\|)\w{2,}/gm) + regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms) async match(match: RegExpMatchArray): Promise { - let id = match.slice().reduce((a, b) => a.length >= b.length ? a : b) - return `https://www4.mp4upload.com:282/d/${id}/video.mp4` + let unpacked = await unPack(match[0]) + console.log(unpacked) + let url = unpacked.match(/(?<=player.src\(").*(?=")/)[0] + return url } } @@ -162,14 +158,16 @@ class Streamzz implements Match { class Upstream implements Match { name = 'Upstream' id = 'upstream' - reliability = Reliability.NORMAL + reliability = Reliability.HIGH domains = [ 'upstream.to' ] - regex = new RegExp(/(?<=\|)\w{2,}/gm) + regex = new RegExp(/eval\(function\(p,a,c,k,e,d\).*?(?=\<\/script\>)/gms) async match(match: RegExpMatchArray): Promise { - return `https://${match[49]}.upstreamcdn.co/hls/${match[148]}/master.m3u8` + let unpacked = await unPack(match[0]) + let url = unpacked.match(/(?<=file:").*(?=")/)[0] + return url } } @@ -215,6 +213,24 @@ class Vupload implements Match { } } +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 { + console.log(match[0]); + let unpacked = await unPack(match[0]) + let url = unpacked.match(/(?<=source=').*(?=')/)[0] + console.log(url) + return url + } +} + export const matches = [ new Doodstream(), new Filemoon(), @@ -226,5 +242,6 @@ export const matches = [ new Upstream(), new Vidoza(), new Voe(), - new Vupload() + new Vupload(), + new Kwik() ] diff --git a/src/match/unpack.ts b/src/match/unpack.ts new file mode 100644 index 0000000..49139a3 --- /dev/null +++ b/src/match/unpack.ts @@ -0,0 +1,84 @@ +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 => { + // 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"); +}