Compare commits

..

12 commits

Author SHA1 Message Date
WebStreamr
720c9bb651
docs: mention PixelServer / pixeldrain limits 2026-03-27 22:10:38 +00:00
webstreamr
f05cf5040c
chore(release): release v0.69.1 (#801) 2026-03-27 22:05:18 +00:00
WebStreamr
65191cfc8d
fix(source): exclude ad videos in HDHub4u 2026-03-27 22:03:14 +00:00
WebStreamr
bd2dec0392
fix(source): remove stray console.log 2026-03-27 21:59:07 +00:00
webstreamr
5f9fbbb30d
chore(release): release v0.69.0 (#799) 2026-03-27 22:48:26 +01:00
WebStreamr
43f04455f1
feat(source): add HDHub4u 2026-03-27 21:42:10 +00:00
WebStreamr
43637a2ff4
refactor(source): extract hd hub link redirect resolver 2026-03-27 20:17:03 +00:00
WebStreamr
b87bf62362
chore: remove duplicate URLs 2026-03-27 19:40:27 +00:00
WebStreamr
89e57d48d9
chore(extractor): detect languages from HubCloud title 2026-03-27 19:30:47 +00:00
renovate[bot]
ad47aecea3
chore(deps): update dependency axios to v1.14.0 (#800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 19:28:52 +00:00
WebStreamr
f192fd0b75
chore(extractor): detect resolution from HubCloud title 2026-03-27 19:26:31 +00:00
WebStreamr
557efd2079
fix(extractor): don't fail for HubDrive if HubCloud link is missing 2026-03-27 18:02:48 +00:00
50 changed files with 30077 additions and 161 deletions

View file

@ -1,3 +1,3 @@
{ {
".": "0.68.0" ".": "0.69.1"
} }

View file

@ -1,5 +1,38 @@
# Changelog # Changelog
## [0.69.1](https://github.com/webstreamr/webstreamr/compare/v0.69.0...v0.69.1) (2026-03-27)
### Bug Fixes
* **source:** exclude ad videos in HDHub4u ([65191cf](https://github.com/webstreamr/webstreamr/commit/65191cfc8dad8f58b44e2c355b9adc600b99b2bf))
* **source:** remove stray console.log ([bd2dec0](https://github.com/webstreamr/webstreamr/commit/bd2dec03925d8b7760044dbfaaaec00b48f7cb66))
## [0.69.0](https://github.com/webstreamr/webstreamr/compare/v0.68.0...v0.69.0) (2026-03-27)
### Miscellaneous Chores
* **deps:** update dependency axios to v1.14.0 ([#800](https://github.com/webstreamr/webstreamr/issues/800)) ([ad47aec](https://github.com/webstreamr/webstreamr/commit/ad47aecea3f3d34700add442e023bd54f73451c8))
* **extractor:** detect languages from HubCloud title ([89e57d4](https://github.com/webstreamr/webstreamr/commit/89e57d48d95a6f8dd6446048ae72de51dd481f37))
* **extractor:** detect resolution from HubCloud title ([f192fd0](https://github.com/webstreamr/webstreamr/commit/f192fd0b7523094746148989e0cf06717db8152e))
* remove duplicate URLs ([b87bf62](https://github.com/webstreamr/webstreamr/commit/b87bf62362f3cd6b3ea055ecdaec44511bf2ec84))
### Features
* **source:** add HDHub4u ([43f0445](https://github.com/webstreamr/webstreamr/commit/43f04455f17063e459df7e7a34af44de36afd6fc))
### Bug Fixes
* **extractor:** don't fail for HubDrive if HubCloud link is missing ([557efd2](https://github.com/webstreamr/webstreamr/commit/557efd207945ea7ada0bcaf475b0b9b9d60d8f5b))
### Code Refactoring
* **source:** extract hd hub link redirect resolver ([43637a2](https://github.com/webstreamr/webstreamr/commit/43637a2ff4280db208ec9c94c0a5d52a49dc6672))
## [0.68.0](https://github.com/webstreamr/webstreamr/compare/v0.67.0...v0.68.0) (2026-03-26) ## [0.68.0](https://github.com/webstreamr/webstreamr/compare/v0.67.0...v0.68.0) (2026-03-26)

View file

@ -16,6 +16,7 @@ A public instance is available at https://webstreamr.hayd.uk. Hosting infrastruc
## Known issues / limitations ## Known issues / limitations
- PixelServer / pixeldrain has a daily limit of 6 GB per IP: https://pixeldrain.dev
- Dropload and SuperVideo on Android do not work because Stremio does not use the `Referer` header properly via HLS playlists: https://github.com/Stremio/stremio-bugs/issues/2389, maybe https://github.com/Stremio/stremio-bugs/issues/1579 - Dropload and SuperVideo on Android do not work because Stremio does not use the `Referer` header properly via HLS playlists: https://github.com/Stremio/stremio-bugs/issues/2389, maybe https://github.com/Stremio/stremio-bugs/issues/1579
- MediaFlow proxy has to be used in an inefficient way because Stremio on Android or its players cannot deal with HLS playlist with redirects: https://github.com/Stremio/stremio-bugs/issues/1574 - MediaFlow proxy has to be used in an inefficient way because Stremio on Android or its players cannot deal with HLS playlist with redirects: https://github.com/Stremio/stremio-bugs/issues/1574
- FlareSolverr cookies cannot be used because Cloudflare does techniques like TLS fingerprinting most likely. But FlareSolverr uses a session per host and should be quick. - FlareSolverr cookies cannot be used because Cloudflare does techniques like TLS fingerprinting most likely. But FlareSolverr uses a session per host and should be quick.

23
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "webstreamr", "name": "webstreamr",
"version": "0.68.0", "version": "0.69.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "webstreamr", "name": "webstreamr",
"version": "0.68.0", "version": "0.69.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@keyv/sqlite": "^4.0.5", "@keyv/sqlite": "^4.0.5",
@ -2961,14 +2961,14 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.6", "version": "1.14.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.11", "follow-redirects": "^1.15.11",
"form-data": "^4.0.5", "form-data": "^4.0.5",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^2.1.0"
} }
}, },
"node_modules/axios-cache-interceptor": { "node_modules/axios-cache-interceptor": {
@ -9197,10 +9197,13 @@
} }
}, },
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT" "license": "MIT",
"engines": {
"node": ">=10"
}
}, },
"node_modules/pstree.remy": { "node_modules/pstree.remy": {
"version": "1.1.8", "version": "1.1.8",

View file

@ -1,7 +1,7 @@
{ {
"name": "webstreamr", "name": "webstreamr",
"description": "Provides HTTP URLs from streaming websites.", "description": "Provides HTTP URLs from streaming websites.",
"version": "0.68.0", "version": "0.69.1",
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
"analyse": "tsc --noEmit --project ./tsconfig.dev.json", "analyse": "tsc --noEmit --project ./tsconfig.dev.json",

View file

@ -1,6 +1,7 @@
import bytes from 'bytes'; import bytes from 'bytes';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { Context, Format, InternalUrlResult, Meta } from '../types'; import { Context, Format, InternalUrlResult, Meta } from '../types';
import { findCountryCodes, findHeight } from '../utils';
import { Extractor } from './Extractor'; import { Extractor } from './Extractor';
export class HubCloud extends Extractor { export class HubCloud extends Extractor {
@ -25,6 +26,10 @@ export class HubCloud extends Extractor {
const linksHtml = await this.fetcher.text(ctx, new URL(redirectUrlMatch[1] as string), { headers: { Referer: url.href } }); const linksHtml = await this.fetcher.text(ctx, new URL(redirectUrlMatch[1] as string), { headers: { Referer: url.href } });
const $ = cheerio.load(linksHtml); const $ = cheerio.load(linksHtml);
const title = $('title').text().trim();
const countryCodes = [...new Set([...meta.countryCodes ?? [], ...findCountryCodes(title)])];
const height = meta.height ?? findHeight(title);
return Promise.all([ return Promise.all([
...$('a') ...$('a')
.filter((_i, el) => { .filter((_i, el) => {
@ -42,7 +47,9 @@ export class HubCloud extends Extractor {
...meta, ...meta,
bytes: bytes.parse($('#size').text()) as number, bytes: bytes.parse($('#size').text()) as number,
extractorId: `${this.id}_fsl`, extractorId: `${this.id}_fsl`,
title: $('title').text().trim(), countryCodes,
height,
title,
}, },
}; };
}).toArray(), }).toArray(),
@ -62,7 +69,9 @@ export class HubCloud extends Extractor {
...meta, ...meta,
bytes: bytes.parse($('#size').text()) as number, bytes: bytes.parse($('#size').text()) as number,
extractorId: `${this.id}_fslv2`, extractorId: `${this.id}_fslv2`,
title: $('title').text().trim(), countryCodes,
height,
title,
}, },
}; };
}).toArray(), }).toArray(),
@ -81,7 +90,9 @@ export class HubCloud extends Extractor {
...meta, ...meta,
bytes: bytes.parse($('#size').text()) as number, bytes: bytes.parse($('#size').text()) as number,
extractorId: `${this.id}_pixelserver`, extractorId: `${this.id}_pixelserver`,
title: $('title').text().trim(), countryCodes,
height,
title,
}, },
requestHeaders: { Referer: userUrl.href }, requestHeaders: { Referer: userUrl.href },
}; };

View file

@ -22,4 +22,8 @@ describe('HubDrive', () => {
test('handle avatar', async () => { test('handle avatar', async () => {
expect(await extractorRegistry.handle(ctx, new URL('https://hubdrive.space/file/7283903021'))).toMatchSnapshot(); expect(await extractorRegistry.handle(ctx, new URL('https://hubdrive.space/file/7283903021'))).toMatchSnapshot();
}); });
test('handle missing hubcloud', async () => {
expect(await extractorRegistry.handle(ctx, new URL('https://hubdrive.space/file/2243124026'))).toMatchSnapshot();
});
}); });

View file

@ -31,8 +31,10 @@ export class HubDrive extends Extractor {
const html = await this.fetcher.text(ctx, url, { headers }); const html = await this.fetcher.text(ctx, url, { headers });
const $ = cheerio.load(html); const $ = cheerio.load(html);
const hubCloudUrl = new URL($('a:contains("HubCloud")').attr('href') as string); const hubCloudUrl = $('a:contains("HubCloud")')
.map((_i, el) => new URL($(el).attr('href') as string))
.get(0);
return this.hubCloud.extract(ctx, hubCloudUrl, meta); return hubCloudUrl ? this.hubCloud.extract(ctx, hubCloudUrl, meta) : [];
}; };
} }

View file

@ -0,0 +1,512 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name='admaven-placement' content=BrHCGpjC9>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex,nofollow">
<meta name="referrer" content="no-referrer"/>
<meta name="referrer" content="none"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta property="og:image" content="/assets/img/favicon.png">
<meta property="og:type" content="website"/>
<meta name="author" content="HubDrive">
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex,nofollow">
<title>HubDrive | Back.to.the.Future.Part.III.1990.1080p.BluRay.Hindi.English.DD.5.1.x264.ESubs.Untouched.mkv</title>
<!-- Custom fonts for this template-->
<script src="https://cdn.jsdelivr.net/clipboard.js/1.5.12/clipboard.min.js"></script>
<script src="https://unpkg.com/sweetalert@2.1.2/dist/sweetalert.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script async="async" type="text/javascript" src="/assets/js/neodrivev2.5.min.js?v=3"></script>
<script src="https://use.fontawesome.com/746f656c7a.js"></script>
<link href="/assets/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link href="/assets/css/sb-admin-2.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
<link rel="icon" href="/assets/img/favicon.png">
<script data-cfasync="false" type="text/javascript" id="AdsCoreLoader100829" src="https://sads.adsboosters.xyz/801bbfc2f5f34af99c45dabeb76aa5ef.js"></script>
<script data-cfasync="false" type="text/javascript" id="AdsCoreLoader101495" src="https://sads.adsboosters.xyz/df182342f761a3795c0c8cfad378c7a4.js"></script>
<div class="clever-core-ads-100829></div>
<div class="clever-core-ads-101495></div>
<style>
h4, .h4 {
font-size: 1.4rem;
}
.table {
margin-bottom: 1rem;
}
#sticky-footer2 {
display: none !important;
}
.Blink {
animation: blinker 0.3s cubic-bezier(.5, 0, 1, 1) infinite alternate;
}
@keyframes blinker {
from { opacity: 1; }
to { opacity: 0; }
}
.sidebar .sidebar-brand {
font-size: .65rem;
}
.sidebar .nav-item .nav-link span {
color: #fff !important;
}
.sidebar .sidebar-brand i {
color: #fff !important;
}
.bg-white {
background-color: #171717!important;
}
.container, .container-fluid {
background-color: transparent !IMPORTANT;
}
.card-header {
background-color: transparent;
}
#wrapper #content-wrapper #content {
background-color: transparent !IMPORTANT;
}
#wrapper #content-wrapper {
background-color: transparent !important;
}
.text-primary {
color: rgb(130, 182, 255) !important;
}
.card-body {
background-color: transparent !IMPORTANT;
}
.card {
background-color: transparent;
border: 2px solid rgb(49, 49, 49);
}
.table {
color: rgb(217, 217, 217);
}
.bg-gradient-primary {
background-image: linear-gradient(180deg,rgb(0, 28, 67)10%,rgb(0, 28, 67) 100%);
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
color: rgb(130, 182, 255);
background-color: rgb(0, 28, 67);
}
footer.sticky-footer {
border: 2px solid rgb(49, 49, 49);
}
.navbar {
border-bottom: 2px solid hsl(213deg 69.03% 35.03% / 68%);
border-radius: 5px;
}
.btn-primary {
background-color: rgb(0 63 151);
font-weight: 600;
}
.text-gray-800 {
color: rgb(130, 182, 255);
}
.card-file{
color: rgb(217, 217, 217);
}
.h5 {
color:rgb(130, 182, 255) !Important;
}
hr {
border: 1px solid rgb(255 255 255 / 23%);
}
.text-gray-400 {
color: rgb(130, 182, 255) !important;
}
.text-gray-500 {
color: rgb(130, 182, 255) !important;
}
.text-gray-600 {
color: rgb(130, 182, 255) !important;
}
.text-gray-700 {
color: rgb(130, 182, 255) !important;
}
.text-gray-800 {
color: rgb(130, 182, 255) !important;
}
.text-gray-900 {
color: rgb(130, 182, 255) !important;
}
.dropdown-menu {
background-color: #171717;
border: 2px solid rgb(49, 49, 49);
}
.form-control {
background-color: rgb(239 239 239);
}
body {
background-color: #171717;
}
.btn-info {
border-radius: 0.25rem !important;
background-color: #1a73e8 !important;
border-color: #1a73e8 !important;
}
.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle {
background-color: #093d94 !important;
}
.btn-info:hover {
background-color: #093d94 !important;
}
</style>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-8QTNRD0R4M"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-8QTNRD0R4M');
</script>
</head>
<body id="page-top">
<!-- Page Wrapper -->
<div id="wrapper">
<!-- Sidebar -->
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion toggled" id="accordionSidebar">
<!-- Sidebar - Brand -->
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="https://hubdrive.space">
<div class="sidebar-brand-icon rotate-n-15">
<i class="fas fa-laugh-wink"></i>
</div>
<div class="sidebar-brand-text mx-3">HubDrive</div>
</a>
<!-- Divider -->
<hr class="sidebar-divider my-0">
<!-- Nav Item - Dashboard -->
<li class="nav-item active">
<a class="nav-link" href="/sign">
<i class="fas fa-fw fa-sign-in-alt"></i>
<span>Login</span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/page/privacy-policy">
<i class="fas fa-fw fa-user-secret"></i>
<span>Privacy Policy</span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/page/terms-conditions">
<i class="fas fa-fw fa-align-justify"></i>
<span>Terms & Conditions</span></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/page/copyright-policy">
<i class="fas fa-fw fa-copyright"></i>
<span>Copyright Policy</span></a>
</li>
<!-- Divider -->
<hr class="sidebar-divider d-none d-md-block">
<!-- Sidebar Toggler (Sidebar) -->
<div class="text-center d-none d-md-inline">
<button class="rounded-circle border-0" id="sidebarToggle"></button>
</div>
</ul>
<!-- End of Sidebar -->
<!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column">
<!-- Main Content -->
<div id="content">
<!-- Topbar -->
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
<i class="fa fa-bars"></i>
</button>
<ul class="navbar-nav ml-auto">
<div style="display: flex; justify-content: center; align-items: center;margin-right: -27rem;">
<a href="/" style="display: flex; align-items: center;text-decoration: none;">
<i class="fab fa-artstation" style="font-size: 50px; color: #5090F0;"></i>
<img class="img-profile" src="https://catimages.org/images/2024/04/23/1b1be1f6932ecf270.png" style="width: 25%; filter: invert(1); margin-left: 5px;">
</a>
</div>
<div class="topbar-divider d-none d-sm-block" style="margin-left: -2px;"></div>
</ul>
</nav> <style>
.btn.btn-primary.btn-user1 {
background-color: #a10dff;
border-color: #ba50fd;
}
.btn-primary.btn-user1:hover {
color: #fff;
background-color: #b542ff;
}
.btn-primary.btn-user1:not(:disabled):not(.disabled):active, .btn-primary.btn-user1:not(:disabled):not(.disabled).active, .show > .btn-primary.btn-user1.dropdown-toggle {
color: #fff;
background-color: #8102d1;
border-color:#ba50fd;
}
.btn-primary.btn-user1:focus, .btn-primary.btn-user1.focus {
box-shadow: 0 0 0 .25rem rgba(151, 51, 215, .5);
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
color: rgb(255 255 255);
background-color: rgb(13 60 125);
border-color: rgb(13 60 125);
}
.nav-tabs {
border-bottom: rgb(49, 49, 49);
}
</style>
<!-- http://caniuse.com/#feat=referrer-policy -->
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex,nofollow">
<meta name="referrer" content="no-referrer" />
<!-- For Edge and Safari -->
<meta name="referrer" content="none" />
<script>(function(s){s.dataset.zone='9824726',s.src='https://llvpn.com/tag.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script>
<div class="container-fluid">
<div id="down-id" hidden="true">2243124026</div>
<!-- Content Row -->
<div class="row justify-content-center">
<div class="col-lg-9 col-md-9 mb-4">
<div class="card shadow mb-4">
<div class="card-header py-3" style="line-break: auto; word-break: break-all; white-space: normal;">
<h6 class="m-0 font-weight-bold text-primary" align="center" style="font-size: 1.2rem;">Back.to.the.Future.Part.III.1990.1080p.BluRay.Hindi.English.DD.5.1.x264.ESubs.Untouched.mkv</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-borderless" id="dataTable" width="100%" cellspacing="0" style="line-break: auto; word-break: initial; white-space: initial;">
<tbody>
<tr style="border-bottom: 2px solid rgb(63 63 63); line-height: 1.8em;">
<td>File Size</td>
<td align="right">3.77 GB</td>
</tr>
<tr style="border-bottom: 2px solid rgb(63 63 63); line-height: 1.8em;">
<td>File Type</td>
<td align="right">video/x-matroska</td>
</tr>
<tr>
<td>File Owner</td>
<td align="right">dhub 2</td>
</tr>
</tbody>
</table>
</div>
<br>
<div class="text" align="center">
<form action="/gsign.php" method="GET">
<input name="r" value="https://hubdrive.space/file/2243124026" type="hidden">
<button class="btn btn-primary btn-user" type="submit"><i class="fa fa-sign-in fa-lg"></i> GDrive [Login]</button>
<!--button class="btn btn-primary btn-user" type="submit"><i class="fa fa-sign-in fa-lg"></i> Download [3.77 GB] [Login]</button-->
</form>
<div>
<button onclick="myDirectDownload()" id="dl-down" type="button" class="btn btn-primary btn-user1" style="margin:10px;"><i class="fas fa-file-download fa-lg"></i> Direct/Instant Download</button>
<div>
<br><br>
</div>
<div class="text" align="left">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" href="#" onclick="copyLink()" role="tab" data-toggle="tab"><i class="fa fa-copy"></i> Copy Link</a>
</li>
</ul>
</div>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade show active" id="link">
<div class="card">
<div class="card-body">
<p id="copyText" style="text-align:left; color:rgb(116 174 255);">https://hubdrive.space/file/2243124026</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
function copyLink() {
var copyText = document.getElementById("copyText");
var textArea = document.createElement("textarea");
textArea.value = copyText.textContent;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
Swal.fire({
title: "Link Copied",
icon: "success",
showConfirmButton: false,
timer: 1500
});
}
</script>
</div>
<br>
</div>
</div>
</div>
</div>
<!-- /.container-fluid -->
</div>
<noscript>
alert("please enable JavaScript in your browser");
</noscript>
<script>
function myDownloadV2() {
var e = document.getElementById("down-id").innerHTML;
document.getElementById("down").innerHTML = '<i class="fa fa-spinner fa-spin"></i> Downloading.. !',
document.getElementById("down").disabled = !0;
var b = window.setTimeout(function() {
document.getElementById("down").innerHTML = '<i class="fa fa-cog fa-spin fa-lg"></i> Generating Link.. !'
}, 6e3);
window.setTimeout(function() {
$.ajax({
url: "/ajax.php?ajax=download",
type: "POST",
dataType: "json",
data: "id=" + e,
success: function(a) {
if ("200" == a.code) {
window.clearTimeout(b);
localStorage.setItem("dls", JSON.stringify(a?.data));
// window.location.href = a.file;
window.location.href = "/newdl";
} else swal.fire("Code : " + a.code, "Message : " + a.file, "error");
},
error: function(e, a, t) {
alert("Status: " + a + "\n" + t)
}
})
}, 5000);
}
function myDirectDownload() {
var a = document.getElementById("down-id").innerHTML;
document.getElementById("dl-down").innerHTML = '<i class="fa fa-spinner fa-spin"></i> Generating Link ...';
document.getElementById("dl-down").disabled = !0;
var b = window.setTimeout(function() {
document.getElementById("dl-down").innerHTML = '<i class="fa fa-cog fa-spin fa-lg"></i>Downloading .. !';
}, 6e3);
window.setTimeout(function() {
$.ajax({
url: "/ajax.php?ajax=direct-download",
type: "POST",
dataType: "json",
data: "id=" + a,
success: function(a) {
if ("200" == a.code) {
window.clearTimeout(b);
localStorage.setItem("dls", JSON.stringify(a?.data));
// window.location.href = a.file;
window.location.href = "/newdl";
} else swal.fire("Code : " + a.code, "Message : " + a.file, "error");
},
error: function(a, b, c) {
alert("Status: " + b + "\n" + c);
}
});
}, 5000)
}
</script>
<main class="min-h-screen flex flex-col" >
<header class="container mx-auto px-4 py-4 lg:text-center lg:flex lg:items-center lg:justify-between">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src='https://kit.fontawesome.com/a076d05399.js' crossorigin='anonymous'></script>
<!--
<footer class="sticky-footer bg-white">
<div class="container my-auto">
<div class="copyright text-center my-auto">
<span>Copyright &copy; HubDrive 2026</span>
</div>
<br>
<div class="copyright text-center my-auto" style="font-size: 15px; font-weight: 600;">
<span><a href="/page/privacy-policy"><i class="fa fa-bullhorn text-danger"></i> Privacy Policy</a> | <a href="/page/terms-conditions"><i class="fa fa-book text-info"></i> Terms & Conditions</a> | <a href="/page/copyright-policy"><i class="fa fa-copyright text-warning"></i> Copyright Policy</a> | <a href="/page/contact-us"><i class="fa fa-envelope text-success"></i> Contact Us</a></span>
</div>
</div>
</footer>
-->
</div>
<!-- End of Content Wrapper -->
</div>
<!-- End of Page Wrapper -->
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-angle-up"></i>
</a>
<!-- Logout Modal-->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Are you sure?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<a class="btn btn-primary" href="/login.php?action=logout">Logout</a>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="/assets/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="/assets/vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="/assets/js/sb-admin-2.min.js"></script>
<!-- Page level plugins -->
</body>
</html>

View file

@ -7,7 +7,12 @@ exports[`HubCloud handle crayon shin-chan 1993 1`] = `
"label": "HubCloud (FSL)", "label": "HubCloud (FSL)",
"meta": { "meta": {
"bytes": 28550795100, "bytes": 28550795100,
"countryCodes": [
"hi",
"ja",
],
"extractorId": "hubcloud_fsl", "extractorId": "hubcloud_fsl",
"height": 1080,
"title": "Crayon Shinchan Action Kamen vs Demon (1993) 1080p BluRay REMUX AVC [Hindi DD 2.0 + Japanese FLAC 2.0] (PitiPati-Ionicboy) (4kHDHub.com).mkv", "title": "Crayon Shinchan Action Kamen vs Demon (1993) 1080p BluRay REMUX AVC [Hindi DD 2.0 + Japanese FLAC 2.0] (PitiPati-Ionicboy) (4kHDHub.com).mkv",
}, },
"ttl": 43200000, "ttl": 43200000,
@ -23,7 +28,12 @@ exports[`HubCloud handle dark 2017 s03e08 1`] = `
"label": "HubCloud (FSL)", "label": "HubCloud (FSL)",
"meta": { "meta": {
"bytes": 3382286745, "bytes": 3382286745,
"countryCodes": [
"de",
"en",
],
"extractorId": "hubcloud_fsl", "extractorId": "hubcloud_fsl",
"height": 1080,
"title": "Dark.S03E08.The.Paradise.1080p.NF.WEB-DL.English.DDP5.1-German.DDP5.1.H.264-4kHdHub.Com.mkv", "title": "Dark.S03E08.The.Paradise.1080p.NF.WEB-DL.English.DDP5.1-German.DDP5.1.H.264-4kHdHub.Com.mkv",
}, },
"ttl": 43200000, "ttl": 43200000,
@ -34,7 +44,12 @@ exports[`HubCloud handle dark 2017 s03e08 1`] = `
"label": "HubCloud (PixelServer)", "label": "HubCloud (PixelServer)",
"meta": { "meta": {
"bytes": 3382286745, "bytes": 3382286745,
"countryCodes": [
"de",
"en",
],
"extractorId": "hubcloud_pixelserver", "extractorId": "hubcloud_pixelserver",
"height": 1080,
"title": "Dark.S03E08.The.Paradise.1080p.NF.WEB-DL.English.DDP5.1-German.DDP5.1.H.264-4kHdHub.Com.mkv", "title": "Dark.S03E08.The.Paradise.1080p.NF.WEB-DL.English.DDP5.1-German.DDP5.1.H.264-4kHdHub.Com.mkv",
}, },
"requestHeaders": { "requestHeaders": {
@ -53,7 +68,9 @@ exports[`HubCloud handle dexter original sin 2024 s01e01 1`] = `
"label": "HubCloud (FSL)", "label": "HubCloud (FSL)",
"meta": { "meta": {
"bytes": 5733781340, "bytes": 5733781340,
"countryCodes": [],
"extractorId": "hubcloud_fsl", "extractorId": "hubcloud_fsl",
"height": 2160,
"title": "Dexter.Original.Sin.S01E01.2160p.SKST.WEB-DL.H.265.DDP5.1-Tyrell.mkv", "title": "Dexter.Original.Sin.S01E01.2160p.SKST.WEB-DL.H.265.DDP5.1-Tyrell.mkv",
}, },
"ttl": 43200000, "ttl": 43200000,
@ -64,7 +81,9 @@ exports[`HubCloud handle dexter original sin 2024 s01e01 1`] = `
"label": "HubCloud (PixelServer)", "label": "HubCloud (PixelServer)",
"meta": { "meta": {
"bytes": 5733781340, "bytes": 5733781340,
"countryCodes": [],
"extractorId": "hubcloud_pixelserver", "extractorId": "hubcloud_pixelserver",
"height": 2160,
"title": "Dexter.Original.Sin.S01E01.2160p.SKST.WEB-DL.H.265.DDP5.1-Tyrell.mkv", "title": "Dexter.Original.Sin.S01E01.2160p.SKST.WEB-DL.H.265.DDP5.1-Tyrell.mkv",
}, },
"requestHeaders": { "requestHeaders": {
@ -83,7 +102,12 @@ exports[`HubCloud handle goat 2026 1`] = `
"label": "HubCloud (FSL)", "label": "HubCloud (FSL)",
"meta": { "meta": {
"bytes": 19091129630, "bytes": 19091129630,
"countryCodes": [
"en",
"hi",
],
"extractorId": "hubcloud_fsl", "extractorId": "hubcloud_fsl",
"height": 2160,
"title": "Goat (2026) 2160p 10bit HDR10+ DV iTunes WEB-DL HEVC x265 [Hindi AMZN DDP 5.1 640kbps + English DDP Atmos 5.1] ESub-(BYNDR-4kHdHub).mkv", "title": "Goat (2026) 2160p 10bit HDR10+ DV iTunes WEB-DL HEVC x265 [Hindi AMZN DDP 5.1 640kbps + English DDP Atmos 5.1] ESub-(BYNDR-4kHdHub).mkv",
}, },
"ttl": 43200000, "ttl": 43200000,
@ -94,7 +118,12 @@ exports[`HubCloud handle goat 2026 1`] = `
"label": "HubCloud (FSLv2)", "label": "HubCloud (FSLv2)",
"meta": { "meta": {
"bytes": 19091129630, "bytes": 19091129630,
"countryCodes": [
"en",
"hi",
],
"extractorId": "hubcloud_fslv2", "extractorId": "hubcloud_fslv2",
"height": 2160,
"title": "Goat (2026) 2160p 10bit HDR10+ DV iTunes WEB-DL HEVC x265 [Hindi AMZN DDP 5.1 640kbps + English DDP Atmos 5.1] ESub-(BYNDR-4kHdHub).mkv", "title": "Goat (2026) 2160p 10bit HDR10+ DV iTunes WEB-DL HEVC x265 [Hindi AMZN DDP 5.1 640kbps + English DDP Atmos 5.1] ESub-(BYNDR-4kHdHub).mkv",
}, },
"ttl": 43200000, "ttl": 43200000,

View file

@ -7,7 +7,12 @@ exports[`HubDrive handle avatar 1`] = `
"label": "HubCloud (FSL)", "label": "HubCloud (FSL)",
"meta": { "meta": {
"bytes": 64649995223, "bytes": 64649995223,
"countryCodes": [
"en",
"hi",
],
"extractorId": "hubcloud_fsl", "extractorId": "hubcloud_fsl",
"height": 2160,
"title": "Avatar (2009) Extended Collector's Edition IMAX 2160p UHD BluRay REMUX DV HDR 10bit HEVC [Hindi DDP 5.1 + English DTS-HD MA 5.1] x265 (LM-LUMiX) 4KHDHUB.[Com].mkv", "title": "Avatar (2009) Extended Collector's Edition IMAX 2160p UHD BluRay REMUX DV HDR 10bit HEVC [Hindi DDP 5.1 + English DTS-HD MA 5.1] x265 (LM-LUMiX) 4KHDHUB.[Com].mkv",
}, },
"ttl": 43200000, "ttl": 43200000,
@ -15,3 +20,5 @@ exports[`HubDrive handle avatar 1`] = `
}, },
] ]
`; `;
exports[`HubDrive handle missing hubcloud 1`] = `[]`;

View file

@ -4,10 +4,10 @@ import { BasicAcceptedElems, CheerioAPI } from 'cheerio';
import { AnyNode } from 'domhandler'; import { AnyNode } from 'domhandler';
import levenshtein from 'fast-levenshtein'; import levenshtein from 'fast-levenshtein';
import memoize from 'memoizee'; import memoize from 'memoizee';
import rot13Cipher from 'rot13-cipher';
import { ContentType } from 'stremio-addon-sdk'; import { ContentType } from 'stremio-addon-sdk';
import { Context, CountryCode, Meta } from '../types'; import { Context, CountryCode, Meta } from '../types';
import { Fetcher, findCountryCodes, getTmdbId, getTmdbNameAndYear, Id, TmdbId } from '../utils'; import { Fetcher, findCountryCodes, getTmdbId, getTmdbNameAndYear, Id, TmdbId } from '../utils';
import { resolveRedirectUrl } from './hd-hub-helper';
import { Source, SourceResult } from './Source'; import { Source, SourceResult } from './Source';
export class FourKHDHub extends Source { export class FourKHDHub extends Source {
@ -115,7 +115,7 @@ export class FourKHDHub extends Source {
.get(0); .get(0);
if (redirectUrlHubCloud) { if (redirectUrlHubCloud) {
return { url: await this.resolveRedirectUrl(ctx, redirectUrlHubCloud), meta }; return { url: await resolveRedirectUrl(ctx, this.fetcher, redirectUrlHubCloud), meta };
} }
const redirectUrlHubDrive = $('a', el) const redirectUrlHubDrive = $('a', el)
@ -123,17 +123,9 @@ export class FourKHDHub extends Source {
.map((_i, el) => new URL($(el).attr('href') as string)) .map((_i, el) => new URL($(el).attr('href') as string))
.get(0) as URL; .get(0) as URL;
return { url: await this.resolveRedirectUrl(ctx, redirectUrlHubDrive), meta }; return { url: await resolveRedirectUrl(ctx, this.fetcher, redirectUrlHubDrive), meta };
}; };
private async resolveRedirectUrl(ctx: Context, redirectUrl: URL): Promise<URL> {
const redirectHtml = await this.fetcher.text(ctx, redirectUrl);
const redirectDataMatch = redirectHtml.match(/'o','(.*?)'/) as string[];
const redirectData = JSON.parse(atob(rot13Cipher(atob(atob(redirectDataMatch[1] as string))))) as { o: string };
return new URL(atob(redirectData['o']));
}
private readonly getBaseUrl = async (ctx: Context): Promise<URL> => { private readonly getBaseUrl = async (ctx: Context): Promise<URL> => {
return await this.fetcher.getFinalRedirectUrl(ctx, new URL(this.baseUrl)); return await this.fetcher.getFinalRedirectUrl(ctx, new URL(this.baseUrl));
}; };

View file

@ -0,0 +1,33 @@
import { createTestContext } from '../test';
import { FetcherMock, ImdbId } from '../utils';
import { HDHub4u } from './HDHub4u';
const ctx = createTestContext();
describe('HDHub4u', () => {
let source: HDHub4u;
beforeEach(() => {
source = new HDHub4u(new FetcherMock(`${__dirname}/__fixtures__/HDHub4u`));
});
test('handle superman 2025', async () => {
const streams = await source.handle(ctx, 'movie', new ImdbId('tt5950044', undefined, undefined));
expect(streams).toMatchSnapshot();
});
test('handle the bone temple 2026', async () => {
const streams = await source.handle(ctx, 'movie', new ImdbId('tt32141377', undefined, undefined));
expect(streams).toMatchSnapshot();
});
test('handle stranger things s05e01', async () => {
const streams = await source.handle(ctx, 'series', new ImdbId('tt4574334', 5, 1));
expect(streams).toMatchSnapshot();
});
test('handle stranger things s05e08', async () => {
const streams = await source.handle(ctx, 'series', new ImdbId('tt4574334', 5, 8));
expect(streams).toMatchSnapshot();
});
});

119
src/source/HDHub4u.ts Normal file
View file

@ -0,0 +1,119 @@
import * as cheerio from 'cheerio';
import { ContentType } from 'stremio-addon-sdk';
import { Context, CountryCode, Meta } from '../types';
import { Fetcher, findCountryCodes, getImdbId, Id, ImdbId } from '../utils';
import { resolveRedirectUrl } from './hd-hub-helper';
import { Source, SourceResult } from './Source';
interface SearchResponsePartial {
hits: {
document: {
imdb_id: string;
permalink: string;
post_title: string;
};
}[];
}
export class HDHub4u extends Source {
public readonly id = 'hdhub4u';
public readonly label = 'HDHub4u';
public readonly contentTypes: ContentType[] = ['movie', 'series'];
public readonly countryCodes: CountryCode[] = [CountryCode.multi, CountryCode.gu, CountryCode.hi, CountryCode.ml, CountryCode.pa, CountryCode.ta, CountryCode.te];
public readonly baseUrl = 'https://new5.hdhub4u.fo';
private readonly searchUrl = 'https://search.pingora.fyi';
private readonly fetcher: Fetcher;
public constructor(fetcher: Fetcher) {
super();
this.fetcher = fetcher;
}
public async handleInternal(ctx: Context, _type: string, id: Id): Promise<SourceResult[]> {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const pageUrls = await this.fetchPageUrls(ctx, imdbId);
return (await Promise.all(
pageUrls.map(async (pageUrl) => {
return await this.handlePage(ctx, pageUrl, imdbId);
}),
)).flat();
};
private readonly handlePage = async (ctx: Context, pageUrl: URL, imdbId: ImdbId): Promise<SourceResult[]> => {
const html = await this.fetcher.text(ctx, pageUrl);
const $ = cheerio.load(html);
const meta = {
countryCodes: [CountryCode.multi, ...findCountryCodes($('div:contains("Language"):not(:has(div)):first').text())],
};
if (!imdbId.episode) {
return [
...this.extractHubDriveUrlResults(html, meta),
...(await Promise.all(
$('a[href*="gadgetsweb"]').map((_i, el) => this.handleHubLinks(ctx, new URL($(el).attr('href') as string), pageUrl, meta)),
)).flat(),
];
}
return [
...(await Promise.all(
$(`a:contains("EPiSODE ${imdbId.episode}"), a:contains("EPiSODE ${String(imdbId.episode).padStart(2, '0')}")`)
.map(async (_i, el) => this.handleHubLinks(ctx, new URL($(el).attr('href') as string), pageUrl, meta)),
)).flat(),
...this.extractHubDriveUrlResults(
$(`h4:contains("EPiSODE ${imdbId.episode}"), h4:contains("EPiSODE ${String(imdbId.episode).padStart(2, '0')}")`)
.first()
.nextUntil('hr')
.map((_i, el) => $.html(el))
.get()
.join(''),
meta,
),
];
};
private readonly handleHubLinks = async (ctx: Context, redirectUrl: URL, refererUrl: URL, meta: Meta): Promise<SourceResult[]> => {
const hubLinksUrl = await resolveRedirectUrl(ctx, this.fetcher, redirectUrl);
const hubLinksHtml = await this.fetcher.text(ctx, hubLinksUrl, { headers: { Referer: refererUrl.href } });
return [
...this.extractHubDriveUrlResults(hubLinksHtml, { ...meta, referer: hubLinksUrl.href }),
];
};
private readonly extractHubDriveUrlResults = (html: string, meta: Meta): SourceResult[] => {
const $ = cheerio.load(html);
return $('a[href*="hubdrive"]:not(:contains("⚡"))')
.map((_i, el) => ({ url: new URL($(el).attr('href') as string), meta }))
.toArray();
};
private readonly fetchPageUrls = async (ctx: Context, imdbId: ImdbId): Promise<URL[]> => {
const searchUrl = new URL(`/collections/post/documents/search?query_by=imdb_id&q=${encodeURIComponent(imdbId.id)}`, this.searchUrl);
const searchResponse = await this.fetcher.json(ctx, searchUrl, { headers: { Referer: this.baseUrl } }) as SearchResponsePartial;
return searchResponse.hits
.filter(hit =>
hit.document.imdb_id === imdbId.id
&& (
!imdbId.season
|| hit.document.post_title.includes(`Season ${imdbId.season}`)
|| hit.document.post_title.includes(`S${String(imdbId.season)}`)
|| hit.document.post_title.includes(`S${String(imdbId.season).padStart(2, '0')}`)
),
)
.map(hit => new URL(hit.document.permalink, this.baseUrl));
};
}

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkVGSU5VaE5iVVZCVFcwd09WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkVGSU5VaE5iVVZDUkVjd09WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkVGSU5VaE5iVVZDU0Vjd09WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkVGSU5VaE5iVVZCY1cwd09WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkhKU01WSklSMU5DY25oSU9WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkhKU01WSklSMU5DY25oU09WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkhKU01WSkhTM1ZCU1ZSbU9WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkhKU01WSkhTM1ZCU1ZSd09WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Redirecting ...</title>
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex, nofollow">
<meta name="author" content="s-b0t">
<meta name="googlebot" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<style>#container{width:100%;aspect-ratio:100 / 29}@media only screen and (min-width: 992px){img{height:80%!important;width:50%!important}}@media only screen and (min-width: 1200px){img{height:80%!important;width:50%!important}}.relative{position:relative}.bottom-left{position:absolute}.PurpleTxt{color:#7e41ff!important;font-weight:600;margin-left:.5ch;margin-right:.5ch}</style>
</head>
<body>
Redirecting ..
<script type="text/javascript">
function stck(e,t,i){let n="";if(i){let o=new Date;o.setTime(o.getTime()+6e4*i),n="; expires="+o.toUTCString()}document.cookie=`${e}=${t}; path=/; SameSite=None; Secure${n}`}
function s(e,t,i){let n=new Date,m={value:t,expiry:n.getTime()+i};window.localStorage.setItem(e,JSON.stringify(m));stck('xla',"s4t",4);}s('o','Y214WE0xWjNZbXRhVUdwMmIxQldObFo2ZFRCeFZVOXRRbmxxYVV0UU9YZHdZWGxxY1ZRNVkyOWhRV05OTW5Vd2NHdzFiVzVMUlhsTFVEbGliekl4ZVc5VVUyaE5WRWxzUzFBNGRsbFFWMmxXZDJKMlRFaDFSbHBVUVZaSFIwMWFja2Q1YVVwS01UUndWRmRuY1ZWaldtOUpWMkpLZUZvMWJsUkJaMGQ2T1hWS1UyTm1URE40TkhKU01WSkdTMk5CU1ZSbU9WWmhNRDA9',180*1000);setTimeout(()=>window.location.href='https://cryptoinsights.site/homelander/', 2000);
</script>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"facet_counts":[],"found":2,"hits":[{"document":{"category":["300MB Movies","Dual Audio","HD Movies","Hindi Dubbed","HollyWood","Horror","Sci-Fi","Thriller"],"director":["Nia DaCosta"],"id":"183681","imdb_id":"tt32141377","permalink":"/28-years-later-the-bone-temple-2026-hindi-webrip-full-movie/","post_date":"February 17, 2026","post_thumbnail":"https://image.tmdb.org/t/p/w500/fVlbHQvKgZHF1uEIYePZqpfDqaJ.jpg","post_title":"28 Years Later: The Bone Temple (2026) WEB-DL [Hindi (DD5.1) & English] 4K 1080p 720p & 480p Dual Audio [x264/10Bit-HEVC] | Full Movie","post_type":"post","sort_by_date":1771333131,"stars":["Ralph Fiennes","Jack O'Connell","Alfie Williams"]},"highlight":{"imdb_id":{"matched_tokens":["tt32141377"],"snippet":"<mark>tt32141377</mark>"}},"highlights":[{"field":"imdb_id","matched_tokens":["tt32141377"],"snippet":"<mark>tt32141377</mark>"}],"text_match":578730123365711993,"text_match_info":{"best_field_score":"1108091339008","best_field_weight":15,"fields_matched":1,"num_tokens_dropped":0,"score":"578730123365711993","tokens_matched":1,"typo_prefix_score":0}},{"document":{"category":["HollyWood","Horror","Sci-Fi","Thriller"],"director":["Nia DaCosta"],"id":"182051","imdb_id":"tt32141377","permalink":"/28-years-later-the-bone-temple-2026-hdtc-hindi-full-movie/","post_date":"January 19, 2026","post_thumbnail":"https://image.tmdb.org/t/p/w400/kK1BGkG3KAvWB0WMV1DfOx9yTMZ.jpg","post_title":"28 Years Later: The Bone Temple (2026) HDTC [Hindi (Clean)] 1080p 720p & 480p [x264] | Full Movie","post_type":"post","sort_by_date":1768822585,"stars":["Ralph Fiennes","Jack O'Connell","Alfie Williams"]},"highlight":{"imdb_id":{"matched_tokens":["tt32141377"],"snippet":"<mark>tt32141377</mark>"}},"highlights":[{"field":"imdb_id","matched_tokens":["tt32141377"],"snippet":"<mark>tt32141377</mark>"}],"text_match":578730123365711993,"text_match_info":{"best_field_score":"1108091339008","best_field_weight":15,"fields_matched":1,"num_tokens_dropped":0,"score":"578730123365711993","tokens_matched":1,"typo_prefix_score":0}}],"out_of":13464,"page":1,"request_params":{"collection_name":"post","first_q":"tt32141377","per_page":10,"q":"tt32141377"},"search_cutoff":false,"search_time_ms":0}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"facet_counts":[],"found":1,"hits":[{"document":{"category":["300MB Movies","Action","Adventure","Dual Audio","HD Movies","Hindi Dubbed","HollyWood","Sci-Fi"],"director":["James Gunn"],"id":"174394","imdb_id":"tt5950044","permalink":"/superman-2025-hindi-webrip-full-movie/","post_date":"August 24, 2025","post_thumbnail":"https://image.tmdb.org/t/p/w500/1H9hdeRoniz30RKhyr1uLdBTfpG.jpg","post_title":"Superman (2025) iMAX DS4K WEB-DL [Hindi (DD5.1) & English] 4K 1080p 720p & 480p Dual Audio [x264/10Bit-HEVC] | Full Movie","post_type":"post","sort_by_date":1756030053,"stars":["David Corenswet","Rachel Brosnahan","Nicholas Hoult"]},"highlight":{"imdb_id":{"matched_tokens":["tt5950044"],"snippet":"<mark>tt5950044</mark>"}},"highlights":[{"field":"imdb_id","matched_tokens":["tt5950044"],"snippet":"<mark>tt5950044</mark>"}],"text_match":578730123365711993,"text_match_info":{"best_field_score":"1108091339008","best_field_weight":15,"fields_matched":1,"num_tokens_dropped":0,"score":"578730123365711993","tokens_matched":1,"typo_prefix_score":0}}],"out_of":13464,"page":1,"request_params":{"collection_name":"post","first_q":"tt5950044","per_page":10,"q":"tt5950044"},"search_cutoff":false,"search_time_ms":0}

View file

@ -0,0 +1,304 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`HDHub4u handle stranger things s05e01 1`] = `
[
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/2452204677",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/1788432714",
},
]
`;
exports[`HDHub4u handle stranger things s05e08 1`] = `
[
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/1793632174",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/2655940410",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/2007803192",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/2913823672",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/1898122266",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/3043520687",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/4664307676",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/102319",
},
"url": "https://hubdrive.space/file/8835271729",
},
]
`;
exports[`HDHub4u handle superman 2025 1`] = `
[
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/2048323831",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/1931317982",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/7875454809",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/3608508909",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/95885",
},
"url": "https://hubdrive.space/file/2171324800",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/95884",
},
"url": "https://hubdrive.space/file/1866728891",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/95883",
},
"url": "https://hubdrive.space/file/3938026297",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/95882",
},
"url": "https://hubdrive.space/file/2823990434",
},
]
`;
exports[`HDHub4u handle the bone temple 2026 1`] = `
[
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/2165806385",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/2777734670",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/6051118182",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
},
"url": "https://hubdrive.space/file/13783802255",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/104571",
},
"url": "https://hubdrive.space/file/2026023433",
},
{
"meta": {
"countryCodes": [
"multi",
"en",
"hi",
],
"referer": "https://hblinks.dad/archives/104570",
},
"url": "https://hubdrive.space/file/4232622187",
},
{
"meta": {
"countryCodes": [
"multi",
"hi",
],
},
"url": "https://hubdrive.space/file/2298599706",
},
{
"meta": {
"countryCodes": [
"multi",
"hi",
],
"referer": "https://hblinks.dad/archives/103118",
},
"url": "https://hubdrive.space/file/2291074311",
},
{
"meta": {
"countryCodes": [
"multi",
"hi",
],
"referer": "https://hblinks.dad/archives/103119",
},
"url": "https://hubdrive.space/file/2244066767",
},
]
`;

View file

@ -0,0 +1,11 @@
import rot13Cipher from 'rot13-cipher';
import { Context } from '../types';
import { Fetcher } from '../utils';
export const resolveRedirectUrl = async (ctx: Context, fetcher: Fetcher, redirectUrl: URL): Promise<URL> => {
const redirectHtml = await fetcher.text(ctx, redirectUrl);
const redirectDataMatch = redirectHtml.match(/'o','(.*?)'/) as string[];
const redirectData = JSON.parse(atob(rot13Cipher(atob(atob(redirectDataMatch[1] as string))))) as { o: string };
return new URL(atob(redirectData['o']));
};

View file

@ -6,6 +6,7 @@ import { Eurostreaming } from './Eurostreaming';
import { FourKHDHub } from './FourKHDHub'; import { FourKHDHub } from './FourKHDHub';
import { Frembed } from './Frembed'; import { Frembed } from './Frembed';
import { FrenchCloud } from './FrenchCloud'; import { FrenchCloud } from './FrenchCloud';
import { HDHub4u } from './HDHub4u';
import { HomeCine } from './HomeCine'; import { HomeCine } from './HomeCine';
import { KinoGer } from './KinoGer'; import { KinoGer } from './KinoGer';
import { Kokoshka } from './Kokoshka'; import { Kokoshka } from './Kokoshka';
@ -28,6 +29,7 @@ export const createSources = (fetcher: Fetcher): Source[] => {
return [ return [
// multi // multi
new FourKHDHub(fetcher), new FourKHDHub(fetcher),
new HDHub4u(fetcher),
new VixSrc(fetcher), new VixSrc(fetcher),
new VidSrc(), new VidSrc(),
new RgShows(fetcher), new RgShows(fetcher),

View file

@ -128,7 +128,7 @@ describe('resolve', () => {
protected readonly extractInternal = async (): Promise<UrlResult[]> => protected readonly extractInternal = async (): Promise<UrlResult[]> =>
[ [
{ {
url: new URL('https://example.com'), url: new URL('https://example1.com'),
format: Format.unknown, format: Format.unknown,
isExternal: true, isExternal: true,
label: 'hoster.com', label: 'hoster.com',
@ -137,106 +137,11 @@ describe('resolve', () => {
countryCodes: [CountryCode.de], countryCodes: [CountryCode.de],
}, },
}, },
{
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new BlockedError(new URL('https://example.com'), BlockedReason.cloudflare_challenge, {}),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new BlockedError(new URL('https://example.com'), BlockedReason.cloudflare_censor, {}),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new BlockedError(new URL('https://example.com'), BlockedReason.media_flow_proxy_auth, {}),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new BlockedError(new URL('https://example.com'), BlockedReason.unknown, {}),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://working2.com'),
format: Format.unknown,
label: 'working1',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new TooManyRequestsError(new URL('https://example.com'), 10),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new TooManyTimeoutsError(new URL('https://example.com')),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://working1.com'),
format: Format.unknown,
label: 'working2',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example2.com'),
format: Format.unknown,
isExternal: true,
error: new TypeError(),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{ {
url: new URL('https://example2.com'), url: new URL('https://example2.com'),
format: Format.unknown, format: Format.unknown,
isExternal: true, isExternal: true,
error: new TimeoutError(new URL('https://example2.com')), error: new BlockedError(new URL('https://example2.com'), BlockedReason.cloudflare_challenge, {}),
label: 'hoster.com', label: 'hoster.com',
ttl: this.ttl, ttl: this.ttl,
meta: { meta: {
@ -247,7 +152,7 @@ describe('resolve', () => {
url: new URL('https://example3.com'), url: new URL('https://example3.com'),
format: Format.unknown, format: Format.unknown,
isExternal: true, isExternal: true,
error: new QueueIsFullError(new URL('https://example3.com')), error: new BlockedError(new URL('https://example3.com'), BlockedReason.cloudflare_censor, {}),
label: 'hoster.com', label: 'hoster.com',
ttl: this.ttl, ttl: this.ttl,
meta: { meta: {
@ -258,7 +163,7 @@ describe('resolve', () => {
url: new URL('https://example4.com'), url: new URL('https://example4.com'),
format: Format.unknown, format: Format.unknown,
isExternal: true, isExternal: true,
error: new HttpError(new URL('https://example4.com'), 500, 'Internal Server Error', { 'x-foo': 'bar' }), error: new BlockedError(new URL('https://example4.com'), BlockedReason.media_flow_proxy_auth, {}),
label: 'hoster.com', label: 'hoster.com',
ttl: this.ttl, ttl: this.ttl,
meta: { meta: {
@ -269,7 +174,102 @@ describe('resolve', () => {
url: new URL('https://example5.com'), url: new URL('https://example5.com'),
format: Format.unknown, format: Format.unknown,
isExternal: true, isExternal: true,
error: new HttpError(new URL('https://example5.com'), 418, 'I\'m a tea pot', { 'x-foo': 'bar' }), error: new BlockedError(new URL('https://example5.com'), BlockedReason.unknown, {}),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example6.com'),
format: Format.unknown,
label: 'working1',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example7.com'),
format: Format.unknown,
isExternal: true,
error: new TooManyRequestsError(new URL('https://example7.com'), 10),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example8.com'),
format: Format.unknown,
isExternal: true,
error: new TooManyTimeoutsError(new URL('https://example8.com')),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example9.com'),
format: Format.unknown,
label: 'working2',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example10.com'),
format: Format.unknown,
isExternal: true,
error: new TypeError(),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example11.com'),
format: Format.unknown,
isExternal: true,
error: new TimeoutError(new URL('https://example11.com')),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example12.com'),
format: Format.unknown,
isExternal: true,
error: new QueueIsFullError(new URL('https://example12.com')),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example13.com'),
format: Format.unknown,
isExternal: true,
error: new HttpError(new URL('https://example13.com'), 500, 'Internal Server Error', { 'x-foo': 'bar' }),
label: 'hoster.com',
ttl: this.ttl,
meta: {
countryCodes: [CountryCode.de],
},
},
{
url: new URL('https://example14.com'),
format: Format.unknown,
isExternal: true,
error: new HttpError(new URL('https://example14.com'), 418, 'I\'m a tea pot', { 'x-foo': 'bar' }),
label: 'hoster.com', label: 'hoster.com',
ttl: this.ttl, ttl: this.ttl,
meta: { meta: {

View file

@ -144,6 +144,10 @@ export class StreamResolver {
streams.push( streams.push(
...urlResults.filter(urlResult => (!urlResult.error || showErrors(ctx.config)) && !isResolutionExcluded(ctx.config, getClosestResolution(urlResult.meta?.height))) ...urlResults.filter(urlResult => (!urlResult.error || showErrors(ctx.config)) && !isResolutionExcluded(ctx.config, getClosestResolution(urlResult.meta?.height)))
.filter((urlResult, index, self) =>
// Remove duplicate URLs
index === self.findIndex(t => t.url.href === urlResult.url.href),
)
.map(urlResult => ({ .map(urlResult => ({
...this.buildUrl(urlResult), ...this.buildUrl(urlResult),
name: this.buildName(ctx, urlResult), name: this.buildName(ctx, urlResult),

View file

@ -10,7 +10,7 @@ exports[`resolve adds error info 1`] = `
}, },
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 working1", "title": "🔗 working1",
"url": "https://working2.com/", "url": "https://example6.com/",
}, },
{ {
"behaviorHints": { "behaviorHints": {
@ -19,14 +19,14 @@ exports[`resolve adds error info 1`] = `
}, },
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 working2", "title": "🔗 working2",
"url": "https://working1.com/", "url": "https://example9.com/",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example1.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com", "title": "🔗 hoster.com",
}, },
@ -43,47 +43,47 @@ exports[`resolve adds error info 2`] = `
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example5.com/", "externalUrl": "https://example14.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
❌ Request to example5.com failed with status 418 (I'm a tea pot). Request-id: test.", ❌ Request to example14.com failed with status 418 (I'm a tea pot). Request-id: test.",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example4.com/", "externalUrl": "https://example13.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
❌ Remote server example4.com has issues. We can't fix this, please try later again.", ❌ Remote server example13.com has issues. We can't fix this, please try later again.",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example3.com/", "externalUrl": "https://example12.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
⏳ Request queue for example3.com is full. Please try again later or consider self-hosting.", ⏳ Request queue for example12.com is full. Please try again later or consider self-hosting.",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example2.com/", "externalUrl": "https://example11.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
🐢 Request to example2.com timed out.", 🐢 Request to example11.com timed out.",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example2.com/", "externalUrl": "https://example10.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
❌ Request failed. Request-id: test.", ❌ Request failed. Request-id: test.",
@ -93,37 +93,37 @@ exports[`resolve adds error info 2`] = `
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example8.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
🚦 Too many recent timeouts when requesting example.com. Please try again later.", 🚦 Too many recent timeouts when requesting example8.com. Please try again later.",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example7.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
🚦 Request to example.com was rate-limited. Please try again later or consider self-hosting.", 🚦 Request to example7.com was rate-limited. Please try again later or consider self-hosting.",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example5.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
⚠️ Request to example.com was blocked. Reason: unknown", ⚠️ Request to example5.com was blocked. Reason: unknown",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example4.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
⚠️ MediaFlow Proxy authentication failed. Please set the correct password.", ⚠️ MediaFlow Proxy authentication failed. Please set the correct password.",
@ -133,20 +133,20 @@ exports[`resolve adds error info 2`] = `
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example3.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
⚠️ Request to example.com was blocked. Reason: cloudflare_censor", ⚠️ Request to example3.com was blocked. Reason: cloudflare_censor",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example2.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com "title": "🔗 hoster.com
⚠️ Request to example.com was blocked. Reason: cloudflare_challenge", ⚠️ Request to example2.com was blocked. Reason: cloudflare_challenge",
}, },
{ {
"behaviorHints": { "behaviorHints": {
@ -155,7 +155,7 @@ exports[`resolve adds error info 2`] = `
}, },
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 working1", "title": "🔗 working1",
"url": "https://working2.com/", "url": "https://example6.com/",
}, },
{ {
"behaviorHints": { "behaviorHints": {
@ -164,14 +164,14 @@ exports[`resolve adds error info 2`] = `
}, },
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 working2", "title": "🔗 working2",
"url": "https://working1.com/", "url": "https://example9.com/",
}, },
{ {
"behaviorHints": { "behaviorHints": {
"bingeGroup": "webstreamr-undefined-undefined-de", "bingeGroup": "webstreamr-undefined-undefined-de",
"notWebReady": true, "notWebReady": true,
}, },
"externalUrl": "https://example.com/", "externalUrl": "https://example1.com/",
"name": "WebStreamr 🇩🇪", "name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com", "title": "🔗 hoster.com",
}, },

View file

@ -11,7 +11,7 @@ exports[`buildManifest default manifest 1`] = `
"config": [ "config": [
{ {
"key": "multi", "key": "multi",
"title": "Multi 🌐 (4KHDHub, RgShows, VidSrc, VixSrc)", "title": "Multi 🌐 (4KHDHub, HDHub4u, RgShows, VidSrc, VixSrc)",
"type": "checkbox", "type": "checkbox",
}, },
{ {
@ -34,9 +34,14 @@ exports[`buildManifest default manifest 1`] = `
"title": "French 🇫🇷 (Frembed, FrenchCloud, Movix)", "title": "French 🇫🇷 (Frembed, FrenchCloud, Movix)",
"type": "checkbox", "type": "checkbox",
}, },
{
"key": "gu",
"title": "Gujarati 🇮🇳 (HDHub4u)",
"type": "checkbox",
},
{ {
"key": "hi", "key": "hi",
"title": "Hindi 🇮🇳 (4KHDHub)", "title": "Hindi 🇮🇳 (4KHDHub, HDHub4u)",
"type": "checkbox", "type": "checkbox",
}, },
{ {
@ -44,19 +49,29 @@ exports[`buildManifest default manifest 1`] = `
"title": "Italian 🇮🇹 (Eurostreaming, MostraGuarda, VixSrc)", "title": "Italian 🇮🇹 (Eurostreaming, MostraGuarda, VixSrc)",
"type": "checkbox", "type": "checkbox",
}, },
{
"key": "ml",
"title": "Malayalam 🇮🇳 (HDHub4u)",
"type": "checkbox",
},
{ {
"key": "mx", "key": "mx",
"title": "Latin American Spanish 🇲🇽 (CineHDPlus, Cuevana, HomeCine, VerHdLink)", "title": "Latin American Spanish 🇲🇽 (CineHDPlus, Cuevana, HomeCine, VerHdLink)",
"type": "checkbox", "type": "checkbox",
}, },
{
"key": "pa",
"title": "Punjabi 🇮🇳 (HDHub4u)",
"type": "checkbox",
},
{ {
"key": "ta", "key": "ta",
"title": "Tamil 🇮🇳 (4KHDHub)", "title": "Tamil 🇮🇳 (4KHDHub, HDHub4u)",
"type": "checkbox", "type": "checkbox",
}, },
{ {
"key": "te", "key": "te",
"title": "Telugu 🇮🇳 (4KHDHub)", "title": "Telugu 🇮🇳 (4KHDHub, HDHub4u)",
"type": "checkbox", "type": "checkbox",
}, },
{ {
@ -134,9 +149,9 @@ exports[`buildManifest default manifest 1`] = `
], ],
"description": "Provides HTTP URLs from streaming websites. Configure add-on for additional languages. Add MediaFlow proxy for more URLs. "description": "Provides HTTP URLs from streaming websites. Configure add-on for additional languages. Add MediaFlow proxy for more URLs.
Supported languages: Albanian, German, Castilian Spanish, French, Hindi, Italian, Latin American Spanish, Tamil, Telugu Supported languages: Albanian, German, Castilian Spanish, French, Gujarati, Hindi, Italian, Malayalam, Latin American Spanish, Punjabi, Tamil, Telugu
Supported sources: 4KHDHub, CineHDPlus, Cuevana, Einschalten, Eurostreaming, Frembed, FrenchCloud, HomeCine, KinoGer, Kokoshka, MegaKino, MeineCloud, MostraGuarda, Movix, RgShows, StreamKiste, VerHdLink, VidSrc, VixSrc Supported sources: 4KHDHub, CineHDPlus, Cuevana, Einschalten, Eurostreaming, Frembed, FrenchCloud, HDHub4u, HomeCine, KinoGer, Kokoshka, MegaKino, MeineCloud, MostraGuarda, Movix, RgShows, StreamKiste, VerHdLink, VidSrc, VixSrc
Supported extractors: ", Supported extractors: ",
"id": "webstreamr", "id": "webstreamr",

View file

@ -16,7 +16,7 @@ const typedEntries = <T extends object>(obj: T): [keyof T, T[keyof T]][] => (Obj
export const buildManifest = (sources: Source[], extractors: Extractor[], config: Config): CustomManifest => { export const buildManifest = (sources: Source[], extractors: Extractor[], config: Config): CustomManifest => {
const manifest: CustomManifest = { const manifest: CustomManifest = {
id: envGetAppId(), id: envGetAppId(),
version: '0.68.0', // x-release-please-version version: '0.69.1', // x-release-please-version
name: envGetAppName(), name: envGetAppName(),
description: 'Provides HTTP URLs from streaming websites. Configure add-on for additional languages. Add MediaFlow proxy for more URLs.', description: 'Provides HTTP URLs from streaming websites. Configure add-on for additional languages. Add MediaFlow proxy for more URLs.',
resources: [ resources: [

View file

@ -22,3 +22,11 @@ export const getClosestResolution = (height: number | undefined) => {
return Math.abs(curr - height) < Math.abs(prev - height) ? curr : prev; return Math.abs(curr - height) < Math.abs(prev - height) ? curr : prev;
})}p`; })}p`;
}; };
export const findHeight = (value: string): number | undefined => {
/* istanbul ignore next */
const height = parseInt(RESOLUTIONS.find(res => value.toLowerCase().includes(res))?.replace('p', '') ?? '', 10);
/* istanbul ignore next */
return isNaN(height) ? undefined : height;
};