This commit is contained in:
2025-11-08 18:29:28 +01:00
parent 607326e6d6
commit fdd1ca350e
86 changed files with 4403 additions and 5373 deletions
+36
View File
@@ -0,0 +1,36 @@
export enum MessageType {
RequestActiveMatch,
NotifyActiveMatch
}
export type MessageData<T extends MessageType> = {
[MessageType.RequestActiveMatch]: undefined;
[MessageType.NotifyActiveMatch]: {
id: string;
url: string;
domain: string;
};
}[T];
export async function sendMessage<T extends MessageType>(message: T, data: MessageData<T>) {
await browser.runtime.sendMessage({ type: message, data: data });
}
export async function sendMessageToActiveTab<T extends MessageType>(message: T, data: MessageData<T>) {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
await browser.tabs.sendMessage(tabs[0].id!, { type: message, data: data }).catch(() => {});
}
export function listenMessages(listener: (type: MessageType, data: any) => void): () => void {
const callback = (callbackData: { type: MessageType; data: any }) => {
const { type, data } = callbackData;
listener(type, data);
};
browser.runtime.onMessage.addListener(callback);
return () => {
browser.runtime.onMessage.removeListener(callback);
};
}
+41
View File
@@ -0,0 +1,41 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Doodstream',
id: 'doodstream',
domains: [
'doodstream.com',
'dood.pm',
'dood.ws',
'dood.wf',
'dood.cx',
'dood.sh',
'dood.watch',
'dood.work',
'dood.to',
'dood.so',
'dood.la',
'dood.li',
'dood.re',
'dood.yt',
'doods.pro',
'ds2play.com',
'dooood.com',
'd000d.com'
],
replace: true,
regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
match: async function (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 {
type: HostMatchType.HLS,
url: `${await response.text()}1234567890${match[2]}${Date.now()}`
};
}
} satisfies Host;
+17
View File
@@ -0,0 +1,17 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
export default {
name: 'Dropload',
id: 'dropload',
domains: ['dropload.io'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return {
type: HostMatchType.HLS,
url: unpacked.match(/(?<=file:").*(?=")/)![0]
};
}
} satisfies Host;
+24
View File
@@ -0,0 +1,24 @@
import { HostMatchType, type Host } from '@/lib/host';
import { HostSettings } from '@/lib/settings';
import { unpack } from '@/utils/content';
export default {
name: 'Filemoon',
id: 'filemoon',
domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'],
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 HostSettings.addTemporaryHostDomain(this, new URL(match[0]).host);
return null;
}
const unpacked = await unpack(match[0]);
return {
type: HostMatchType.HLS,
url: unpacked.match(/(?<=file:")\S*(?=")/)![0]
};
}
} satisfies Host;
+15
View File
@@ -0,0 +1,15 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Goodstream',
id: 'goodstream',
domains: ['goodstream.uno'],
regex: [/(?<=file:\s+").*(?=")/g],
match: async function (match: RegExpMatchArray) {
return {
type: HostMatchType.HLS,
url: match[0]
};
}
} satisfies Host;
+78
View File
@@ -0,0 +1,78 @@
import Doodstream from './doodstream';
import Dropload from './dropload';
import Filemoon from './filemoon';
import Goodstream from './goodstream';
import Kwik from './kwik';
import Loadx from './loadx';
import Luluvdo from './luluvdo';
import Mixdrop from './mixdrop';
import Mp4Upload from './mp4upload';
import Newgrounds from './newgrounds';
import StreamA2z from './streama2z';
import Streamtape from './streamtape';
import Streamzz from './streamzz';
import SuperVideo from './supervideo';
import Upstream from './upstream';
import Vidmoly from './vidmoly';
import Vidoza from './vidoza';
import Voe from './voe';
import Vupload from './vupload';
import { HostSettings } from '@/lib/settings';
export enum HostMatchType {
NATIVE = 'native',
HLS = 'hls'
}
export interface HostMatch {
type: HostMatchType;
/** If null, it's interpreted that a url should be present but isn't, probably because the website broke */
url: string | null;
}
export interface Host {
name: string;
id: string;
domains: string[];
replace?: boolean;
regex: RegExp[];
notice?: string;
match(match: RegExpMatchArray): Promise<HostMatch | null>;
}
export const hosts = [
Doodstream,
Dropload,
Filemoon,
Goodstream,
Kwik,
Loadx,
Luluvdo,
Mixdrop,
Mp4Upload,
Newgrounds,
StreamA2z,
Streamtape,
Streamzz,
SuperVideo,
Upstream,
Vidmoly,
Vidoza,
Voe,
Vupload
];
export async function getHost(domain: string) {
if (await HostSettings.getAllHostsDisabled()) return null;
const disabledIds = await HostSettings.getDisabledHosts();
for (const host of hosts) {
if (host.domains.includes(domain)) {
if (!disabledIds.includes(host.id)) return host;
else return null;
}
}
return HostSettings.checkTemporaryHostDomain(domain);
}
+17
View File
@@ -0,0 +1,17 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
export default {
name: 'Kwik',
id: 'kwik',
domains: ['kwik.cx'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return {
type: HostMatchType.HLS,
url: unpacked.match(/(?<=source=').*(?=')/)![0]
};
}
} satisfies Host;
+27
View File
@@ -0,0 +1,27 @@
import { HostMatchType, type Host } from '@/lib/host';
import { lastPathSegment } from '@/utils/extract';
export default {
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'];
return {
type: HostMatchType.HLS,
url: videoSource.replace('\\/', '/')
};
}
} satisfies Host;
+32
View File
@@ -0,0 +1,32 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
import { lastPathSegment } from '@/utils/extract';
export default {
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
});
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
const unpacked = evalMatch ? await unpack(evalMatch[0]) : responseText;
return {
type: HostMatchType.HLS,
url: unpacked.match(/(?<=file:").*(?=")/)![0]
};
}
} satisfies Host;
+18
View File
@@ -0,0 +1,18 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
export default {
name: 'Mixdrop',
id: 'mixdrop',
domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
return {
type: HostMatchType.NATIVE,
url: `https:${url}`
};
}
} satisfies Host;
+18
View File
@@ -0,0 +1,18 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
export default {
name: 'Mp4Upload',
id: 'mp4upload',
domains: ['mp4upload.com'],
replace: true,
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return {
type: HostMatchType.NATIVE,
url: unpacked.match(/(?<=player.src\(").*(?=")/)![0]
};
}
} satisfies Host;
+22
View File
@@ -0,0 +1,22 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Newgrounds',
id: 'newgrounds',
domains: ['newgrounds.com'],
regex: [/.*/gm],
match: async () => {
const id = window.location.pathname.split('/').slice(-1)[0];
const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const json = await response.json();
return {
type: HostMatchType.HLS,
url: decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src'])
};
}
} satisfies Host;
+20
View File
@@ -0,0 +1,20 @@
import { HostMatchType, type Host } from '@/lib/host';
import { HostSettings } from '@/lib/settings';
export default {
name: 'Stream2Az',
id: 'stream2az',
domains: ['streama2z.com', 'streama2z.xyz'],
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
match: async function (match: RegExpMatchArray) {
if (this.domains.indexOf(window.location.hostname) !== -1) {
await HostSettings.addTemporaryHostDomain(this, window.location.hostname);
return null;
}
return {
type: HostMatchType.HLS,
url: match[0]
};
}
} satisfies Host;
+26
View File
@@ -0,0 +1,26 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Streamtape',
id: 'streamtape',
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
regex: [/id=.*(?=')/gm],
match: async function (match: RegExpMatchArray) {
let i = 0;
while (i < match.length) {
if (match[++i - 1] == match[i]) {
return {
type: HostMatchType.HLS,
url: `https://streamtape.com/get_video?${match[i]}`
};
}
}
return {
type: HostMatchType.HLS,
// use the old method as fallback
url: `https://streamtape.com/get_video?${match.reverse()[0]}`
};
}
} satisfies Host;
+17
View File
@@ -0,0 +1,17 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Streamzz',
id: 'streamzz',
domains: ['streamzz.to', 'streamz.ws'],
regex: [/(?<=\|)\w{2,}/gm],
match: async function (match: RegExpMatchArray) {
return {
type: HostMatchType.HLS,
url: `https://get.${location.hostname.split('.')[0]}.tw/getlink-${
match.sort((a, b) => b.length - a.length)[0]
}.dll`
};
}
} satisfies Host;
+17
View File
@@ -0,0 +1,17 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
export default {
name: 'Supervideo',
id: 'supervideo',
domains: ['supervideo.cc', 'supervideo.tv'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return {
type: HostMatchType.HLS,
url: unpacked.match(/(?<=file:").*(?=")/)![0]
};
}
} satisfies Host;
+17
View File
@@ -0,0 +1,17 @@
import { HostMatchType, type Host } from '@/lib/host';
import { unpack } from '@/utils/content';
export default {
name: 'Upstream',
id: 'upstream',
domains: ['upstream.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return {
type: HostMatchType.HLS,
url: unpacked.match(/(?<=file:").*(?=")/)![0]
};
}
} satisfies Host;
+16
View File
@@ -0,0 +1,16 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Vidmoly',
id: 'vidmoly',
domains: ['vidmoly.me', 'vidmoly.to'],
regex: [/(?<=file:").+\.m3u8/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
return {
type: HostMatchType.HLS,
url: match[0]
};
}
} satisfies Host;
+16
View File
@@ -0,0 +1,16 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Vidoza',
id: 'vidoza',
domains: ['vidoza.net', 'videzz.net'],
regex: [/(?<=src:\s?").+?(?=")/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
return {
type: HostMatchType.HLS,
url: match[0]
};
}
} satisfies Host;
+74
View File
@@ -0,0 +1,74 @@
import { HostMatchType, type Host } from '@/lib/host';
import { HostSettings } from '@/lib/settings';
function rot13(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;
}
function removeSpecialSequences(input: string) {
return input
.replaceAll(/@\$/g, '')
.replaceAll(/\^\^/g, '')
.replaceAll(/~@/g, '')
.replaceAll(/%\?/g, '')
.replaceAll(/\*~/g, '')
.replaceAll(/!!/g, '')
.replaceAll(/#&/g, '');
}
function shiftString(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;
}
export default {
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
],
match: async function (match: RegExpMatchArray) {
if (window.location.host === 'voe.sx') {
await HostSettings.addTemporaryHostDomain(this, new URL(match[0]).host);
return null;
} else {
let json = match[0];
json = JSON.parse(json);
let deobfuscated = json[0];
deobfuscated = rot13(deobfuscated);
deobfuscated = removeSpecialSequences(deobfuscated);
deobfuscated = atob(deobfuscated);
deobfuscated = shiftString(deobfuscated);
deobfuscated = deobfuscated.split('').reverse().join('');
deobfuscated = atob(deobfuscated);
const payload = JSON.parse(deobfuscated);
return {
type: HostMatchType.HLS,
url: payload['source']
};
}
}
} satisfies Host;
+15
View File
@@ -0,0 +1,15 @@
import { HostMatchType, type Host } from '@/lib/host';
export default {
name: 'Vupload',
id: 'vupload',
domains: ['vupload.com'],
regex: [/(?<=src:\s?").+?(?=")/gm],
match: async function (match: RegExpMatchArray) {
return {
type: HostMatchType.HLS,
url: match[0]
};
}
} satisfies Host;
-441
View File
@@ -1,441 +0,0 @@
import { Hosters, Redirect, TmpHost } from './settings';
import { lastPathSegment } from './util/extract';
import { unpack } from './util/userspace';
export interface Match {
name: string;
id: string;
domains: string[];
replace?: boolean;
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'
}
export const Doodstream: Match = {
name: 'Doodstream',
id: 'doodstream',
domains: [
'doodstream.com',
'dood.pm',
'dood.ws',
'dood.wf',
'dood.cx',
'dood.sh',
'dood.watch',
'dood.work',
'dood.to',
'dood.so',
'dood.la',
'dood.li',
'dood.re',
'dood.yt',
'doods.pro',
'ds2play.com',
'dooood.com',
'd000d.com'
],
replace: true,
regex: [/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s],
match: async function (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',
domains: ['dropload.io'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Filemoon: Match = {
name: 'Filemoon',
id: 'filemoon',
domains: ['filemoon.sx', 'filemoon.to', 'filemoon.in'],
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();
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:")\S*(?=")/)![0];
}
};
export const GoodStream: Match = {
name: 'Goodstream',
id: 'goodstream',
domains: ['goodstream.uno'],
regex: [/(?<=file:\s+").*(?=")/g],
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const Kwik: Match = {
name: 'Kwik',
id: 'kwik',
domains: ['kwik.cx'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (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',
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 = {
name: 'Mixdrop',
id: 'mixdrop',
domains: ['mixdrop.bz', 'mixdrop.ch', 'mixdrop.co', 'mixdrop.gl', 'mixdrop.my', 'mixdrop.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
const url = unpacked.match(/(?<=MDCore.wurl=").*(?=")/)![0];
return `https:${url}`;
}
};
export const Mp4Upload: Match = {
name: 'Mp4Upload',
id: 'mp4upload',
domains: ['mp4upload.com'],
replace: true,
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=player.src\(").*(?=")/)![0];
}
};
export const Newgrounds: Match = {
name: 'Newgrounds',
id: 'newgrounds',
domains: ['newgrounds.com'],
regex: [/.*/gm],
match: async () => {
const id = window.location.pathname.split('/').slice(-1)[0];
const response = await fetch(`https://www.newgrounds.com/portal/video/${id}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const json = await response.json();
return decodeURI(json['sources'][Object.keys(json['sources'])[0]][0]['src']);
}
};
export const StreamA2z: Match = {
name: 'Stream2Az',
id: 'stream2az',
domains: ['streama2z.com', 'streama2z.xyz'],
regex: [/https?:\/\/\S*m3u8.+(?=['"])/gm],
match: async function (match: RegExpMatchArray) {
if (StreamA2z.domains.indexOf(window.location.hostname) !== -1) {
await Redirect.set(StreamA2z);
return null;
}
return match[0];
}
};
export const Streamtape: Match = {
name: 'Streamtape',
id: 'streamtape',
domains: ['streamtape.com', 'streamtape.net', 'shavetape.cash'],
regex: [/id=.*(?=')/gm],
match: async function (match: RegExpMatchArray) {
let i = 0;
while (i < match.length) {
if (match[++i - 1] == match[i]) {
return `https://streamtape.com/get_video?${match[i]}`;
}
}
// use the old method as fallback
return `https://streamtape.com/get_video?${match.reverse()[0]}`;
}
};
export const Streamzz: Match = {
name: 'Streamzz',
id: 'streamzz',
domains: ['streamzz.to', 'streamz.ws'],
regex: [/(?<=\|)\w{2,}/gm],
match: async function (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',
domains: ['supervideo.cc', 'supervideo.tv'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Upstream: Match = {
name: 'Upstream',
id: 'upstream',
domains: ['upstream.to'],
regex: [/eval\(function\(p,a,c,k,e,d\).*?(?=<\/script>)/gms],
match: async function (match: RegExpMatchArray) {
const unpacked = await unpack(match[0]);
return unpacked.match(/(?<=file:").*(?=")/)![0];
}
};
export const Vidmoly: Match = {
name: 'Vidmoly',
id: 'vidmoly',
domains: ['vidmoly.me', 'vidmoly.to'],
regex: [/(?<=file:").+\.m3u8/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const Vidoza: Match = {
name: 'Vidoza',
id: 'vidoza',
domains: ['vidoza.net', 'videzz.net'],
regex: [/(?<=src:\s?").+?(?=")/gm],
replace: true,
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
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
],
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;
}
};
export const Vupload: Match = {
name: 'Vupload',
id: 'vupload',
domains: ['vupload.com'],
regex: [/(?<=src:\s?").+?(?=")/gm],
match: async function (match: RegExpMatchArray) {
return match[0];
}
};
export const matches = {
[Doodstream.id]: Doodstream,
[DropLoad.id]: DropLoad,
[Filemoon.id]: Filemoon,
[GoodStream.id]: GoodStream,
[Kwik.id]: Kwik,
[LoadX.id]: LoadX,
[Luluvdo.id]: Luluvdo,
[Mixdrop.id]: Mixdrop,
[Mp4Upload.id]: Mp4Upload,
[Newgrounds.id]: Newgrounds,
[StreamA2z.id]: StreamA2z,
[Streamtape.id]: Streamtape,
[Streamzz.id]: Streamzz,
[SuperVideo.id]: SuperVideo,
[Upstream.id]: Upstream,
[Vidmoly.id]: Vidmoly,
[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;
}
}
const tmpHost = await TmpHost.get();
if (tmpHost && tmpHost[0] === domain) {
return tmpHost[1];
}
return null;
}
+66 -90
View File
@@ -1,112 +1,88 @@
import { matches, type Match } from './match';
import { storage } from '#imports';
import { hosts, type Host } from '@/lib/host';
export const Hosters = {
getDisabled: async () => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
return disabled.map((id) => matches[id]).filter((m) => m !== undefined);
},
disable: async (match: Match) => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
const index = disabled.indexOf(match.id);
export class HostSettings {
/* disabled hosts */
private static disabledHosts = storage.defineItem<string[]>('local:disabledHosts', { fallback: [] });
private static allHostsDisabled = storage.defineItem<boolean>('local:allHostsDisabled', { fallback: false });
static async addDisabledHost(host: Host) {
const ids = await this.disabledHosts.getValue();
const index = ids.indexOf(host.id);
if (index === -1) {
disabled.push(match.id);
await storageSet('hosters.disabled', disabled);
ids.push(host.id);
await this.disabledHosts.setValue(ids);
}
},
enable: async (match: Match) => {
const disabled = (await storageGet('hosters.disabled', [])) as string[];
const index = disabled.indexOf(match.id);
}
static async removeDisabledHost(host: Host) {
const ids = await this.disabledHosts.getValue();
const index = ids.indexOf(host.id);
if (index !== -1) {
disabled.splice(index, 1);
await storageSet('hosters.disabled', disabled);
ids.splice(index, 1);
await this.disabledHosts.setValue(ids);
}
},
getAllDisabled: async () => {
return await storageGet<boolean>('hosters.allDisabled', false);
},
setAll: async (enable: boolean) => {
await storageSet('hosters.allDisabled', !enable);
}
};
export const Redirect = {
get: async (): Promise<Match | null> => {
return matches[(await storageGet('redirect')) as string] || null;
},
set: async (match: Match) => {
await storageSet('redirect', match.id);
},
delete: async () => {
await storageDelete('redirect');
static async getDisabledHosts() {
return await this.disabledHosts.getValue();
}
};
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');
static async getAllHostsDisabled() {
return await this.allHostsDisabled.getValue();
}
};
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}`);
static async setAllHostsDisabled(disabled: boolean) {
await this.allHostsDisabled.setValue(disabled);
}
};
export const Other = {
getFf2mpv: async () => {
return await storageGet('other.ff2mpv', false);
},
setFf2mpv: async (enable: boolean) => {
await storageSet('other.ff2mpv', enable);
}
};
async function storageGet<T>(key: string, defaultValue?: T): Promise<T | undefined> {
let resolve: (value: T | undefined) => void;
const promise = new Promise<T | undefined>((r) => (resolve = r));
chrome.storage.local.get(key, (entry) => {
const value = entry[key];
resolve(value === undefined ? defaultValue : value);
/* tmp */
private static temporaryHostDomain = storage.defineItem<Record<string, string>>('local:temporaryHostDomain', {
fallback: {}
});
return promise;
static async addTemporaryHostDomain(host: Host, domain: string) {
const temporaryHostDomains = await this.temporaryHostDomain.getValue();
temporaryHostDomains[domain] = host.id;
await this.temporaryHostDomain.setValue(temporaryHostDomains);
console.log(await this.temporaryHostDomain.getValue());
}
static async checkTemporaryHostDomain(domain: string) {
const temporaryHostDomains = await this.temporaryHostDomain.getValue();
const hostId = temporaryHostDomains[domain];
return hostId ? (hosts.find((host) => host.id === hostId) ?? null) : null;
}
}
async function storageSet<T>(key: string, value: T) {
let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r));
export class FF2MPVSettings {
private static ff2mpvEnabled = storage.defineItem<boolean>('local:ff2mpv', { fallback: false });
const obj = {
[key]: value
};
chrome.storage.local.set(obj, () => resolve());
return promise;
static async getEnabled() {
return await this.ff2mpvEnabled.getValue();
}
static async setEnabled(enabled: boolean) {
console.log('set', enabled);
await this.ff2mpvEnabled.setValue(enabled);
}
}
async function storageDelete(key: string) {
let resolve: () => void;
const promise = new Promise<void>((r) => (resolve = r));
export class UrlReferer {
private static temporaryUrlReferer = storage.defineItem<Record<string, string>>('local:temporaryUrlReferer', {
fallback: {}
});
chrome.storage.local.remove(key, () => resolve());
static async addTemporary(hostname: string, referer: string) {
const tmpUrlReferer = await this.temporaryUrlReferer.getValue();
return promise;
tmpUrlReferer[hostname] = referer;
await this.temporaryUrlReferer.setValue(tmpUrlReferer);
}
static async get(hostname: string) {
const tmpUrlReferer = await this.temporaryUrlReferer.getValue();
return tmpUrlReferer[hostname] ?? null;
}
}
-6
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);
}
-92
View File
@@ -1,92 +0,0 @@
// Adapted from http://matthewfl.com/unPacker.html by matthew@matthewfl.com
export async function unpack(packed: string): Promise<string> {
const context = `
{
eval: function (c) {
packed = c;
},
window: {},
document: {}
}
`;
const toExecute = `
function() {
let packed = "";
with(${context}) {
${packed}
};
return packed;
}
`;
const res = (await runInPageContext(toExecute)) as string;
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 | null> {
// test that we are running with the allow-scripts permission
try {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
window.sessionStorage;
} catch {
return null;
}
// returned value container
const resultMessageId = crypto.randomUUID();
// prepare script container
const scriptElm = document.createElement('script');
scriptElm.setAttribute('type', 'application/javascript');
// inject the script
scriptElm.textContent = `
(
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, '*');
}
)();
`;
// 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;
}